# these commands herself, we don't care as much about helpful warnings,

# and we certainly don't want them to obscure actual errors.

# By default if the NPM registry is unavailable, it will timeout after more than 60s.

# These settings make sure we throw an error faster

/// 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 assert = require('assert');
var cleanup = require('../tool-env/cleanup.js');
var fs = require('fs');
var files = require('../fs/files.js');
var os = require('os');
var _ = require('underscore');
var httpHelpers = require('../utils/http-helpers.js');
var buildmessage = require('../utils/buildmessage.js');
var utils = require('../utils/utils.js');
var runLog = require('../runners/run-log.js');
var Profile = require('../tool-env/profile.js').Profile;
import { execFileAsync } from "../utils/processes.js";
import {
  get as getRebuildArgs
} from "../static-assets/server/npm-rebuild-args.js";
import {
  convert as convertColonsInPath
} from "../utils/colon-converter.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 =
    convertColonsInPath(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;
};

// Returns a flattened dictionary of npm package names used in production,
// or false if there is no package.json file in the parent directory.
export function getProdPackageNames(nodeModulesDir) {
  const names = Object.create(null);
  const dirs = Object.create(null);
  const nodeModulesDirStack = [];

  // Returns true iff dir is a package directory.
  function walk(dir) {
    const packageJsonPath = files.pathJoin(dir, "package.json");
    const packageJsonStat = files.statOrNull(packageJsonPath);

    if (packageJsonStat &&
        packageJsonStat.isFile()) {
      const pkg = JSON.parse(files.readFile(packageJsonPath));
      const nodeModulesDir = files.pathJoin(dir, "node_modules");
      nodeModulesDirStack.push(nodeModulesDir);

      // Scan all dependencies except pkg.devDependencies.
      scanDeps(pkg.dependencies);
      scanDeps(pkg.peerDependencies);
      scanDeps(pkg.optionalDependencies);
      scanDeps(pkg.bundledDependencies);
      // This typo is also honored.
      scanDeps(pkg.bundleDependencies);

      assert.strictEqual(
        nodeModulesDirStack.pop(),
        nodeModulesDir
      );

      return true;
    }

    return false;
  }

  function scanDeps(deps) {
    if (! deps) {
      return;
    }

    Object.keys(deps).forEach(name => {
      const resDir = resolve(name);
      if (! resDir || _.has(dirs, resDir)) {
        return;
      }

      // Record that we've seen this directory so that we don't try to
      // walk it again.
      dirs[resDir] = name;

      if (walk(resDir)) {
        // If resDir is indeed a package directory, record the package
        // name in the set of production names.
        names[name] = true;
      }
    });
  }

  function resolve(name) {
    for (let i = nodeModulesDirStack.length - 1; i >= 0; --i) {
      const nodeModulesDir = nodeModulesDirStack[i];
      const candidate = files.pathJoin(nodeModulesDir, name);
      const stat = files.statOrNull(candidate);
      if (stat && stat.isDirectory()) {
        return candidate;
      }
    }
  }

  // If the top-level nodeModulesDir is not contained by a package
  // directory with a package.json file, then we return false to indicate
  // that we don't know or care which packages are production-specific.
  // Concretely, this means your app needs to have a package.json file if
  // you want any npm packages to be excluded in production.
  return walk(files.pathDirname(nodeModulesDir)) && names;
}

const lastRebuildJSONFilename = ".meteor-last-rebuild-version.json";

const currentVersions = {
  platform: process.platform,
  arch: process.arch,
  versions: {...process.versions},
};

const currentVersionsJSON =
  JSON.stringify(currentVersions, null, 2) + "\n";

function recordLastRebuildVersions(pkgDir) {
  // Record the current process.{platform,arch,versions} so that we can
  // avoid copying/rebuilding/renaming next time.
  files.writeFile(
    files.pathJoin(pkgDir, lastRebuildJSONFilename),
    currentVersionsJSON,
    "utf8"
  );
}

// Returns true iff isSubtreeOf(currentVersions, versions), allowing
// valid semantic versions to differ in their patch versions.
function versionsAreCompatible(versions) {
  import { parse } from "semver";

  return isSubtreeOf(currentVersions, versions, (a, b) => {
    // Technically already handled by isSubtreeOf, but doesn't hurt.
    if (a === b) {
      return true;
    }

    if (! a || ! b) {
      return false;
    }

    const aType = typeof a;
    const bType = typeof b;

    if (aType !== bType) {
      return false;
    }

    if (aType === "string") {
      const aVer = parse(a);
      const bVer = parse(b);
      return aVer && bVer &&
        aVer.major === bVer.major &&
        aVer.minor === bVer.minor;
    }
  });
}

function rebuildVersionsAreCompatible(pkgPath) {
  const versionFile =
    files.pathJoin(pkgPath, lastRebuildJSONFilename);

  try {
    var versions = JSON.parse(files.readFile(versionFile));
  } catch (e) {
    if (! (e instanceof SyntaxError ||
           e.code === "ENOENT")) {
      throw e;
    }
  }

  return versionsAreCompatible(versions);
}

// Rebuilds any binary dependencies in the given node_modules directory,
// and returns true iff anything was rebuilt.
meteorNpm.rebuildIfNonPortable =
Profile("meteorNpm.rebuildIfNonPortable", function (nodeModulesDir) {
  const dirsToRebuild = [];

  files.readdir(nodeModulesDir).forEach(function (pkg) {
    const pkgPath = files.pathJoin(nodeModulesDir, pkg);

    if (isPortable(pkgPath)) {
      return;
    }

    if (rebuildVersionsAreCompatible(pkgPath)) {
      return;
    }

    dirsToRebuild.push(pkgPath);
  });

  if (dirsToRebuild.length === 0) {
    return false;
  }

  const tempDir = files.pathJoin(
    nodeModulesDir,
    ".temp-" + utils.randomToken()
  );

  // There's a chance the basename of the original nodeModulesDir isn't
  // actually "node_modules", which will confuse the `npm rebuild`
  // command, but fortunately we can ensure this temporary directory has
  // exactly that basename.
  const tempNodeModules = files.pathJoin(tempDir, "node_modules");
  files.mkdir_p(tempNodeModules);

  // Map from original package directory paths to temporary package
  // directory paths.
  const tempPkgDirs = {};

  dirsToRebuild.forEach(function (pkgPath) {
    const tempPkgDir = tempPkgDirs[pkgPath] = files.pathJoin(
      tempNodeModules,
      files.pathBasename(pkgPath)
    );

    // Copy the package directory instead of renaming it, so that the
    // original package will be left untouched if the rebuild fails. We
    // could just run files.cp_r(pkgPath, tempPkgDir) here, except that we
    // want to handle nested node_modules directories specially.
    copyNpmPackageWithSymlinkedNodeModules(pkgPath, tempPkgDir);

    // Record the current process.versions so that we can avoid
    // copying/rebuilding/renaming next time.
    recordLastRebuildVersions(tempPkgDir);
  });

  // The `npm rebuild` command must be run in the parent directory of the
  // relevant node_modules directory, which in this case is tempDir.
  const rebuildResult = runNpmCommand(getRebuildArgs(), tempDir);
  if (! rebuildResult.success) {
    buildmessage.error(rebuildResult.error);
    files.rm_recursive(tempDir);
    return false;
  }

  // If the `npm rebuild` command succeeded, overwrite the original
  // package directories with the rebuilt package directories.
  dirsToRebuild.forEach(function (pkgPath) {
    const actualNodeModulesDir =
      files.pathJoin(pkgPath, "node_modules");

    const actualNodeModulesStat =
      files.statOrNull(actualNodeModulesDir);

    if (actualNodeModulesStat &&
        actualNodeModulesStat.isDirectory()) {
      // If the original package had a node_modules directory, move it
      // into the temporary package directory, overwriting the one created
      // by copyNpmPackageWithSymlinkedNodeModules (which contains only
      // symlinks), so that when we rename the temporary directory back to
      // the original directory below, we'll end up with a node_modules
      // directory that contains real packages rather than symlinks.

      const symlinkNodeModulesDir =
        files.pathJoin(tempPkgDirs[pkgPath], "node_modules");

      files.renameDirAlmostAtomically(
        actualNodeModulesDir,
        symlinkNodeModulesDir
      );
    }

    files.renameDirAlmostAtomically(tempPkgDirs[pkgPath], pkgPath);
  });

  files.rm_recursive(tempDir);

  return true;
});

// Copy an npm package directory to another location, but attempt to
// symlink all of its node_modules rather than recursively copying them,
// which potentially saves a lot of time.
function copyNpmPackageWithSymlinkedNodeModules(fromPkgDir, toPkgDir) {
  files.mkdir_p(toPkgDir);

  let needToHandleNodeModules = false;

  files.readdir(fromPkgDir).forEach(item => {
    if (item === "node_modules") {
      // We'll link or copy node_modules in a follow-up step.
      needToHandleNodeModules = true;
      return;
    }

    files.cp_r(
      files.pathJoin(fromPkgDir, item),
      files.pathJoin(toPkgDir, item)
    );
  });

  if (! needToHandleNodeModules) {
    return;
  }

  const nodeModulesFromPath = files.pathJoin(fromPkgDir, "node_modules");
  const nodeModulesToPath = files.pathJoin(toPkgDir, "node_modules");

  files.mkdir(nodeModulesToPath);

  files.readdir(nodeModulesFromPath).forEach(depPath => {
    if (depPath === ".bin") {
      // Avoid copying node_modules/.bin because commands like
      // .bin/node-gyp and .bin/node-pre-gyp tend to cause problems.
      return;
    }

    const absDepFromPath = files.pathJoin(nodeModulesFromPath, depPath);

    if (! files.stat(absDepFromPath).isDirectory()) {
      // Only copy package directories, even though there might be other
      // kinds of files in node_modules.
      return;
    }

    const absDepToPath = files.pathJoin(nodeModulesToPath, depPath);

    // Try to symlink node_modules dependencies if possible (faster),
    // and fall back to a recursive copy otherwise.
    try {
      files.symlink(absDepFromPath, absDepToPath, "junction");
    } catch (e) {
      files.cp_r(absDepFromPath, absDepToPath);
    }
  });
}

function isPortable(dir) {
  const lstat = files.lstat(dir);
  if (! lstat.isDirectory()) {
    // Non-directory files are portable unless they end with .node.
    return ! dir.endsWith(".node");
  }

  const pkgJsonStat = files.statOrNull(files.pathJoin(dir, "package.json"));
  const canCache = pkgJsonStat && pkgJsonStat.isFile();
  const portableFile = files.pathJoin(dir, ".meteor-portable");

  if (canCache) {
    // Cache previous results by writing a boolean value to a hidden file
    // called .meteor-portable. Although it's tempting to write this file
    // once for the whole node_modules directory, it's important that we
    // put .meteor-portable files only in the individual top-level package
    // directories, so that they will get cleared away the next time those
    // packages are (re)installed.
    try {
      return JSON.parse(files.readFile(portableFile));
    } catch (e) {
      if (! (e instanceof SyntaxError ||
             e.code === "ENOENT")) {
        throw e;
      }
    }
  } else {
    // Clean up any .meteor-portable files we mistakenly wrote in
    // directories that do not contain package.json files. #7296
    fs.unlink(portableFile, error => {});
  }

  const result = files.readdir(dir).every(
    // Ignore files that start with a ".", such as .bin directories.
    itemName => itemName.startsWith(".") ||
      isPortable(files.pathJoin(dir, itemName)));

  if (canCache) {
    // Write the .meteor-portable file asynchronously, and don't worry
    // if it fails, e.g. because the file system is read-only (#6591).
    // Failing to write the file only means more work next time.
    fs.writeFile(
      portableFile,
      JSON.stringify(result) + "\n",
      error => {},
    );
  }

  return result;
}

// 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
// nodeModulesDir up to date.
meteorNpm.dependenciesArePortable = function (nodeModulesDir) {
  // We use a simple heuristic: we check to see if a package (or any
  // of its transitive dependencies) 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.

  assert.ok(
    files.pathBasename(nodeModulesDir).startsWith("node_modules"),
    "Bad node_modules directory: " + nodeModulesDir,
  );

  // Only check/write .meteor-portable files in each of the top-level
  // package directories.
  return isPortable(nodeModulesDir);
};

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 shrinkwrappedDependenciesTree =
    getShrinkwrappedDependenciesTree(packageNpmDir);

  const npmTree = { dependencies: {} };
  _.each(npmDependencies, (version, name) => {
    npmTree.dependencies[name] = { version };
  });

  const minInstalledTree =
    minimizeDependencyTree(installedDependenciesTree);
  const minShrinkwrapTree =
    minimizeDependencyTree(shrinkwrappedDependenciesTree);

  if (isSubtreeOf(npmTree, minInstalledTree) &&
      isSubtreeOf(minShrinkwrapTree, minInstalledTree)) {
    return;
  }

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

  let preservedShrinkwrap;

  if (_.isEmpty(npmDependencies)) {
    // If there are no npmDependencies, make sure nothing is installed.
    preservedShrinkwrap = { dependencies: {} };
  } else if (isSubtreeOf(npmTree, minShrinkwrapTree)) {
    // If the top-level npm dependencies are already encompassed by the
    // npm-shrinkwrap.json file, then reuse that file.
    preservedShrinkwrap = shrinkwrappedDependenciesTree;
  } else {
    // Otherwise install only the required npm packages and their
    // dependencies.
    preservedShrinkwrap = npmTree;
  }

  makeNewPackageNpmDir(newPackageNpmDir);

  if (!_.isEmpty(preservedShrinkwrap.dependencies)) {
    const newShrinkwrapFile = files.pathJoin(
      newPackageNpmDir,
      'npm-shrinkwrap.json'
    );

    // There are some unchanged packages here. Install from shrinkwrap.
    files.writeFile(
      newShrinkwrapFile,
      JSON.stringify(preservedShrinkwrap, null, 2)
    );

    // `npm install`
    installFromShrinkwrap(newPackageNpmDir);

    files.unlink(newShrinkwrapFile);
  }

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

function isSubtreeOf(subsetTree, supersetTree, predicate) {
  if (subsetTree === supersetTree) {
    return true;
  }

  if (_.isObject(subsetTree)) {
    return _.isObject(supersetTree) &&
      _.every(subsetTree, (value, key) => {
        return isSubtreeOf(value, supersetTree[key], predicate);
      });
  }

  if (_.isFunction(predicate)) {
    const result = predicate(subsetTree, supersetTree);
    if (typeof result === "boolean") {
      return result;
    }
  }

  return false;
}

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) {
  // Create a shrinkwrap file.
  shrinkwrap(newPackageNpmDir);

  // And stow a copy of npm-shrinkwrap too.
  files.copyFile(
    files.pathJoin(newPackageNpmDir, 'npm-shrinkwrap.json'),
    files.pathJoin(newPackageNpmDir, 'node_modules', '.npm-shrinkwrap.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';
};

const npmUserConfigFile = files.pathJoin(
  __dirname,
  "meteor-npm-userconfig"
);

var runNpmCommand = meteorNpm.runNpmCommand =
Profile("meteorNpm.runNpmCommand", function (args, cwd) {
  import { getEnv } from "../cli/dev-bundle-bin-helpers.js";

  const devBundleDir = files.getDevBundle();
  const isWindows = process.platform === "win32";
  const npmPath = files.convertToOSPath(files.pathJoin(
    devBundleDir, "bin",
    isWindows ? "npm.cmd" : "npm"
  ));

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

  return getEnv({
    devBundle: devBundleDir
  }).then(env => {
    const opts = {
      env: env,
      maxBuffer: 10 * 1024 * 1024
    };

    if (cwd) {
      opts.cwd = files.convertToOSPath(cwd);
    }

    // Make sure we don't honor any user-provided configuration files.
    env.npm_config_userconfig = npmUserConfigFile;

    return new Promise(function (resolve) {
      require('child_process').execFile(
        npmPath, args, opts, function (err, stdout, stderr) {
          if (meteorNpm._printNpmCalls) {
            process.stdout.write(err ? 'failed\n' : 'done\n');
          }

          resolve({
            success: ! err,
            error: (err ? `${err.message}${stderr}` : stderr),
            stdout: stdout,
            stderr: stderr
          });
        }
      );
    }).await();

  }).await();
});

// 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"
//         }
//       }
//     }
//   }
// }
function getInstalledDependenciesTree(dir) {
  function ls(nodeModulesDir) {
    let contents;
    try {
      contents = files.readdir(nodeModulesDir).sort();
    } finally {
      if (! contents) return;
    }

    const result = {};

    contents.forEach(item => {
      if (item.startsWith(".")) {
        return;
      }

      const pkgDir = files.pathJoin(nodeModulesDir, item);
      const pkgJsonPath = files.pathJoin(pkgDir, "package.json");

      let pkg;
      try {
        pkg = JSON.parse(files.readFile(pkgJsonPath));
      } finally {
        if (! pkg) return;
      }

      const info = result[item] = {
        version: pkg.version
      };

      const resolved = pkg._resolved || pkg.resolved;
      if (resolved) {
        info.resolved = resolved;
      }

      const from = pkg._from || pkg.from;
      if (from) {
        info.from = from;
      }

      const deps = ls(files.pathJoin(pkgDir, "node_modules"));
      if (deps && ! _.isEmpty(deps)) {
        info.dependencies = deps;
      }
    });

    return result;
  }

  return {
    dependencies: ls(files.pathJoin(dir, "node_modules"))
  };
}

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.isNpmUrl(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.isNpmUrl(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 {
      buildmessage.error(`couldn't install npm package ${name}@${version}: ${result.error}`);
    }

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

  const pkgDir = files.pathJoin(dir, "node_modules", name);
  if (! isPortable(pkgDir)) {
    recordLastRebuildVersions(pkgDir);
  }

  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();

  const tempPkgJsonPath = files.pathJoin(dir, "package.json");
  const pkgJsonExisted = files.exists(tempPkgJsonPath);
  if (! pkgJsonExisted) {
    // Writing an empty package.json file prevents ENOENT warnings about
    // package.json not existing, which are noisy at best and sometimes
    // seem to interfere with the install.
    files.writeFile(tempPkgJsonPath, "{}\n", "utf8");
  }

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

  if (! pkgJsonExisted) {
    files.rm_recursive(tempPkgJsonPath);
  }

  if (! result.success) {
    buildmessage.error(`couldn't install npm packages from npm-shrinkwrap: ${result.error}`);
    // Recover by returning false from updateDependencies
    throw new NpmFailure;
  }

  const nodeModulesDir = files.pathJoin(dir, "node_modules");
  files.readdir(nodeModulesDir).forEach(function (name) {
    const pkgDir = files.pathJoin(nodeModulesDir, name);
    if (! isPortable(pkgDir, true)) {
      recordLastRebuildVersions(pkgDir);
    }
  });
};

// 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(process.env.NPM_CONFIG_REGISTRY || "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`
function shrinkwrap(dir) {
  const tree = getInstalledDependenciesTree(dir);

  files.writeFile(
    files.pathJoin(dir, "npm-shrinkwrap.json"),
    JSON.stringify(tree, null, 2) + "\n"
  );

  return tree;
}

// 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.isNpmUrl(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(', ') + '...');
};


Package.describe({
  summary: "Opt out of sending package stats",
  version: '1.0.7'
});

Package.onUse(function (api) {
  // Empty. This package's presence tells the meteor tool to stop
  // sending package stats.
});


// XXX from Underscore.String (http://epeli.github.com/underscore.string/)
var startsWith = function(str, starts) {
  return str.length >= starts.length &&
    str.substring(0, starts.length) === starts;
};
var endsWith = function(str, ends) {
  return str.length >= ends.length &&
    str.substring(str.length - ends.length) === ends;
};

// @param url {String} URL to Meteor app, eg:
//   "/" or "madewith.meteor.com" or "https://foo.meteor.com"
//   or "ddp+sockjs://ddp--****-foo.meteor.com/sockjs"
// @returns {String} URL to the endpoint with the specific scheme and subPath, e.g.
// for scheme "http" and subPath "sockjs"
//   "http://subdomain.meteor.com/sockjs" or "/sockjs"
//   or "https://ddp--1234-foo.meteor.com/sockjs"
var translateUrl =  function(url, newSchemeBase, subPath) {
  if (! newSchemeBase) {
    newSchemeBase = "http";
  }

  var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//);
  var httpUrlMatch = url.match(/^http(s?):\/\//);
  var newScheme;
  if (ddpUrlMatch) {
    // Remove scheme and split off the host.
    var urlAfterDDP = url.substr(ddpUrlMatch[0].length);
    newScheme = ddpUrlMatch[1] === "i" ? newSchemeBase : newSchemeBase + "s";
    var slashPos = urlAfterDDP.indexOf('/');
    var host =
          slashPos === -1 ? urlAfterDDP : urlAfterDDP.substr(0, slashPos);
    var rest = slashPos === -1 ? '' : urlAfterDDP.substr(slashPos);

    // In the host (ONLY!), change '*' characters into random digits. This
    // allows different stream connections to connect to different hostnames
    // and avoid browser per-hostname connection limits.
    host = host.replace(/\*/g, function () {
      return Math.floor(Random.fraction()*10);
    });

    return newScheme + '://' + host + rest;
  } else if (httpUrlMatch) {
    newScheme = !httpUrlMatch[1] ? newSchemeBase : newSchemeBase + "s";
    var urlAfterHttp = url.substr(httpUrlMatch[0].length);
    url = newScheme + "://" + urlAfterHttp;
  }

  // Prefix FQDNs but not relative URLs
  if (url.indexOf("://") === -1 && !startsWith(url, "/")) {
    url = newSchemeBase + "://" + url;
  }

  // XXX This is not what we should be doing: if I have a site
  // deployed at "/foo", then DDP.connect("/") should actually connect
  // to "/", not to "/foo". "/" is an absolute path. (Contrast: if
  // deployed at "/foo", it would be reasonable for DDP.connect("bar")
  // to connect to "/foo/bar").
  //
  // We should make this properly honor absolute paths rather than
  // forcing the path to be relative to the site root. Simultaneously,
  // we should set DDP_DEFAULT_CONNECTION_URL to include the site
  // root. See also client_convenience.js #RationalizingRelativeDDPURLs
  url = Meteor._relativeToSiteRootUrl(url);

  if (endsWith(url, "/"))
    return url + subPath;
  else
    return url + "/" + subPath;
};

toSockjsUrl = function (url) {
  return translateUrl(url, "http", "sockjs");
};

toWebsocketUrl = function (url) {
  var ret = translateUrl(url, "ws", "websocket");
  return ret;
};

LivedataTest.toSockjsUrl = toSockjsUrl;

_.extend(LivedataTest.ClientStream.prototype, {

  // Register for callbacks.
  on: function (name, callback) {
    var self = this;

    if (name !== 'message' && name !== 'reset' && name !== 'disconnect')
      throw new Error("unknown event type: " + name);

    if (!self.eventCallbacks[name])
      self.eventCallbacks[name] = [];
    self.eventCallbacks[name].push(callback);
  },

  _initCommon: function (options) {
    var self = this;
    options = options || {};

    //// Constants

    // how long to wait until we declare the connection attempt
    // failed.
    self.CONNECT_TIMEOUT = options.connectTimeoutMs || 10000;

    self.eventCallbacks = {}; // name -> [callback]

    self._forcedToDisconnect = false;

    //// Reactive status
    self.currentStatus = {
      status: "connecting",
      connected: false,
      retryCount: 0
    };

    self.statusListeners = typeof Tracker !== 'undefined' && new Tracker.Dependency;
    self.statusChanged = function () {
      if (self.statusListeners)
        self.statusListeners.changed();
    };

    //// Retry logic
    self._retry = new Retry;
    self.connectionTimer = null;

  },

  // Trigger a reconnect.
  reconnect: function (options) {
    var self = this;
    options = options || {};

    if (options.url) {
      self._changeUrl(options.url);
    }

    if (options._sockjsOptions) {
      self.options._sockjsOptions = options._sockjsOptions;
    }

    if (self.currentStatus.connected) {
      if (options._force || options.url) {
        // force reconnect.
        self._lostConnection(new DDP.ForcedReconnectError);
      } // else, noop.
      return;
    }

    // if we're mid-connection, stop it.
    if (self.currentStatus.status === "connecting") {
      // Pretend it's a clean close.
      self._lostConnection();
    }

    self._retry.clear();
    self.currentStatus.retryCount -= 1; // don't count manual retries
    self._retryNow();
  },

  disconnect: function (options) {
    var self = this;
    options = options || {};

    // Failed is permanent. If we're failed, don't let people go back
    // online by calling 'disconnect' then 'reconnect'.
    if (self._forcedToDisconnect)
      return;

    // If _permanent is set, permanently disconnect a stream. Once a stream
    // is forced to disconnect, it can never reconnect. This is for
    // error cases such as ddp version mismatch, where trying again
    // won't fix the problem.
    if (options._permanent) {
      self._forcedToDisconnect = true;
    }

    self._cleanup();
    self._retry.clear();

    self.currentStatus = {
      status: (options._permanent ? "failed" : "offline"),
      connected: false,
      retryCount: 0
    };

    if (options._permanent && options._error)
      self.currentStatus.reason = options._error;

    self.statusChanged();
  },

  // maybeError is set unless it's a clean protocol-level close.
  _lostConnection: function (maybeError) {
    var self = this;

    self._cleanup(maybeError);
    self._retryLater(maybeError); // sets status. no need to do it here.
  },

  // fired when we detect that we've gone online. try to reconnect
  // immediately.
  _online: function () {
    // if we've requested to be offline by disconnecting, don't reconnect.
    if (this.currentStatus.status != "offline")
      this.reconnect();
  },

  _retryLater: function (maybeError) {
    var self = this;

    var timeout = 0;
    if (self.options.retry ||
        (maybeError && maybeError.errorType === "DDP.ForcedReconnectError")) {
      timeout = self._retry.retryLater(
        self.currentStatus.retryCount,
        _.bind(self._retryNow, self)
      );
      self.currentStatus.status = "waiting";
      self.currentStatus.retryTime = (new Date()).getTime() + timeout;
    } else {
      self.currentStatus.status = "failed";
      delete self.currentStatus.retryTime;
    }

    self.currentStatus.connected = false;
    self.statusChanged();
  },

  _retryNow: function () {
    var self = this;

    if (self._forcedToDisconnect)
      return;

    self.currentStatus.retryCount += 1;
    self.currentStatus.status = "connecting";
    self.currentStatus.connected = false;
    delete self.currentStatus.retryTime;
    self.statusChanged();

    self._launchConnection();
  },

  // Get current status. Reactive.
  status: function () {
    var self = this;
    if (self.statusListeners)
      self.statusListeners.depend();
    return self.currentStatus;
  }
});

DDP.ConnectionError = Meteor.makeErrorType(
  "DDP.ConnectionError", function (message) {
    var self = this;
    self.message = message;
});

DDP.ForcedReconnectError = Meteor.makeErrorType(
  "DDP.ForcedReconnectError", function () {});


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

var installArg = utils.isNpmUrl(version)

keep keep keep replace keep keep keep keep

# These configuration options govern `npm` commands run on behalf of
# Meteor packages that have `Npm.depends` calls in their package.js files,
# not `meteor npm ...` commands. Because the developer is not running
# these commands herself, we don't care as much about helpful warnings,
# and we certainly don't want them to obscure actual errors.

loglevel = error
progress = false


// Run a function named `run` which modifies a sequence. While it
// executes, observe changes to the sequence and accumulate them in an
// array, canonicalizing as necessary. Then make sure the results are
// the same as passed in `expectedCallbacks`. In items in
// `expectedCallbacks`, allow for special values of the form {NOT:
// "foo"}, which match anything other than "foo".
//
// @param test {Object} as passed to Tinytest.add
// @param sequenceFunc {Function(): sequence type}
// @param run {Function()} modify the sequence or cause sequenceFunc
//     to be recomupted
// @param expectedCallbacks {Array}
//     elements are objects eg {addedAt: [array of arguments]}
// @param numExpectedWarnings {Number}
runOneObserveSequenceTestCase = function (test, sequenceFunc,
                                          run, expectedCallbacks,
                                          numExpectedWarnings) {
  if (numExpectedWarnings)
    ObserveSequence._suppressWarnings += numExpectedWarnings;

  var firedCallbacks = [];
  var handle = ObserveSequence.observe(sequenceFunc, {
    addedAt: function () {
      firedCallbacks.push({addedAt: _.toArray(arguments)});
    },
    changedAt: function () {
      var obj = {changedAt: _.toArray(arguments)};

      // Browsers are inconsistent about the order in which 'changedAt'
      // callbacks fire. To ensure consistent behavior of these tests,
      // we can't simply push `obj` at the end of `firedCallbacks` as
      // we do for the other callbacks. Instead, we use insertion sort
      // to place `obj` in a canonical position within the chunk of
      // contiguously recently fired 'changedAt' callbacks.
      for (var i = firedCallbacks.length; i > 0; i--) {

        var compareTo = firedCallbacks[i - 1];
        if (!compareTo.changedAt)
          break;

        if (EJSON.stringify(compareTo, {canonical: true}) <
            EJSON.stringify(obj, {canonical: true}))
          break;
      }

      firedCallbacks.splice(i, 0, obj);
    },
    removedAt: function () {
      firedCallbacks.push({removedAt: _.toArray(arguments)});
    },
    movedTo: function () {
      firedCallbacks.push({movedTo: _.toArray(arguments)});
    }
  });

  run();
  Tracker.flush();
  handle.stop();

  test.equal(ObserveSequence._suppressWarnings, 0);
  test.equal(ObserveSequence._loggedWarnings, 0);
  ObserveSequence._loggedWarnings = 0;

  // any expected argument this is `{NOT: "foo"}`, should match any
  // corresponding value in the fired callbacks other than "foo". so,
  // assert non-equality and then replace the appropriate entries in
  // the 'firedCallbacks' array with `{NOT: "foo"}` before calling
  // `test.equal` below.
  var commonLength = Math.min(firedCallbacks.length, expectedCallbacks.length);
  for (var i = 0; i < commonLength; i++) {
    var callback = expectedCallbacks[i];
    if (_.keys(callback).length !== 1)
      throw new Error("Callbacks should be objects with one key, eg `addedAt`");
    var callbackName = _.keys(callback)[0];
    var args = _.values(callback)[0];
    _.each(args, function (arg, argIndex) {
      if (arg && typeof arg === 'object' &&
          'NOT' in arg &&
          firedCallbacks[i][callbackName]) {
        test.notEqual(firedCallbacks[i][callbackName][argIndex],
                      arg.NOT, "Should be NOT " + arg.NOT);
        firedCallbacks[i][callbackName][argIndex] = arg;
      }
    });
  }

  var compress = function (str) {
    return str.replace(/\[\n\s*/gm, "[").replace(/\{\n\s*/gm, "{").
      replace(/\n\s*\]/gm, "]").replace(/\n\s*\}/gm, "}");
  };

  test.equal(compress(EJSON.stringify(firedCallbacks, {canonical: true, indent: true})),
             compress(EJSON.stringify(expectedCallbacks, {canonical: true, indent: true})));
};

Tinytest.add('observe-sequence - initial data for all sequence types', function (test) {
  runOneObserveSequenceTestCase(test, function () {
    return null;
  }, function () {}, []);

  runOneObserveSequenceTestCase(test, function () {
    return [];
  }, function () {}, []);

  runOneObserveSequenceTestCase(test, function () {
    return [{foo: 1}, {bar: 2}];
  }, function () {}, [
    {addedAt: [0, {foo: 1}, 0, null]},
    {addedAt: [1, {bar: 2}, 1, null]}
  ]);

  runOneObserveSequenceTestCase(test, function () {
    return [{_id: "13", foo: 1}, {_id: "37", bar: 2}];
  }, function () {}, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]}
  ]);

  runOneObserveSequenceTestCase(test, function () {
    var coll = new Mongo.Collection(null);
    coll.insert({_id: "13", foo: 1});
    coll.insert({_id: "37", bar: 2});
    var cursor = coll.find({}, {sort: {_id: 1}});
    return cursor;
  }, function () {}, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]}
  ]);

  // shouldn't break on array with duplicate _id's, and the ids sent
  // in the callbacks should be distinct
  runOneObserveSequenceTestCase(test, function () {
    return [
      {_id: "13", foo: 1},
      {_id: "13", foo: 2}
    ];
  }, function () {}, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: [{NOT: "13"}, {_id: "13", foo: 2}, 1, null]}
  ], /*numExpectedWarnings = */1);
});

Tinytest.add('observe-sequence - array to other array', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "13", foo: 1}, {_id: "38", bar: 2}];
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["37", {_id: "37", bar: 2}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2}, 1, null]},
    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, strings', function (test) {
  var dep = new Tracker.Dependency;
  var seq = ["A", "B"];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = ["B", "C"];
    dep.changed();
  }, [
    {addedAt: ["-A", "A", 0, null]},
    {addedAt: ["-B", "B", 1, null]},
    {removedAt: ["-A", "A", 0]},
    {addedAt: ["-C", "C", 1, null]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, objects without ids', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{foo: 1}, {bar: 2}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{foo: 2}];
    dep.changed();
  }, [
    {addedAt: [0, {foo: 1}, 0, null]},
    {addedAt: [1, {bar: 2}, 1, null]},
    {removedAt: [1, {bar: 2}, 1]},
    {changedAt: [0, {foo: 2}, {foo: 1}, 0]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, changes', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}, {_id: "42", baz: 42}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "13", foo: 1}, {_id: "38", bar: 2}, {_id: "42", baz: 43}];
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {addedAt: ["42", {_id: "42", baz: 42}, 2, null]},
    {removedAt: ["37", {_id: "37", bar: 2}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2}, 1, "42"]},
    // change fires for all elements, because we don't diff the actual
    // objects.
    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]},
    {changedAt: ["42", {_id: "42", baz: 43}, {_id: "42", baz: 42}, 2]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, movedTo', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}, {_id: "42", baz: 42}, {_id: "43", baz: 43}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "43", baz: 43}, {_id: "37", bar: 2}, {_id: "42", baz: 42}, {_id: "13", foo: 1}];
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {addedAt: ["42", {_id: "42", baz: 42}, 2, null]},
    {addedAt: ["43", {_id: "43", baz: 43}, 3, null]},

    {movedTo: ["43", {_id: "43", baz: 43}, 3, 1, "37"]},
    {movedTo: ["13", {_id: "13", foo: 1}, 0, 3, null]},

    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 3]},
    {changedAt: ["37", {_id: "37", bar: 2}, {_id: "37", bar: 2}, 1]},
    {changedAt: ["42", {_id: "42", baz: 42}, {_id: "42", baz: 42}, 2]},
    {changedAt: ["43", {_id: "43", baz: 43}, {_id: "43", baz: 43}, 0]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, movedTo the end', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "0"}, {_id: "2"}, {_id: "3"}, {_id: "1"}];
    dep.changed();
  }, [
    {addedAt: ["0", {_id: "0"}, 0, null]},
    {addedAt: ["1", {_id: "1"}, 1, null]},
    {addedAt: ["2", {_id: "2"}, 2, null]},
    {addedAt: ["3", {_id: "3"}, 3, null]},

    {movedTo: ["1", {_id: "1"}, 1, 3, null]},
    {changedAt: ["0", {_id: "0"}, {_id: "0"}, 0]},
    {changedAt: ["1", {_id: "1"}, {_id: "1"}, 3]},
    {changedAt: ["2", {_id: "2"}, {_id: "2"}, 1]},
    {changedAt: ["3", {_id: "3"}, {_id: "3"}, 2]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, movedTo later position but not the latest #2845', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "1"}, {_id: "2"}, {_id: "0"}, {_id: "3"}];
    dep.changed();
  }, [
    {addedAt: ["0", {_id: "0"}, 0, null]},
    {addedAt: ["1", {_id: "1"}, 1, null]},
    {addedAt: ["2", {_id: "2"}, 2, null]},
    {addedAt: ["3", {_id: "3"}, 3, null]},

    {movedTo: ["0", {_id: "0"}, 0, 2, "3"]},

    {changedAt: ["0", {_id: "0"}, {_id: "0"}, 2]},
    {changedAt: ["1", {_id: "1"}, {_id: "1"}, 0]},
    {changedAt: ["2", {_id: "2"}, {_id: "2"}, 1]},
    {changedAt: ["3", {_id: "3"}, {_id: "3"}, 3]}
  ]);
});

Tinytest.add('observe-sequence - array to other array, movedTo earlier position but not the first', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}, {_id: "4"}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [{_id: "0"}, {_id: "4"}, {_id: "1"}, {_id: "2"}, {_id: "3"}];
    dep.changed();
  }, [
    {addedAt: ["0", {_id: "0"}, 0, null]},
    {addedAt: ["1", {_id: "1"}, 1, null]},
    {addedAt: ["2", {_id: "2"}, 2, null]},
    {addedAt: ["3", {_id: "3"}, 3, null]},
    {addedAt: ["4", {_id: "4"}, 4, null]},

    {movedTo: ["4", {_id: "4"}, 4, 1, "1"]},

    {changedAt: ["0", {_id: "0"}, {_id: "0"}, 0]},
    {changedAt: ["1", {_id: "1"}, {_id: "1"}, 2]},
    {changedAt: ["2", {_id: "2"}, {_id: "2"}, 3]},
    {changedAt: ["3", {_id: "3"}, {_id: "3"}, 4]},
    {changedAt: ["4", {_id: "4"}, {_id: "4"}, 1]}
  ]);
});

Tinytest.add('observe-sequence - array to null', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = null;
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["13", {_id: "13", foo: 1}, 0]},
    {removedAt: ["37", {_id: "37", bar: 2}, 0]}
  ]);
});

Tinytest.add('observe-sequence - array to cursor', function (test) {
  var dep = new Tracker.Dependency;
  var seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}];

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    var coll = new Mongo.Collection(null);
    coll.insert({_id: "13", foo: 1});
    coll.insert({_id: "38", bar: 2});
    var cursor = coll.find({}, {sort: {_id: 1}});
    seq = cursor;
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["37", {_id: "37", bar: 2}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2}, 1, null]},
    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]}
  ]);
});

Tinytest.add('observe-sequence - cursor to null', function (test) {
  var dep = new Tracker.Dependency;
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", foo: 1});
  coll.insert({_id: "37", bar: 2});
  var cursor = coll.find({}, {sort: {_id: 1}});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = null;
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["13", {_id: "13", foo: 1}, 0]},
    {removedAt: ["37", {_id: "37", bar: 2}, 0]}
  ]);
});

Tinytest.add('observe-sequence - cursor to array', function (test) {
  var dep = new Tracker.Dependency;
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", foo: 1});
  var cursor = coll.find({}, {sort: {_id: 1}});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    coll.insert({_id: "37", bar: 2});
    seq = [{_id: "13", foo: 1}, {_id: "38", bar: 2}];
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["37", {_id: "37", bar: 2}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2}, 1, null]},
    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]}
  ]);
});

Tinytest.add('observe-sequence - cursor', function (test) {
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", rank: 1});
  var cursor = coll.find({}, {sort: {rank: 1}});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    return seq;
  }, function () {
    coll.insert({_id: "37", rank: 2});
    coll.insert({_id: "77", rank: 3});
    coll.remove({_id: "37"});                           // should fire a 'removedAt' callback
    coll.insert({_id: "11", rank: 0});                  // should fire an 'addedAt' callback
    coll.update({_id: "13"}, {$set: {updated: true}});  // should fire an 'changedAt' callback
    coll.update({_id: "77"}, {$set: {rank: -1}});       // should fire 'changedAt' and 'movedTo' callback
  }, [
    // this case must not fire spurious calls as the array to array
    // case does. otherwise, the entire power of cursors is lost in
    // blaze.
    {addedAt: ["13", {_id: "13", rank: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", rank: 2}, 1, null]},
    {addedAt: ["77", {_id: "77", rank: 3}, 2, null]},
    {removedAt: ["37", {_id: "37", rank: 2}, 1]},
    {addedAt: ["11", {_id: "11", rank: 0}, 0, "13"]},
    {changedAt: ["13", {_id: "13", rank: 1, updated: true}, {_id: "13", rank: 1}, 1]},
    {changedAt: ["77", {_id: "77", rank: -1}, {_id: "77", rank: 3}, 2]},
    {movedTo: ["77", {_id: "77", rank: -1}, 2, 0, "11"]}
  ]);
});

Tinytest.add('observe-sequence - cursor to other cursor', function (test) {
  var dep = new Tracker.Dependency;
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", foo: 1});
  var cursor = coll.find({}, {sort: {_id: 1}});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    coll.insert({_id: "37", bar: 2});

    var newColl = new Mongo.Collection(null);
    newColl.insert({_id: "13", foo: 1});
    newColl.insert({_id: "38", bar: 2});
    var newCursor = newColl.find({}, {sort: {_id: 1}});
    seq = newCursor;
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2}, 1, null]},
    {removedAt: ["37", {_id: "37", bar: 2}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2}, 1, null]},
    {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]}
  ]);
});

Tinytest.add('observe-sequence - cursor to other cursor with transform', function (test) {
  var dep = new Tracker.Dependency;
  var transform = function(doc) {
    return _.extend({idCopy: doc._id}, doc);
  };

  var coll = new Mongo.Collection(null, {transform: transform});
  coll.insert({_id: "13", foo: 1});
  var cursor = coll.find({}, {sort: {_id: 1}});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    coll.insert({_id: "37", bar: 2});

    var newColl = new Mongo.Collection(null, {transform: transform});
    newColl.insert({_id: "13", foo: 1});
    newColl.insert({_id: "38", bar: 2});
    var newCursor = newColl.find({}, {sort: {_id: 1}});
    seq = newCursor;
    dep.changed();
  }, [
    {addedAt: ["13", {_id: "13", foo: 1, idCopy: "13"}, 0, null]},
    {addedAt: ["37", {_id: "37", bar: 2, idCopy: "37"}, 1, null]},
    {removedAt: ["37", {_id: "37", bar: 2, idCopy: "37"}, 1]},
    {addedAt: ["38", {_id: "38", bar: 2, idCopy: "38"}, 1, null]},
    {changedAt: ["13", {_id: "13", foo: 1, idCopy: "13"}, {_id: "13", foo: 1, idCopy: "13"}, 0]}
  ]);
});

Tinytest.add('observe-sequence - cursor to same cursor', function (test) {
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", rank: 1});
  var cursor = coll.find({}, {sort: {rank: 1}});
  var seq = cursor;
  var dep = new Tracker.Dependency;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    coll.insert({_id: "24", rank: 2});
    dep.changed();
    Tracker.flush();
    coll.insert({_id: "78", rank: 3});
  }, [
    {addedAt: ["13", {_id: "13", rank: 1}, 0, null]},
    {addedAt: ["24", {_id: "24", rank: 2}, 1, null]},
    // even if the cursor changes to the same cursor, we do a diff,
    // which leads to these 'changedAt' events.
    {changedAt: ["13", {_id: "13", rank: 1}, {_id: "13", rank: 1}, 0]},
    {changedAt: ["24", {_id: "24", rank: 2}, {_id: "24", rank: 2}, 1]},
    {addedAt: ["78", {_id: "78", rank: 3}, 2, null]}
  ]);
});

Tinytest.add('observe-sequence - string arrays', function (test) {
  var seq = ['A', 'B'];
  var dep = new Tracker.Dependency;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = ['B', 'C'];
    dep.changed();
  }, [
    {addedAt: ['-A', 'A', 0, null]},
    {addedAt: ['-B', 'B', 1, null]},
    {removedAt: ['-A', 'A', 0]},
    {addedAt: ['-C', 'C', 1, null]}
  ]);
});

Tinytest.add('observe-sequence - number arrays', function (test) {
  var seq = [1, 1, 2];
  var dep = new Tracker.Dependency;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    seq = [1, 3, 2, 3];
    dep.changed();
  }, [
    {addedAt: [1, 1, 0, null]},
    {addedAt: [{NOT: 1}, 1, 1, null]},
    {addedAt: [2, 2, 2, null]},
    {removedAt: [{NOT: 1}, 1, 1]},
    {addedAt: [3, 3, 1, 2]},
    {addedAt: [{NOT: 3}, 3, 3, null]}
  ]);
});

Tinytest.add('observe-sequence - number arrays, _id:0 correctly handled, no duplicate ids warning #4049', function (test) {
  var seq = _.map(_.range(3), function (i) { return { _id: i}; });
  var dep = new Tracker.Dependency;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    // There was a bug before fixing #4049 that 0 wouldn't be handled well as an
    // _id. An expression like `(item && item._id) || index` would incorrectly
    // return '2' for the last item because the _id is falsy (although it is not
    // undefined, but 0!).
    seq = _.map([1, 2, 0], function (i) { return { _id: i}; });
    dep.changed();
  }, [
    {addedAt: [0, {_id: 0}, 0, null]},
    {addedAt: [1, {_id: 1}, 1, null]},
    {addedAt: [2, {_id: 2}, 2, null]},
    {movedTo: [0, {_id: 0}, 0, 2, null]},
    {changedAt: [0, {_id: 0}, {_id: 0}, 2]},
    {changedAt: [1, {_id: 1}, {_id: 1}, 0]},
    {changedAt: [2, {_id: 2}, {_id: 2}, 1]}
  ]);
});

Tinytest.add('observe-sequence - cursor to other cursor, same collection', function (test) {
  var dep = new Tracker.Dependency;
  var coll = new Mongo.Collection(null);
  coll.insert({_id: "13", foo: 1});
  coll.insert({_id: "37", foo: 2});
  var cursor = coll.find({foo: 1});
  var seq = cursor;

  runOneObserveSequenceTestCase(test, function () {
    dep.depend();
    return seq;
  }, function () {
    var newCursor = coll.find({foo: 2});
    seq = newCursor;
    dep.changed();
    Tracker.flush();
    coll.insert({_id: "38", foo: 1});
    coll.insert({_id: "39", foo: 2});
  }, [
    {addedAt: ["13", {_id: "13", foo: 1}, 0, null]},
    {removedAt: ["13", {_id: "13", foo: 1}, 0]},
    {addedAt: ["37", {_id: "37", foo: 2}, 0, null]},
    {addedAt: ["39", {_id: "39", foo: 2}, 1, null]}
  ]);
});


// Subscribe to the `meteor_autoupdate_clientVersions` collection,
// which contains the set of acceptable client versions.
//
// A "hard code push" occurs when the running client version is not in
// the set of acceptable client versions (or the server updates the
// collection, there is a published client version marked `current` and
// the running client version is no longer in the set).
//
// When the `reload` package is loaded, a hard code push causes
// the browser to reload, so that it will load the latest client
// version from the server.
//
// A "soft code push" represents the situation when the running client
// version is in the set of acceptable versions, but there is a newer
// version available on the server.
//
// `Autoupdate.newClientAvailable` is a reactive data source which
// becomes `true` if there is a new version of the client is available on
// the server.
//
// This package doesn't implement a soft code reload process itself,
// but `newClientAvailable` could be used for example to display a
// "click to reload" link to the user.

// The client version of the client code currently running in the
// browser.
var autoupdateVersion = __meteor_runtime_config__.autoupdateVersion || "unknown";
var autoupdateVersionRefreshable =
  __meteor_runtime_config__.autoupdateVersionRefreshable || "unknown";

// The collection of acceptable client versions.
ClientVersions = new Mongo.Collection("meteor_autoupdate_clientVersions");

Autoupdate = {};

Autoupdate.newClientAvailable = function () {
  return !! ClientVersions.findOne({
               _id: "version",
               version: {$ne: autoupdateVersion} }) ||
         !! ClientVersions.findOne({
               _id: "version-refreshable",
               version: {$ne: autoupdateVersionRefreshable} });
};
Autoupdate._ClientVersions = ClientVersions;  // Used by a self-test

var knownToSupportCssOnLoad = false;

var retry = new Retry({
  // Unlike the stream reconnect use of Retry, which we want to be instant
  // in normal operation, this is a wacky failure. We don't want to retry
  // right away, we can start slowly.
  //
  // A better way than timeconstants here might be to use the knowledge
  // of when we reconnect to help trigger these retries. Typically, the
  // server fixing code will result in a restart and reconnect, but
  // potentially the subscription could have a transient error.
  minCount: 0, // don't do any immediate retries
  baseTimeout: 30*1000 // start with 30s
});
var failures = 0;

Autoupdate._retrySubscription = function () {
  Meteor.subscribe("meteor_autoupdate_clientVersions", {
    onError: function (error) {
      Meteor._debug("autoupdate subscription failed:", error);
      failures++;
      retry.retryLater(failures, function () {
        // Just retry making the subscription, don't reload the whole
        // page. While reloading would catch more cases (for example,
        // the server went back a version and is now doing old-style hot
        // code push), it would also be more prone to reload loops,
        // which look really bad to the user. Just retrying the
        // subscription over DDP means it is at least possible to fix by
        // updating the server.
        Autoupdate._retrySubscription();
      });
    },
    onReady: function () {
      if (Package.reload) {
        var checkNewVersionDocument = function (doc) {
          var self = this;
          if (doc._id === 'version-refreshable' &&
              doc.version !== autoupdateVersionRefreshable) {
            autoupdateVersionRefreshable = doc.version;
            // Switch out old css links for the new css links. Inspired by:
            // https://github.com/guard/guard-livereload/blob/master/js/livereload.js#L710
            var newCss = (doc.assets && doc.assets.allCss) || [];
            var oldLinks = [];
            _.each(document.getElementsByTagName('link'), function (link) {
              if (link.className === '__meteor-css__') {
                oldLinks.push(link);
              }
            });

            var waitUntilCssLoads = function  (link, callback) {
              var executeCallback = _.once(callback);
              link.onload = function () {
                knownToSupportCssOnLoad = true;
                executeCallback();
              };
              if (! knownToSupportCssOnLoad) {
                var id = Meteor.setInterval(function () {
                  if (link.sheet) {
                    executeCallback();
                    Meteor.clearInterval(id);
                  }
                }, 50);
              }
            };

            var removeOldLinks = _.after(newCss.length, function () {
              _.each(oldLinks, function (oldLink) {
                oldLink.parentNode.removeChild(oldLink);
              });
            });

            var attachStylesheetLink = function (newLink) {
              document.getElementsByTagName("head").item(0).appendChild(newLink);

              waitUntilCssLoads(newLink, function () {
                Meteor.setTimeout(removeOldLinks, 200);
              });
            };

            if (newCss.length !== 0) {
              _.each(newCss, function (css) {
                var newLink = document.createElement("link");
                newLink.setAttribute("rel", "stylesheet");
                newLink.setAttribute("type", "text/css");
                newLink.setAttribute("class", "__meteor-css__");
                newLink.setAttribute("href", css.url);
                attachStylesheetLink(newLink);
              });
            } else {
              removeOldLinks();
            }

          }
          else if (doc._id === 'version' && doc.version !== autoupdateVersion) {
            handle && handle.stop();

            if (Package.reload) {
              Package.reload.Reload._reload();
            }
          }
        };

        var handle = ClientVersions.find().observe({
          added: checkNewVersionDocument,
          changed: checkNewVersionDocument
        });
      }
    }
  });
};
Autoupdate._retrySubscription();


if (! files.exists(files.pathJoin(dir, "npm-shrinkwrap.json"))) {

"Can't call `npm install` without a npm-shrinkwrap.json file present");

const tempPkgJsonPath = files.pathJoin(dir, "package.json");

const pkgJsonExisted = files.exists(tempPkgJsonPath);

// Writing an empty package.json file prevents ENOENT warnings about

// package.json not existing, which are noisy at best and sometimes

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


Tinytest.add("ecmascript - runtime - template literals", (test) => {
  function dump(pieces) {
    var copy = {};
    // Can't use _.extend({}, pieces) because es5-shim adds enumerable
    // methods to Array.prototype, and _.extend has no own property check.
    _.each(_.keys(pieces), key => copy[key] = pieces[key]);
    return [copy, _.toArray(arguments).slice(1)];
  };
  const foo = 'B';
  // uses `babelHelpers.taggedTemplateLiteralLoose`
  test.equal(`\u0041${foo}C`, 'ABC');
  test.equal(dump`\u0041${foo}C`,
             [{0:'A', 1: 'C', raw: ['\\u0041', 'C']},
              ['B']]);
});

Tinytest.add("ecmascript - runtime - classes - basic", (test) => {
  {
    class Foo {
      constructor(x) {
        this.x = x;
      }
    }

    test.throws(() => {
      Foo(); // called without `new`
    });

    test.equal((new Foo(3)).x, 3);
  }

  {
    class Bar {
      constructor(x) {
        this.x = x;
      }
    }
    class Foo extends Bar {}

    test.throws(() => {
      Foo(); // called without `new`
    });

    test.equal((new Foo(3)).x, 3);
    test.isTrue((new Foo(3)) instanceof Foo);
    test.isTrue((new Foo(3)) instanceof Bar);
  }

  {
    class Foo {
      static staticMethod() {
        return 'classy';
      }

      prototypeMethod() {
        return 'prototypical';
      }
    }

    test.equal(Foo.staticMethod(), 'classy');
    test.equal((new Foo).prototypeMethod(), 'prototypical');
  }
});

Tinytest.add("ecmascript - runtime - classes - use before declare", (test) => {
  const x = function asdf() {};
  if (typeof asdf === 'function') {
    // We seem to be in IE 8, where function names leak into the enclosing
    // scope, contrary to the spec.  In this case, Babel does not (currently)
    // throw an error if you use a class before you declare it.  (Of course,
    // any other browser can tell the developer they screwed up!)
    test.expect_fail();
  }

  test.throws(() => {
    new Foo(); // use before definition
    class Foo {}
  });
});

Tinytest.add("ecmascript - runtime - classes - inheritance", (test) => {

  // uses `babelHelpers.inherits`
  {
    class Foo {
      static static1() {
        return 1;
      }
    }
    Foo.static2 = function () {
      return 2;
    };

    // static methods are inherited!
    class Bar extends Foo {}

    test.equal(Foo.static1(), 1);
    test.equal(Foo.static2(), 2);
    test.equal(Bar.static1(), 1);
    test.equal(Bar.static2(), 2);
  }

  {
    const buf = [];
    class Foo {
      constructor() {
        buf.push('hi');
      }
    }

    class Bar extends Foo {}

    new Bar();
    // derived class with no constructor gets a default constructor
    // that calls the super constructor
    test.equal(buf, ['hi']);
  }
});

Tinytest.add("ecmascript - runtime - classes - computed props", (test) => {
  {
    const frob = "inc";

    class Foo {
      static [frob](n) { return n+1; }
    }

    test.equal(Foo.inc(3), 4);
  }
});

if (Meteor.isServer) {
  // getters and setters don't work in all clients, but they should work
  // in classes on browsers that support them in the first place, and on
  // the server.  (Technically they just need a working
  // Object.defineProperty, found in IE9+ and all modern environments.)
  Tinytest.add("ecmascript - runtime - classes - getters/setters", (test) => {
    // uses `babelHelpers.createClass`
    class Foo {
      get two() { return 1+1; }
      static get three() { return 1+1+1; }
    }

    test.equal((new Foo).two, 2);
    test.equal(Foo.three, 3);
  });
}

Tinytest.add("ecmascript - runtime - block scope", (test) => {
  {
    const buf = [];
    const thunks = [];
    function print(x) {
      buf.push(x);
    };
    function doLater(f) {
      thunks.push(f);
    };

    for (let i = 0; i < 3; i++) {
      print(i);
    }
    test.equal(buf, [0, 1, 2]);
    buf.length = 0;

    for (let i = 0; i < 3; i++) {
      doLater(() => {
        print(i);
      });
    }

    _.each(thunks, f => f());
    test.equal(buf, [0, 1, 2]);
  }
});

Tinytest.add("ecmascript - runtime - classes - super", (test) => {
  {
    class Class1 {
      foo() { return 123; }
      static bar() { return 1; }
    }
    class Class2 extends Class1 {}
    class Class3 extends Class2 {
      foo() {
        return super.foo() + Class3.bar();
      }
    }

    test.equal((new Class3).foo(), 124);
  }

  {
    class Foo {
      constructor(value) { this.value = value; }
      x() { return this.value; }
    }

    class Bar extends Foo {
      constructor() { super(123); }
      x() { return super.x(); }
    }

    test.equal((new Bar).x(), 123);
  }
});

Tinytest.add("ecmascript - runtime - object rest/spread", (test) => {
  const middle = {b:2, c:3};
  // uses `babelHelpers._extends`
  const full = {a:1, ...middle, d:4};
  test.equal(full, {a:1, b:2, c:3, d:4});
});

Tinytest.add("ecmascript - runtime - spread args to new", (test) => {

  const Foo = function (one, two, three) {
    test.isTrue(this instanceof Foo);
    test.equal(one, 1);
    test.equal(two, 2);
    test.equal(three, 3);
    this.created = true;
  };

  const oneTwo = [1, 2];

  // uses `babelHelpers.bind`
  const foo = new Foo(...oneTwo, 3);
  test.isTrue(foo.created);
});

Tinytest.add("ecmascript - runtime - Map spread", (test) => {
  const map = new Map;

  map.set(0, 1);
  map.set(1, 2);
  map.set(2, 3);

  test.equal([...map], [
    [0, 1],
    [1, 2],
    [2, 3]
  ]);
});

Tinytest.add("ecmascript - runtime - Set spread", (test) => {
  const set = new Set;

  set.add("a");
  set.add(1);
  set.add(false);

  test.equal([...set], ["a", 1, false]);
});

Tinytest.add("ecmascript - runtime - destructuring", (test) => {
  // uses `babelHelpers.objectWithoutProperties` and
  // `babelHelpers.objectDestructuringEmpty`

  const obj = {a:1, b:2};
  const {a, ...rest} = obj;
  test.equal(a, 1);
  test.equal(rest, {b:2});

  const {} = {};

  test.throws(() => {
    const {} = null;
  }, /Cannot destructure undefined/);

  const [x, y, z] = function*() {
    let x = 1;
    while (true) {
      yield x++;
    }
  }();

  test.equal(x, 1);
  test.equal(y, 2);
  test.equal(z, 3);
});

Tinytest.addAsync("ecmascript - runtime - misc support", (test, done) => {
  // Verify that the runtime was installed.
  test.equal(typeof meteorBabelHelpers, "object");
  test.equal(typeof meteorBabelHelpers.sanitizeForInObject, "function");

  class Base {
    constructor(...args) {
      this.sum = 0;
      args.forEach(arg => this.sum += arg);
    }

    static inherited() {
      return "inherited";
    }
  }

  class Derived extends Base {
    constructor() {
      super(1, 2, 3);
    }
  }

  // Check that static methods are inherited.
  test.equal(Derived.inherited(), "inherited");

  const d = new Derived();
  test.equal(d.sum, 6);

  const expectedError = new Error("expected");

  Promise.resolve("working").then(result => {
    test.equal(result, "working");
    throw expectedError;
  }).catch(error => {
    test.equal(error, expectedError);
    if (Meteor.isServer) {
      const Fiber = Npm.require("fibers");
      // Make sure the Promise polyfill runs callbacks in a Fiber.
      test.instanceOf(Fiber.current, Fiber);
    }
  }).then(done, error => test.exception(error));
});


import {tokenizer, SourceLocation, tokTypes as tt, Node, lineBreak, isNewLine} from "../index"

// Registered plugins
export const pluginsLoose = {}

export class LooseParser {
  constructor(input, options = {}) {
    this.toks = tokenizer(input, options)
    this.options = this.toks.options
    this.input = this.toks.input
    this.tok = this.last = {type: tt.eof, start: 0, end: 0}
    if (this.options.locations) {
      let here = this.toks.curPosition()
      this.tok.loc = new SourceLocation(this.toks, here, here)
    }
    this.ahead = [] // Tokens ahead
    this.context = [] // Indentation contexted
    this.curIndent = 0
    this.curLineStart = 0
    this.nextLineStart = this.lineEnd(this.curLineStart) + 1
    this.inAsync = false
    // Load plugins
    this.options.pluginsLoose = options.pluginsLoose || {}
    this.loadPlugins(this.options.pluginsLoose)
  }

  startNode() {
    return new Node(this.toks, this.tok.start, this.options.locations ? this.tok.loc.start : null)
  }

  storeCurrentPos() {
    return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start
  }

  startNodeAt(pos) {
    if (this.options.locations) {
      return new Node(this.toks, pos[0], pos[1])
    } else {
      return new Node(this.toks, pos)
    }
  }

  finishNode(node, type) {
    node.type = type
    node.end = this.last.end
    if (this.options.locations)
      node.loc.end = this.last.loc.end
    if (this.options.ranges)
      node.range[1] = this.last.end
    return node
  }

  dummyNode(type) {
    let dummy = this.startNode()
    dummy.type = type
    dummy.end = dummy.start
    if (this.options.locations)
      dummy.loc.end = dummy.loc.start
    if (this.options.ranges)
      dummy.range[1] = dummy.start
    this.last = {type: tt.name, start: dummy.start, end: dummy.start, loc: dummy.loc}
    return dummy
  }

  dummyIdent() {
    let dummy = this.dummyNode("Identifier")
    dummy.name = "✖"
    return dummy
  }

  dummyString() {
    let dummy = this.dummyNode("Literal")
    dummy.value = dummy.raw = "✖"
    return dummy
  }

  eat(type) {
    if (this.tok.type === type) {
      this.next()
      return true
    } else {
      return false
    }
  }

  isContextual(name) {
    return this.tok.type === tt.name && this.tok.value === name
  }

  eatContextual(name) {
    return this.tok.value === name && this.eat(tt.name)
  }

  canInsertSemicolon() {
    return this.tok.type === tt.eof || this.tok.type === tt.braceR ||
      lineBreak.test(this.input.slice(this.last.end, this.tok.start))
  }

  semicolon() {
    return this.eat(tt.semi)
  }

  expect(type) {
    if (this.eat(type)) return true
    for (let i = 1; i <= 2; i++) {
      if (this.lookAhead(i).type == type) {
        for (let j = 0; j < i; j++) this.next()
        return true
      }
    }
  }

  pushCx() {
    this.context.push(this.curIndent)
  }

  popCx() {
    this.curIndent = this.context.pop()
  }

  lineEnd(pos) {
    while (pos < this.input.length && !isNewLine(this.input.charCodeAt(pos))) ++pos
    return pos
  }

  indentationAfter(pos) {
    for (let count = 0;; ++pos) {
      let ch = this.input.charCodeAt(pos)
      if (ch === 32) ++count
      else if (ch === 9) count += this.options.tabSize
      else return count
    }
  }

  closes(closeTok, indent, line, blockHeuristic) {
    if (this.tok.type === closeTok || this.tok.type === tt.eof) return true
    return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() &&
      (!blockHeuristic || this.nextLineStart >= this.input.length ||
       this.indentationAfter(this.nextLineStart) < indent)
  }

  tokenStartsLine() {
    for (let p = this.tok.start - 1; p >= this.curLineStart; --p) {
      let ch = this.input.charCodeAt(p)
      if (ch !== 9 && ch !== 32) return false
    }
    return true
  }

  extend(name, f) {
    this[name] = f(this[name])
  }

  loadPlugins(pluginConfigs) {
    for (let name in pluginConfigs) {
      let plugin = pluginsLoose[name]
      if (!plugin) throw new Error("Plugin '" + name + "' not found")
      plugin(this, pluginConfigs[name])
    }
  }
}


// ensure we can reach http://npmjs.org before we try to install

// dependencies. `npm install` times out after more than a minute.

buildmessage.error("Can't install npm dependencies. " +

// Recover by returning false from updateDependencies

const tree = getInstalledDependenciesTree(dir);

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


if (Meteor.isClient) {
  var backgroundColor = function () {
    return $(document.body).css('background-color');
  };

  var linkHref = function () {
    var links = document.getElementsByTagName('link');
    if (links.length > 0) {
      return links[0].href;
    } else {
      return null;
    }
  }

  Meteor.startup(function () {
    Meteor.call("clientLoad");
    var numCssChanges = 0;
    var oldBackgroundColor = backgroundColor();
    var oldLinkHref = linkHref();
    Meteor.call("newStylesheet", numCssChanges, oldBackgroundColor);
    var waitingForCssReloadToComplete = false;
    Meteor.setInterval(function () {
      if (waitingForCssReloadToComplete)
        return;

      var newBackgroundColor = backgroundColor();
      if (oldBackgroundColor !== newBackgroundColor) {
        waitingForCssReloadToComplete = true;

        // give the client some time to load the new css
        var handle = Meteor.setInterval(function () {
          var newLinkHref = linkHref();
          if (newLinkHref !== oldLinkHref) {
            oldBackgroundColor = backgroundColor();
            oldLinkHref = newLinkHref;
            Meteor.call("newStylesheet", ++numCssChanges, oldBackgroundColor);
            waitingForCssReloadToComplete = false;
            Meteor.clearInterval(handle);
          }
        }, 500);
      }
    }, 500);
  });
}

if (Meteor.isServer) {
  Meteor.methods({
    clientLoad: function () {
      console.log("client connected");
    },

    newStylesheet: function (numCssChanges, backgroundColor) {
      console.log("numCssChanges: " + numCssChanges);
      console.log("background-color: " + backgroundColor);
    }
  });
}


DDPCommon.SUPPORTED_DDP_VERSIONS = [ '1', 'pre2', 'pre1' ];

DDPCommon.parseDDP = function (stringMessage) {
  try {
    var msg = JSON.parse(stringMessage);
  } catch (e) {
    Meteor._debug("Discarding message with invalid JSON", stringMessage);
    return null;
  }
  // DDP messages must be objects.
  if (msg === null || typeof msg !== 'object') {
    Meteor._debug("Discarding non-object DDP message", stringMessage);
    return null;
  }

  // massage msg to get it into "abstract ddp" rather than "wire ddp" format.

  // switch between "cleared" rep of unsetting fields and "undefined"
  // rep of same
  if (_.has(msg, 'cleared')) {
    if (!_.has(msg, 'fields'))
      msg.fields = {};
    _.each(msg.cleared, function (clearKey) {
      msg.fields[clearKey] = undefined;
    });
    delete msg.cleared;
  }

  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(msg, field))
      msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]);
  });

  return msg;
};

DDPCommon.stringifyDDP = function (msg) {
  var copy = EJSON.clone(msg);
  // swizzle 'changed' messages from 'fields undefined' rep to 'fields
  // and cleared' rep
  if (_.has(msg, 'fields')) {
    var cleared = [];
    _.each(msg.fields, function (value, key) {
      if (value === undefined) {
        cleared.push(key);
        delete copy.fields[key];
      }
    });
    if (!_.isEmpty(cleared))
      copy.cleared = cleared;
    if (_.isEmpty(copy.fields))
      delete copy.fields;
  }
  // adjust types to basic
  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(copy, field))
      copy[field] = EJSON._adjustTypesToJSONValue(copy[field]);
  });
  if (msg.id && typeof msg.id !== 'string') {
    throw new Error("Message id is not a string");
  }
  return JSON.stringify(copy);
};


import _ from 'underscore';
import child_process from 'child_process';
import files from '../fs/mini-files';

// The execFileSync function is meant to resemble the similarly-named Node 0.12
// synchronous process creation API, but instead of being fully blocking it
// uses a promise-based implementation. You can also use
// execFileAsync directly, which returns a promise.
// Some functionality is currently missing but could be added when the need
// arises (e.g. support for timeout, maxBuffer, and encoding options).
// Eventually, these versions should replace the ones in tools/utils/utils.js
// and tools/tool-testing/selftest.js.

/**
 * @summary Executes a command synchronously, returning either the captured
 * stdout output or throwing an error containing the stderr output as part of
 * the message. In addition, the error will contain fields pid, stderr, stdout,
 * status and signal.
 * @param {String} command The command to run
 * @param {Array} [args] List of string arguments
 * @param {Object} [options]
 * @param {Object} [options.cwd] Current working directory of the child process
 * @param {Object} [options.env] Environment key-value pairs
 * @param {Array|String} [options.stdio] Child's stdio configuration.
 * (Default: 'pipe') Specifying anything else than 'pipe' will disallow
 * capture.
 * @param {Writable} [options.destination] If specified, instead of capturing
 * the output, the child process stdout will be piped to the destination stream.
 * @param {String} [options.waitForClose] Whether to wait for the child process
 * streams to close or to resolve the promise when the child process exits.
 * @returns {String} The stdout from the command
 */
export function execFileSync(command, args, options) {
  return Promise.await(execFileAsync(command, args, options));
}

/**
 * @summary Executes a command asynchronously, returning a promise that will
 * either be resolved to the captured stdout output or be rejected with an
 * error containing the stderr output as part of the message. In addition,
 * the error will contain fields pid, stderr, stdout, status and signal.
 * @param {String} command The command to run
 * @param {Array} [args] List of string arguments
 * @param {Object} [options]
 * @param {Object} [options.cwd] Current working directory of the child process
 * @param {Object} [options.env] Environment key-value pairs
 * @param {Array|String} [options.stdio] Child's stdio configuration.
 * (Default: 'pipe') Specifying anything else than 'pipe' will disallow
 * capture.
 * @param {Writable} [options.destination] If specified, instead of capturing
 * the output, the child process stdout will be piped to the destination stream.
 * @param {String} [options.waitForClose] Whether to wait for the child process
 * streams to close or to resolve the promise when the child process exits.
 * @returns {Promise<String>}
 */
export function execFileAsync(command, args,
  options = { waitForClose: true }) {
  // args is optional, so if it's not an array we interpret it as options
  if (!Array.isArray(args)) {
    options = _.extend(options, args);
    args = [];
  }
  if (options.cwd) {
    options.cwd = files.convertToOSPath(options.cwd);
  }
  // The child process close event is emitted when the stdio streams
  // have all terminated. If those streams are shared with other
  // processes, that means we won't receive a 'close' until all processes
  // have exited, so we may want to respond to 'exit' instead.
  // (The downside of responding to 'exit' is that the streams may not be
  // fully flushed, so we could miss captured output. Only use this
  // option when needed.)
  const exitEvent = options.waitForClose ? 'close' : 'exit';

  return new Promise((resolve, reject) => {
    var child;

    if (process.platform !== 'win32') {
      child = child_process.spawn(command, args,
      { cwd, env, stdio } = options);
    } else {
      // https://github.com/nodejs/node-v0.x-archive/issues/2318
      args.forEach(arg => {
        command += ' ' + arg;
      });
      child = child_process.exec(command,
      { cwd, env, stdio } = options);
    }

    let capturedStdout = '';
    if (child.stdout) {
      if (options.destination) {
        child.stdout.pipe(options.destination);
      } else {
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', (data) => {
          capturedStdout += data;
        });
      }
    }

    let capturedStderr = '';
    if (child.stderr) {
      child.stderr.setEncoding('utf8');
      child.stderr.on('data', (data) => {
        capturedStderr += data;
      });
    }

    const errorCallback = (error) => {
      // Make sure we only receive one type of callback
      child.removeListener(exitEvent, exitCallback);

      // Trim captured output to get rid of excess whitespace
      capturedStdout = capturedStdout.trim();
      capturedStderr = capturedStderr.trim();

      _.extend(error, {
        pid: child.pid,
        stdout: capturedStdout,
        stderr: capturedStderr,
      });

      // Set a more informative error message on ENOENT, that includes the
      // command we attempted to execute
      if (error.code === 'ENOENT') {
        error.message = `Could not find command '${command}'`;
      }

      reject(error);
    };
    child.on('error', errorCallback);

    const exitCallback = (code, signal) => {
      // Make sure we only receive one type of callback
      child.removeListener('error', errorCallback);

      // Trim captured output to get rid of excess whitespace
      capturedStdout = capturedStdout.trim();
      capturedStderr = capturedStderr.trim();

      if (code === 0) {
        resolve(capturedStdout);
      } else {
        let errorMessage = `Command failed: ${command}`;
        if (args) {
          errorMessage += ` ${args.join(' ')}`;
        }
        errorMessage += `\n${capturedStderr}`;

        const error = new Error(errorMessage);

        _.extend(error, {
          pid: child.pid,
          stdout: capturedStdout,
          stderr: capturedStderr,
          status: code,
          signal: signal
        });

        reject(error);
      }
    };
    child.on(exitEvent, exitCallback);
  });
}


keep keep keep keep keep replace keep keep keep keep

var run = s.run("create", "foobar", "--blaze");

var selftest = require('../tool-testing/selftest.js');
var Sandbox = selftest.Sandbox;
const SIMPLE_WAREHOUSE = { v1: { recommended: true } };

selftest.define("create main", function () {
  // We need a warehouse so the tool doesn't think we are running from checkout
  var s = new Sandbox({ warehouse: SIMPLE_WAREHOUSE });
  // Can we create an app? Yes!
  var run = s.run("create", "foobar");
  run.match("Created a new Meteor app in 'foobar'.");
  run.match("To run your new app");
  run.expectExit(0);

  // Test that the release constraints have been written to .meteor/packages
  s.cd("foobar");
  const packages = s.read(".meteor/packages");
  if (!packages.match('meteor-base@')) {
    selftest.fail("Failed to add a version specifier to `meteor-base` package");
  }

  const packageJson = JSON.parse(s.read("package.json"));
  if (! packageJson.dependencies.hasOwnProperty("@babel/runtime")) {
    selftest.fail("New app package.json does not depend on @babel/runtime");
  }

  // Install basic packages like babel-runtime and meteor-node-stubs from
  // package.json.
  run = s.run("npm", "install");
  run.expectExit(0);

  // Now, can we run it?
  run = s.run();
  run.match("foobar");
  run.match("proxy.");
  // Do not print out the changes to the versions file!
  run.waitSecs(5);
  run.read("=> Started MongoDB", false);
  run.match("your app");
  run.match("running at");
  run.match("localhost");
  run.stop();

  run = s.run("create", "--list");
  run.read('Available');
  run.match('leaderboard');
  run.expectExit(0);
  // XXX test that --list always gives you the examples of the current
  // release!

  // XXX XXX more more
});

["bare",
 "minimal",
 "full",
].forEach(template => {
  selftest.define("create --" + template, function () {
    const s = new Sandbox;

    // Can we create an app? Yes!
    let run = s.run("create", "--" + template, template);
    run.waitSecs(60);
    run.match("Created a new Meteor app in '" + template + "'.");
    run.match("To run your new app");

    s.cd(template);
    run = s.run();
    run.waitSecs(60);
    run.match(template);
    run.match("proxy")
    run.waitSecs(60);
    run.match("your app");
    run.waitSecs(5);
    run.match("running at");
    run.match("localhost");

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


try {
  var babelRuntimeVersion = require("@babel/runtime/package.json").version;
} catch (e) {
  throw new Error([
    "",
    "The @babel/runtime npm package could not be found in your node_modules ",
    "directory. Please run the following command to install it:",
    "",
    "  meteor npm install --save @babel/runtime",
    ""
  ].join("\n"));
}

if (parseInt(babelRuntimeVersion, 10) < 7 ||
    (babelRuntimeVersion.indexOf("7.0.0-beta.") === 0 &&
     parseInt(babelRuntimeVersion.split(".").pop(), 10) < 56)) {
  console.error([
    "The version of @babel/runtime installed in your node_modules directory ",
    "(" + babelRuntimeVersion + ") is out of date. Please upgrade it by running ",
    "",
    "  meteor npm install --save @babel/runtime@latest",
    "",
    "in your application directory.",
    ""
  ].join("\n"));
}


# mobile-experience
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/mobile-experience) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/mobile-experience)
***

A set of Cordova/PhoneGap-specific packages that set some good defaults when building for mobile. These packages only activate when you are building a native Android or iOS app.

1. [mobile-status-bar](https://atmospherejs.com/meteor/mobile-status-bar) - avoid the status bar information covering up your app content
2. [launch-screen](https://atmospherejs.com/meteor/launch-screen) - cover the app with a launch image so that people don’t have to see things loading