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

----------
jsdoc: https://github.com/jsdoc3/jsdoc
----------

# License #

JSDoc 3 is free software, licensed under the Apache License, Version 2.0 (the
"License"). Commercial and non-commercial use are permitted in compliance with
the License.

Copyright (c) 2011-2014 Michael Mathews <micmath@gmail.com> and the
[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors).
All rights reserved.

You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0

In addition, a copy of the License is included with this distribution.

As stated in Section 7, "Disclaimer of Warranty," of the License:

> Licensor provides the Work (and each Contributor provides its Contributions)
> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
> express or implied, including, without limitation, any warranties or
> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
> PARTICULAR PURPOSE. You are solely responsible for determining the
> appropriateness of using or redistributing the Work and assume any risks
> associated with Your exercise of permissions under this License.

The source code for JSDoc 3 is available at:
https://github.com/jsdoc3/jsdoc

# Third-Party Software #

JSDoc 3 includes or depends upon the following third-party software, either in
whole or in part. Each third-party software package is provided under its own
license.

## MIT License ##

Several of the following software packages are distributed under the MIT
license, which is reproduced below:

> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.

## Acorn ##

Portions of the Acorn source code are incorporated into the following files:

- `lib/jsdoc/src/walker.js`

Acorn is distributed under the MIT license, which is reproduced above.

Copyright (C) 2012 Marijn Haverbeke <marijnh@gmail.com>.

The source code for Acorn is available at:
https://github.com/marijnh/acorn

## Async.js ##

Async.js is distributed under the MIT license, which is reproduced above.

Copyright (c) 2010 Caolan McMahon.

The source code for Async.js is available at:
https://github.com/caolan/async

## Catharsis ##

Catharsis is distributed under the MIT license, which is reproduced above.

Copyright (c) 2012-2014 Jeff Williams.

The source code for Catharsis is available at:
https://github.com/hegemonic/catharsis

## crypto-browserify ##

crypto-browserify is distributed under the MIT license, which is reproduced
above.

Copyright (c) 2013 Dominic Tarr.

The source code for crypto-browserify is available at:
https://github.com/dominictarr/crypto-browserify

## escape-string-regexp ##

escape-string-regexp is distributed under the MIT License, which is reproduced
above.

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com>.

The source code for escape-string-regexp is available at:
https://github.com/sindresorhus/escape-string-regexp

## Esprima ##

Esprima is distributed under the BSD 2-clause license:

> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions are met:
>
> - Redistributions of source code must retain the above copyright notice,
>   this list of conditions and the following disclaimer.
> - Redistributions in binary form must reproduce the above copyright notice,
>   this list of conditions and the following disclaimer in the documentation
>   and/or other materials provided with the distribution.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Copyright (c) 2011-2013 Ariya Hidayat and other Esprima contributors.

The source code for Esprima is available at:
https://github.com/ariya/esprima

## events ##

Portions of the events source code are incorporated into the following files:

+ `rhino/events.js`

events is distributed under the MIT license, which is reproduced above.

Copyright Joyent, Inc. and other Node contributors. All rights reserved.

The source code for events is available at:
https://github.com/Gozala/events

## github-flavored-markdown ##

github-flavored-markdown is distributed under the BSD 3-clause license:

> Copyright (c) 2007, John Fraser <http://www.attacklab.net/> All rights
> reserved.
>
> Original Markdown copyright (c) 2004, John Gruber <http://daringfireball.net/>
> All rights reserved.
>
> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions are met:
>
> - Redistributions of source code must retain the above copyright notice,
>   this list of conditions and the following disclaimer.
>
> - Redistributions in binary form must reproduce the above copyright notice,
>   this list of conditions and the following disclaimer in the documentation
>   and/or other materials provided with the distribution.

> - Neither the name "Markdown" nor the names of its contributors may be used
>   to endorse or promote products derived from this software without specific
>   prior written permission.
>
> This software is provided by the copyright holders and contributors "as is"
> and any express or implied warranties, including, but not limited to, the
> implied warranties of merchantability and fitness for a particular purpose are
> disclaimed. In no event shall the copyright owner or contributors be liable
> for any direct, indirect, incidental, special, exemplary, or consequential
> damages (including, but not limited to, procurement of substitute goods or
> services; loss of use, data, or profits; or business interruption) however
> caused and on any theory of liability, whether in contract, strict liability,
> or tort (including negligence or otherwise) arising in any way out of the use
> of this software, even if advised of the possibility of such damage.

The source code for github-flavored-markdown is available at:
https://github.com/hegemonic/github-flavored-markdown

## Google Code Prettify ##

Google Code Prettify is distributed under the Apache License 2.0, which is
included with this package.

Copyright (c) 2006 Google Inc.

The source code for Google Code Prettify is available at:
https://code.google.com/p/google-code-prettify/

## Jasmine ##

Jasmine is distributed under the MIT license, which is reproduced above.

Copyright (c) 2008-2011 Pivotal Labs.

The source code for Jasmine is available at:
https://github.com/pivotal/jasmine

## jasmine-node ##

jasmine-node is distributed under the MIT license, which is reproduced above.

Copyright (c) 2010 Adam Abrons and Misko Hevery (http://getangular.com).

The source code for jasmine-node is available at:
https://github.com/mhevery/jasmine-node

## js2xmlparser ##

js2xmlparser is distributed under the MIT license, which is reproduced above.

Copyright (c) 2012 Michael Kourlas.

The source code for js2xmlparser is available at:
https://github.com/michaelkourlas/node-js2xmlparser

## Node.js ##

Portions of the Node.js source code are incorporated into the following files:

- `rhino/fs.js`
- `rhino/path.js`
- `rhino/querystring.js`
- `rhino/util.js`

Node.js is distributed under the MIT license, which is reproduced above.

Copyright Joyent, Inc. and other Node contributors. All rights reserved.

The source code for Node.js is available at:
https://github.com/joyent/node

## node-browser-builtins ##

Portions of the node-browser-builtins source code are incorporated into the
following files:

- `rhino/assert.js`
- `rhino/rhino-shim.js`

node-browser-builtins is distributed under the MIT license, which is reproduced
above.

The source code for node-browser-builtins is available at:
https://github.com/alexgorbatchev/node-browser-builtins

## Requizzle ##

Requizzle is distributed under the MIT license, which is reproduced above.

Copyright (c) 2014 Google Inc. All rights reserved.
Copyright (c) 2012-2013 Johannes Ewald.

The source code for Requizzle is available at:
https://github.com/hegemonic/requizzle

## Rhino ##

Rhino is distributed under the following licenses:

### MPL 2.0 License ###
The majority of the source code for Rhino is available under the Mozilla Public
License (MPL) 2.0, which is included in this distribution.

### License for portions of the Rhino debugger ###
Additionally, some files are available under the BSD 3-clause license:

> Copyright 1997, 1998 Sun Microsystems, Inc.  All Rights Reserved.
>
> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions are met:
>
> - Redistributions of source code must retain the above copyright notice,
>   this list of conditions and the following disclaimer.
> - Redistributions in binary form must reproduce the above copyright
>   notice, this list of conditions and the following disclaimer in the
>   documentation and/or other materials provided with the distribution.
> - Neither the name of Sun Microsystems nor the names of its contributors
>   may be used to endorse or promote products derived from this software
>   without specific prior written permission.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
> DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

### Source Code ###
The source code for Rhino is available at:
https://github.com/jsdoc3/rhino

## TaffyDB ##

TaffyDB is distributed under a modified BSD license:

> All rights reserved.
>
> Redistribution and use of this software in source and binary forms, with or
> without modification, are permitted provided that the following condition is
> met:
>
> Redistributions of source code must retain the above copyright notice, this
> list of conditions and the following disclaimer.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> POSSIBILITY OF SUCH DAMAGE.

The source code for TaffyDB is available at:
https://github.com/hegemonic/taffydb

## Tomorrow Theme for Google Code Prettify ##

License information for the Tomorrow Theme for Google Code Prettify is not
available. It is assumed that the package is distributed under an open source
license that is compatible with the Apache License 2.0.

Copyright (c) Yoshihide Jimbo.

The source code for the Tomorrow Theme is available at:
https://github.com/jmblog/color-themes-for-google-code-prettify

## tv4 ##

tv4 is in the public domain. It is also distributed under the MIT license, which
is reproduced above.

The source code for tv4 is available at:
https://github.com/geraintluff/tv4

## Underscore.js ##

Underscore.js is distributed under the MIT license, which is reproduced above.

Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative
Reporters & Editors.

The source code for Underscore.js is available at:
https://github.com/jashkenas/underscore

## wrench-js ##

wrench-js is distributed under the MIT license, which is reproduced above.

Copyright (c) 2010 Ryan McGrath.

The source code for wrench-js is available at:
https://github.com/ryanmcgrath/wrench-js


///
/// utility functions for files and directories. includes both generic
/// helper functions (such as rm_recursive), and meteor-specific ones
/// (such as testing whether an directory is a meteor app)
///

import assert from "assert";
import fs, { PathLike, Stats, Dirent } from "fs";
import path from "path";
import os from "os";
import { spawn, execFile } from "child_process";
import { EventEmitter } from "events";
import { Slot } from "@wry/context";
import { dep } from "optimism";

const _ = require('underscore');
const Fiber = require("fibers");

const rimraf = require('rimraf');
const sourcemap = require('source-map');
const sourceMapRetrieverStack = require('../tool-env/source-map-retriever-stack.js');

const utils = require('../utils/utils.js');
const cleanup = require('../tool-env/cleanup.js');
const buildmessage = require('../utils/buildmessage.js');
const fiberHelpers = require('../utils/fiber-helpers.js');
const colonConverter = require('../utils/colon-converter.js');

const Profile = require('../tool-env/profile').Profile;

export * from '../static-assets/server/mini-files';
import {
  convertToOSPath,
  convertToPosixPath,
  convertToStandardLineEndings,
  convertToStandardPath,
  convertToWindowsPath,
  isWindowsLikeFilesystem,
  pathBasename,
  pathDirname,
  pathJoin,
  pathNormalize,
  pathOsDelimiter,
  pathRelative,
  pathResolve,
  pathSep,
} from "../static-assets/server/mini-files";

const { hasOwnProperty } = Object.prototype;

const parsedSourceMaps: Record<string, any> = {};
let nextStackFilenameCounter = 1;

// Use the source maps specified to runJavaScript
function useParsedSourceMap(pathForSourceMap: string) {
  // Check our fancy source map data structure, used for isopacks
  if (hasOwnProperty.call(parsedSourceMaps, pathForSourceMap)) {
    return {map: parsedSourceMaps[pathForSourceMap]};
  }

  return null;
}

// Try this source map first
sourceMapRetrieverStack.push(useParsedSourceMap);

function canYield() {
  return Fiber.current &&
    Fiber.yield &&
    ! Fiber.yield.disallowed;
}

// given a predicate function and a starting path, traverse upwards
// from the path until we find a path that satisfies the predicate.
//
// returns either the path to the lowest level directory that passed
// the test or null for none found. if starting path isn't given, use
// cwd.
function findUpwards(
  predicate: (path: string) => boolean,
  startPath: string = cwd(),
): string | null {
  let testDir: string | null = startPath;
  while (testDir) {
    if (predicate(testDir)) {
      break;
    }
    var newDir: string = pathDirname(testDir);
    if (newDir === testDir) {
      testDir = null;
    } else {
      testDir = newDir;
    }
  }
  return testDir || null;
}

export function cwd() {
  return convertToStandardPath(process.cwd());
}

// Determine if 'filepath' (a path, or omit for cwd) is within an app
// directory. If so, return the top-level app directory.
export function findAppDir(filepath: string) {
  return findUpwards(function isAppDir(filepath) {
    // XXX once we are done with the transition to engine, this should
    // change to: `return exists(path.join(filepath, '.meteor',
    // 'release'))`

    // .meteor/packages can be a directory, if .meteor is a warehouse
    // directory.  since installing meteor initializes a warehouse at
    // $HOME/.meteor, we want to make sure your home directory (and all
    // subdirectories therein) don't count as being within a meteor app.
    try { // use try/catch to avoid the additional syscall to exists
      return stat(
        pathJoin(filepath, '.meteor', 'packages')).isFile();
    } catch (e) {
      return false;
    }
  }, filepath);
}

export function findPackageDir(filepath: string) {
  return findUpwards(function isPackageDir(filepath) {
    try {
      return stat(pathJoin(filepath, 'package.js')).isFile();
    } catch (e) {
      return false;
    }
  }, filepath);
}

// Returns the hash of the current Git HEAD revision of the application,
// if possible. Always resolves rather than rejecting (unless something
// truly unexpected happens). The result value is a string when a Git
// revision was successfully resolved, or undefined otherwise.
export function findGitCommitHash(path: string) {
  return new Promise<string|void>(resolve => {
    const appDir = findAppDir(path);
    if (appDir) {
      execFile("git", ["rev-parse", "HEAD"], {
        cwd: convertToOSPath(appDir),
      }, (error: any, stdout: string) => {
        if (! error && typeof stdout === "string") {
          resolve(stdout.trim());
        } else {
          resolve();
        }
      });
    } else {
      resolve();
    }
  }).await();
}

// create a .gitignore file in dirPath if one doesn't exist. add
// 'entry' to the .gitignore on its own line at the bottom of the
// file, if the exact line does not already exist in the file.
export function addToGitignore(dirPath: string, entry: string) {
  const filePath = pathJoin(dirPath, ".gitignore");
  if (exists(filePath)) {
    let data = readFile(filePath, 'utf8') as string;
    const lines = data.split(/\n/);
    if (lines.some(line => line === entry)) {
      // already there do nothing
    } else {
      // rewrite file w/ new entry.
      if (data.substr(-1) !== "\n") {
        data = data + "\n";
      }
      data = data + entry + "\n";
      writeFile(filePath, data, 'utf8');
    }
  } else {
    // doesn't exist, just write it.
    writeFile(filePath, entry + "\n", 'utf8');
  }
}

// Are we running Meteor from a git checkout?
export const inCheckout = _.once(function () {
  try {
    if (exists(pathJoin(getCurrentToolsDir(), '.git'))) {
      return true;
    }
  } catch (e) { console.log(e); }

  return false;
});

// True if we are using a warehouse: either installed Meteor, or if
// $METEOR_WAREHOUSE_DIR is set. Otherwise false (we're in a git checkout and
// just using packages from the checkout).
export function usesWarehouse() {
  // Test hook: act like we're "installed" using a non-homedir warehouse
  // directory.
  if (process.env.METEOR_WAREHOUSE_DIR) {
    return true;
  } else {
    return ! inCheckout();
  }
}

// Read the '.tools_version.txt' file. If in a checkout, throw an error.
export function getToolsVersion() {
  if (! inCheckout()) {
    const isopackJsonPath = pathJoin(getCurrentToolsDir(),
      '..',  // get out of tool, back to package
      'isopack.json');

    let parsed;

    if (exists(isopackJsonPath)) {
      // XXX "isopack-1" is duplicate of isopack.currentFormat
      parsed = JSON.parse(readFile(isopackJsonPath))["isopack-1"];
      return parsed.name + '@' + parsed.version;
    }

    // XXX COMPAT WITH 0.9.3
    const unipackageJsonPath = pathJoin(
      getCurrentToolsDir(),
      '..',  // get out of tool, back to package
      'unipackage.json'
    );
    parsed = JSON.parse(readFile(unipackageJsonPath));
    return parsed.name + '@' + parsed.version;

  } else {
    throw new Error("Unexpected. Git checkouts don't have tools versions.");
  }
}

// Return the root of dev_bundle (probably /usr/local/meteor in an
// install, or (checkout root)/dev_bundle in a checkout.).
export function getDevBundle() {
  return pathJoin(getCurrentToolsDir(), 'dev_bundle');
}

export function getCurrentNodeBinDir() {
  return pathJoin(getDevBundle(), "bin");
}

// Return the top-level directory for this meteor install or checkout
export function getCurrentToolsDir() {
  return pathDirname(pathDirname(convertToStandardPath(__dirname)));
}

// Read a settings file and sanity-check it. Returns a string on
// success or null on failure (in which case buildmessages will be
// emitted).
export function getSettings(
  filename: string,
  watchSet: import("./watch").WatchSet,
) {
  buildmessage.assertInCapture();
  const absPath = pathResolve(filename);
  const buffer = require("./watch").readAndWatchFile(watchSet, absPath);
  if (buffer === null) {
    buildmessage.error("file not found (settings file)",
                       { file: filename });
    return null;
  }

  if (buffer.length > 0x10000) {
    buildmessage.error("settings file is too large (must be less than 64k)",
                       { file: filename });
    return null;
  }

  let str = buffer.toString('utf8');

  // The use of a byte order mark crashes JSON parsing. Since a BOM is not
  // required (or recommended) when using UTF-8, let's remove it if it exists.
  str = str.charCodeAt(0) === 0xFEFF ? str.slice(1) : str;

  // Ensure that the string is parseable in JSON, but there's no reason to use
  // the object value of it yet.
  if (str.match(/\S/)) {
    try {
      JSON.parse(str);
    } catch (e) {
      buildmessage.error("parse error reading settings file",
                         { file: filename });
    }
  }

  return str;
}

// Try to find the prettiest way to present a path to the
// user. Presently, the main thing it does is replace $HOME with ~.
export function prettyPath(p: string) {
  p = realpath(p);
  const home = getHomeDir();
  if (! home) {
    return p;
  }
  const relativeToHome = pathRelative(home, p);
  if (relativeToHome.substr(0, 3) === ('..' + pathSep)) {
    return p;
  }
  return pathJoin('~', relativeToHome);
}

// Like statSync, but null if file not found
export function statOrNull(path: string) {
  return statOrNullHelper(path, false);
}

function statOrNullHelper(path: string, preserveSymlinks = false) {
  try {
    return preserveSymlinks
      ? lstat(path)
      : stat(path);
  } catch (e) {
    if (e.code === "ENOENT") {
      return null;
    }
    throw e;
  }
}

export function realpathOrNull(path: string) {
  try {
    return realpath(path);
  } catch (e) {
    if (e.code !== "ENOENT") throw e;
    return null;
  }
}

export function rm_recursive_async(path: string) {
  return new Promise<void>((resolve, reject) => {
    rimraf(convertToOSPath(path), (err: Error) => err
      ? reject(err)
      : resolve());
  });
}

// Like rm -r.
export const rm_recursive = Profile("files.rm_recursive", (path: string) => {
  try {
    rimraf.sync(convertToOSPath(path));
  } catch (e) {
    if ((e.code === "ENOTEMPTY" ||
         e.code === "EPERM") &&
        canYield()) {
      rm_recursive_async(path).await();
      return;
    }
    throw e;
  }
});

// Returns the base64 SHA256 of the given file.
export function fileHash(filename: string) {
  const crypto = require('crypto');
  const hash = crypto.createHash('sha256');
  hash.setEncoding('base64');
  const rs = createReadStream(filename);
  return new Promise(function (resolve) {
    rs.on('end', function () {
      rs.close();
      resolve(hash.digest('base64'));
    });
    rs.pipe(hash, { end: false });
  }).await();
}

// This is the result of running fileHash on a blank file.
export const blankHash = "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";

// Returns a base64 SHA256 hash representing a tree on disk. It is not sensitive
// to modtime, uid/gid, or any permissions bits other than the current-user-exec
// bit on normal files.
export function treeHash(root: string, optionsParams: {
  ignore?: (path: string) => boolean;
}) {
  const options = {
    ignore() { return false; },
    ...optionsParams,
  };

  const hash = require('crypto').createHash('sha256');

  function traverse(relativePath: string) {
    if (options.ignore(relativePath)) {
      return;
    }

    var absPath = pathJoin(root, relativePath);
    var stat = lstat(absPath);

    if (stat.isDirectory()) {
      if (relativePath) {
        hash.update('dir ' + JSON.stringify(relativePath) + '\n');
      }
      readdir(absPath).forEach(entry => {
        traverse(pathJoin(relativePath, entry));
      });
    } else if (stat.isFile()) {
      if (!relativePath) {
        throw Error("must call files.treeHash on a directory");
      }
      hash.update('file ' + JSON.stringify(relativePath) + ' ' +
                  stat.size + ' ' + fileHash(absPath) + '\n');
      if (stat.mode & 0o100) {
        hash.update('exec\n');
      }
    } else if (stat.isSymbolicLink()) {
      if (!relativePath) {
        throw Error("must call files.treeHash on a directory");
      }
      hash.update('symlink ' + JSON.stringify(relativePath) + ' ' +
                  JSON.stringify(readlink(absPath)) + '\n');
    }
    // ignore anything weirder
  };

  traverse('');

  return hash.digest('base64');
}

// like mkdir -p. if it returns true, the item is a directory (even if
// it was already created). if it returns false, the item is not a
// directory and we couldn't make it one.
export function mkdir_p(dir: string, mode: number | null = null) {
  const p = pathResolve(dir);
  const ps = pathNormalize(p).split(pathSep);

  const stat = statOrNull(p);
  if (stat) {
    return stat.isDirectory();
  }

  // doesn't exist. recurse to build parent.
  // Don't use pathJoin here because it can strip off the leading slash
  // accidentally.
  const parentPath = ps.slice(0, -1).join(pathSep);
  const success = mkdir_p(parentPath, mode);
  // parent is not a directory.
  if (! success) { return false; }

  try {
    mkdir(p, mode);
  } catch (err) {
    if (err.code === "EEXIST") {
      if (pathIsDirectory(p)) {
        // all good, someone else created this directory for us while we were
        // yielding
        return true;
      } else {
        return false;
      }
    } else {
      throw err;
    }
  }

  // double check we exist now
  return pathIsDirectory(p);
}

function pathIsDirectory(path: string) {
  const stat = statOrNull(path);
  return stat && stat.isDirectory();
}

// Roughly like cp -R.
//
// The output files will be readable and writable by everyone that the umask
// allows, and executable by everyone (modulo umask) if the original file was
// owner-executable. Symlinks are treated transparently (ie the contents behind
// them are copied, and it's an error if that points nowhere).
//
// If options.transform{Filename, Contents} is present, it should
// be a function, and the contents (as a buffer) or filename will be
// passed through the function. Use this to, eg, fill templates.
//
// If options.ignore is present, it should be a list of regexps. Any
// file whose basename matches one of the regexps, before
// transformation, will be skipped.
export function cp_r(from: string, to: string, options: {
  preserveSymlinks?: boolean;
  ignore?: RegExp[];
  transformFilename?: (f: string) => string;
  transformContents?: (
    contents: ReturnType<typeof readFile>,
    file: string,
  ) => typeof contents;
} = {}) {
  from = pathResolve(from);

  const stat = statOrNullHelper(from, options.preserveSymlinks);
  if (! stat) {
    return;
  }

  if (stat.isDirectory()) {
    mkdir_p(to, 0o755);

    readdir(from).forEach(f => {
      if (options.ignore &&
          options.ignore.some(pattern => f.match(pattern))) {
        return;
      }

      const fullFrom = pathJoin(from, f);

      if (options.transformFilename) {
        f = options.transformFilename(f);
      }

      cp_r(
        fullFrom,
        pathJoin(to, f),
        options
      );
    })

    return;
  }

  mkdir_p(pathDirname(to));

  if (stat.isSymbolicLink()) {
    symlinkWithOverwrite(readlink(from), to);

  } else if (options.transformContents) {
    writeFile(to, options.transformContents(
      readFile(from),
      pathBasename(from)
    ), {
      // Create the file as readable and writable by everyone, and
      // executable by everyone if the original file is executable by
      // owner. (This mode will be modified by umask.) We don't copy the
      // mode *directly* because this function is used by 'meteor create'
      // which is copying from the read-only tools tree into a writable app.
      mode: (stat.mode & 0o100) ? 0o777 : 0o666,
    });

  } else {
    // Note: files.copyFile applies the same stat.mode logic as above.
    copyFile(from, to);
  }
}

// create a symlink, overwriting the target link, file, or directory
// if it exists
export const symlinkWithOverwrite =
Profile("files.symlinkWithOverwrite", function symlinkWithOverwrite(
  source: string,
  target: string,
) {
  const args: [string, string, "junction"?] = [source, target];

  if (process.platform === "win32") {
    const absoluteSource = pathResolve(target, source);

    if (stat(absoluteSource).isDirectory()) {
      args[2] = "junction";
    }
  }

  try {
    symlink(...args);
  } catch (e) {
    if (e.code === "EEXIST") {
      function normalizePath(path: string) {
        return convertToOSPath(path).replace(/[\/\\]$/, "")
      }

      if (lstat(target).isSymbolicLink() &&
          normalizePath(readlink(target)) === normalizePath(source)) {
        // If the target already points to the desired source, we don't
        // need to do anything.
        return;
      }
      // overwrite existing link, file, or directory
      rm_recursive(target);
      symlink(...args);
    } else {
      throw e;
    }
  }
})

/**
 * Get every path in a directory recursively, treating symlinks as files
 * @param  {String} dir     The directory to walk, either relative to options.cwd or completely absolute
 * @param  {Object} options Some options
 * @param {String} options.cwd The directory that paths should be relative to
 * @param {String[]} options.output An array to push results to
 * @return {String[]}         All of the paths in the directory recursively
 */
export function getPathsInDir(dir: string, options: {
  cwd?: string;
  output?: string[];
}) {
  // Don't let this function yield so that the file system doesn't get changed
  // underneath us
  return fiberHelpers.noYieldsAllowed(function () {
    var cwd = options.cwd || convertToStandardPath(process.cwd());

    if (! exists(cwd)) {
      throw new Error("Specified current working directory doesn't exist: " +
        cwd);
    }

    const absoluteDir = pathResolve(cwd, dir);

    if (! exists(absoluteDir)) {
      // There are no paths in this dir, so don't do anything
      return;
    }

    const output = options.output || [];

    function pathIsDirectory(path: string) {
      var stat = lstat(path);
      return stat.isDirectory();
    }

    readdir(absoluteDir).forEach(entry => {
      const newPath = pathJoin(dir, entry);
      const newAbsPath = pathJoin(absoluteDir, entry);

      output.push(newPath);

      if (pathIsDirectory(newAbsPath)) {
        getPathsInDir(newPath, {
          cwd: cwd,
          output: output
        });
      }
    });

    return output;
  });
}

export function findPathsWithRegex(
  dir: string,
  regex: RegExp,
  options: {
    cwd: string;
  },
) {
  return getPathsInDir(dir, {
    cwd: options.cwd
  }).filter(function (path: string) {
    return path.match(regex);
  });
}

// Make a temporary directory. Returns the path to the newly created
// directory. Only the current user is allowed to read or write the
// files in the directory (or add files to it). The directory will
// be cleaned up on exit.
const tempDirs = Object.create(null);
export function mkdtemp(prefix: string): string {
  function make(): string {
    prefix = prefix || 'mt-';
    // find /tmp
    let tmpDir: string | undefined;
    ['TMPDIR', 'TMP', 'TEMP'].some(t => {
      const value = process.env[t];
      if (value) {
        tmpDir = value;
        return true;
      }
    });

    if (! tmpDir && process.platform !== 'win32') {
      tmpDir = '/tmp';
    }

    if (! tmpDir) {
      throw new Error("Couldn't create a temporary directory.");
    }

    tmpDir = realpath(tmpDir);

    // make the directory. give it 3 tries in case of collisions from
    // crappy random.
    var tries = 3;
    while (tries > 0) {
      const dirPath = pathJoin(
        tmpDir,
        prefix + (Math.random() * 0x100000000 + 1).toString(36),
      );
      try {
        mkdir(dirPath, 0o700);
        return dirPath;
      } catch (err) {
        tries--;
      }
    }
    throw new Error("failed to make temporary directory in " + tmpDir);
  };
  const dir = make();
  tempDirs[dir] = true;
  return dir;
}

// Call this if you're done using a temporary directory. It will asynchronously
// be deleted.
export function freeTempDir(dir: string) {
  if (! tempDirs[dir]) {
    throw Error("not a tracked temp dir: " + dir);
  }

  if (process.env.METEOR_SAVE_TMPDIRS) {
    return;
  }

  return rm_recursive_async(dir).then(() => {
    // Delete tempDirs[dir] only when the removal finishes, so that the
    // cleanup.onExit handler can attempt the removal synchronously if it
    // fires in the meantime.
    delete tempDirs[dir];
  }, error => {
    // Leave tempDirs[dir] in place so the cleanup.onExit handler can try
    // to delete it again when the process exits.
    console.log(error);
  });
}

// Change the status of a dir
export function changeTempDirStatus(dir: string, status: boolean) {
  if (! tempDirs[dir]) {
    throw Error("not a tracked temp dir: " + dir);
  }

  tempDirs[dir] = status;
}

if (! process.env.METEOR_SAVE_TMPDIRS) {
  cleanup.onExit(function () {
    Object.entries(tempDirs).filter(([_, isTmp]) => !!isTmp).map(([dir]) => dir).forEach(dir => {
      delete tempDirs[dir];
      try {
        rm_recursive(dir);
      } catch (err) {
        // Don't crash and print a stack trace because we failed to delete
        // a temp directory. This happens sometimes on Windows and seems
        // to be unavoidable.
      }
    });
  });
}

type TarOptions = {
  verbose?: boolean;
  forceConvert?: boolean;
}

// Takes a buffer containing `.tar.gz` data and extracts the archive
// into a destination directory. destPath should not exist yet, and
// the archive should contain a single top-level directory, which will
// be renamed atomically to destPath.
export function extractTarGz(
  buffer: Buffer,
  destPath: string,
  options: TarOptions = {},
) {
  const parentDir = pathDirname(destPath);
  const tempDir = pathJoin(parentDir, '.tmp' + utils.randomToken());
  mkdir_p(tempDir);

  if (! hasOwnProperty.call(options, "verbose")) {
    options.verbose = require("../console/console.js").Console.verbose;
  }

  const startTime = +new Date;

  let promise = process.platform === "win32"
    ? tryExtractWithNative7z(buffer, tempDir, options)
    : tryExtractWithNativeTar(buffer, tempDir, options)

  promise = promise.catch(
    () => tryExtractWithNpmTar(buffer, tempDir, options)
  );

  promise.await();

  // succeed!
  const topLevelOfArchive = readdir(tempDir)
    // On Windows, the 7z.exe tool sometimes creates an auxiliary
    // PaxHeader directory.
    .filter(file => ! file.startsWith("PaxHeader"));

  if (topLevelOfArchive.length !== 1) {
    throw new Error(
      "Extracted archive '" + tempDir + "' should only contain one entry");
  }

  const extractDir = pathJoin(tempDir, topLevelOfArchive[0]);
  rename(extractDir, destPath);
  rm_recursive(tempDir);

  if (options.verbose) {
    console.log("Finished extracting in", Date.now() - startTime, "ms");
  }
}

function ensureDirectoryEmpty(dir: string) {
  readdir(dir).forEach(file => {
    rm_recursive(pathJoin(dir, file));
  });
}

function tryExtractWithNativeTar(
  buffer: Buffer,
  tempDir: string,
  options: TarOptions = {},
) {
  ensureDirectoryEmpty(tempDir);

  if (options.forceConvert) {
    return Promise.reject(new Error(
      "Native tar cannot convert colons in package names"));
  }

  return new Promise((resolve, reject) => {
    const flags = options.verbose ? "-xzvf" : "-xzf";
    const tarProc = spawn("tar", [flags, "-"], {
      cwd: convertToOSPath(tempDir),
      stdio: options.verbose ? [
        "pipe", // Always need to write to tarProc.stdin.
        process.stdout,
        process.stderr
      ] : "pipe",
    });

    tarProc.on("error", reject);
    tarProc.on("exit", resolve);

    if (tarProc.stdin) {
      tarProc.stdin.write(buffer);
      tarProc.stdin.end();
    }
  });
}

function tryExtractWithNative7z(
  buffer: Buffer,
  tempDir: string,
  options: TarOptions = {},
) {
  ensureDirectoryEmpty(tempDir);

  if (options.forceConvert) {
    return Promise.reject(new Error(
      "Native 7z.exe cannot convert colons in package names"));
  }

  const exeOSPath = convertToOSPath(pathJoin(getCurrentNodeBinDir(), "7z.exe"));
  const tarGzBasename = "out.tar.gz";
  const spawnOptions = {
    cwd: convertToOSPath(tempDir),
    stdio: (options.verbose ? "inherit" : "pipe") as ("inherit" | "pipe"),
  };

  writeFile(pathJoin(tempDir, tarGzBasename), buffer);

  return new Promise((resolve, reject) => {
    spawn(exeOSPath, [
      "x", "-y", tarGzBasename
    ], spawnOptions)
      .on("error", reject)
      .on("exit", resolve);

  }).then(code => {
    assert.strictEqual(code, 0);

    let tarBasename: string;
    const foundTar = readdir(tempDir).some(file => {
      if (file !== tarGzBasename) {
        tarBasename = file;
        return true;
      }
    });

    assert.ok(foundTar, "failed to find .tar file");

    function cleanUp() {
      unlink(pathJoin(tempDir, tarGzBasename));
      unlink(pathJoin(tempDir, tarBasename));
    }

    return new Promise((resolve, reject) => {
      spawn(exeOSPath, [
        "x", "-y", tarBasename
      ], spawnOptions)
        .on("error", reject)
        .on("exit", resolve);

    }).then(code => {
      cleanUp();
      return code;
    }, error => {
      cleanUp();
      throw error;
    });
  });
}

function tryExtractWithNpmTar(
  buffer: Buffer,
  tempDir: string,
  options: TarOptions = {},
) {
  ensureDirectoryEmpty(tempDir);

  const tar = require("tar");
  const zlib = require("zlib");

  return new Promise((resolve, reject) => {
    const gunzip = zlib.createGunzip().on('error', reject);
    const extractor = new tar.Extract({
      path: convertToOSPath(tempDir)
    }).on('entry', function (e: any) {
      if (process.platform === "win32" || options.forceConvert) {
        // On Windows, try to convert old packages that have colons in
        // paths by blindly replacing all of the paths. Otherwise, we
        // can't even extract the tarball
        e.path = colonConverter.convert(e.path);
      }
    }).on('error', reject)
      .on('end', resolve);

    // write the buffer to the (gunzip|untar) pipeline; these calls
    // cause the tar to be extracted to disk.
    gunzip.pipe(extractor);
    gunzip.write(buffer);
    gunzip.end();
  });
}

// In the same fashion as node-pre-gyp does, add the executable
// bit but only if the read bit was present.  Same as:
// https://github.com/mapbox/node-pre-gyp/blob/7a28f4b0f562ba4712722fefe4eeffb7b20fbf7a/lib/install.js#L71-L77
// and others reported in: https://github.com/npm/node-tar/issues/7
function addExecBitWhenReadBitPresent(fileMode: number) {
  return fileMode |= (fileMode >>> 2) & 0o111;
}

// Tar-gzips a directory, returning a stream that can then be piped as
// needed.  The tar archive will contain a top-level directory named
// after dirPath.
export function createTarGzStream(dirPath: string) {
  const tar = require("tar");
  const fstream = require('fstream');
  const zlib = require("zlib");

  // Create a segment of the file path which we will look for to
  // identify exactly what we think is a "bin" file (that is, something
  // which should be expected to work within the context of an
  // 'npm run-script').
  const binPathMatch = ["", "node_modules", ".bin", ""].join(path.sep);

  // Don't use `{ path: dirPath, type: 'Directory' }` as an argument to
  // fstream.Reader. This triggers a collection of odd behaviors in fstream
  // (which might be bugs or might just be weirdnesses).
  //
  // First, if we pass an object with `type: 'Directory'` as an argument, then
  // the resulting tarball has no entry for the top-level directory, because
  // the reader emits an entry (with just the path, no permissions or other
  // properties) before the pipe to gzip is even set up, so that entry gets
  // lost. Even if we pause the streams until all the pipes are set up, we'll
  // get the entry in the tarball for the top-level directory without
  // permissions or other properties, which is problematic. Just passing
  // `dirPath` appears to cause `fstream` to stat the directory before emitting
  // an entry for it, so the pipes are set up by the time the entry is emitted,
  // and the entry has all the right permissions, etc. from statting it.
  //
  // The second weird behavior is that we need an entry for the top-level
  // directory in the tarball to untar it with npm `tar`. (GNU tar, in
  // contrast, appears to have no problems untarring tarballs without entries
  // for the top-level directory inside them.) The problem is that, without an
  // entry for the top-level directory, `fstream` will create the directory
  // with the same permissions as the first file inside it. This manifests as
  // an EACCESS when untarring if the first file inside the top-level directory
  // is not writeable.
  const fileStream = fstream.Reader({
    path: convertToOSPath(dirPath),
    filter(entry: any) {
      if (process.platform !== "win32") {
        return true;
      }

      // Refuse to create a directory that isn't listable. Tarballs
      // created on Windows will have non-executable directories (since
      // executable isn't a thing in Windows directory permissions), and
      // so the resulting extracted directories will not be listable on
      // Linux/Mac unless we explicitly make them executable. We think
      // this should really be an option that you pass to node tar, but
      // setting it in an 'entry' handler is the same strategy that npm
      // does, so we do that here too.
      if (entry.type === "Directory") {
        entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode);
      }

      // In a similar way as for directories, but only if is in a path
      // location that is expected to be executable (npm "bin" links)
      if (entry.type === "File" && entry.path.indexOf(binPathMatch) > -1) {
        entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode);
      }

      return true;
    }
  });

  return fileStream.pipe(tar.Pack({
    noProprietary: true,
  })).pipe(zlib.createGzip());
}

// Tar-gzips a directory into a tarball on disk, synchronously.
// The tar archive will contain a top-level directory named after dirPath.
export const createTarball = Profile(function (_: string, tarball: string) {
  return "files.createTarball " + pathBasename(tarball);
}, function (dirPath: string, tarball: string) {
  const out = createWriteStream(tarball);
  new Promise(function (resolve, reject) {
    out.on('error', reject);
    out.on('close', resolve);
    createTarGzStream(dirPath).pipe(out);
  }).await();
});

// Use this if you'd like to replace a directory with another
// directory as close to atomically as possible. It's better than
// recursively deleting the target directory first and then
// renaming. (Failure modes here include "there's a brief moment where
// toDir does not exist" and "you can end up with garbage directories
// sitting around", but not "there's any time where toDir exists but
// is in a state other than initial or final".)
export const renameDirAlmostAtomically =
Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => {
  const garbageDir = pathJoin(
    pathDirname(toDir),
    // Begin the base filename with a '.' character so that it can be
    // ignored by other directory-scanning code.
    `.${pathBasename(toDir)}-garbage-${utils.randomToken()}`,
  );

  // Get old dir out of the way, if it exists.
  let cleanupGarbage = false;
  let forceCopy = false;
  try {
    rename(toDir, garbageDir);
    cleanupGarbage = true;
  } catch (e) {
    if (e.code === 'EXDEV') {
      // Some (notably Docker) file systems will fail to do a seemingly
      // harmless operation, such as renaming, on what is apparently the same
      // file system.  AUFS will do this even if the `fromDir` and `toDir`
      // are on the same layer, and OverlayFS will fail if the `fromDir` and
      // `toDir` are on different layers.  In these cases, we will not be
      // atomic and will need to do a recursive copy.
      forceCopy = true;
    } else if (e.code !== 'ENOENT') {
      // No such file or directory is okay, but anything else is not.
      throw e;
    }
  }

  if (! forceCopy) {
    try {
      rename(fromDir, toDir);
    } catch (e) {
      // It's possible that there may not have been a `toDir` to have
      // advanced warning about this, so we're prepared to handle it again.
      if (e.code === 'EXDEV') {
        forceCopy = true;
      } else {
        throw e;
      }
    }
  }

  // If we've been forced to jeopardize our atomicity due to file-system
  // limitations, we'll resort to copying.
  if (forceCopy) {
    rm_recursive(toDir);
    cp_r(fromDir, toDir, {
      preserveSymlinks: true,
    });
  }

  // ... and take out the trash.
  if (cleanupGarbage) {
    // We don't care about how long this takes, so we'll let it go async.
    rm_recursive_async(garbageDir);
  }
});

export const writeFileAtomically =
Profile("files.writeFileAtomically", function (filename: string, contents: string | Buffer) {
  const parentDir = pathDirname(filename);
  mkdir_p(parentDir);

  const tmpFile = pathJoin(
    parentDir,
    '.' + pathBasename(filename) + '.' + utils.randomToken()
  );

  writeFile(tmpFile, contents);
  rename(tmpFile, filename);
});

// Like fs.symlinkSync, but creates a temporay link and renames it over the
// file; this means it works even if the file already exists.
// Do not use this function on Windows, it won't work.
export function symlinkOverSync(linkText: string, file: string) {
  file = pathResolve(file);
  const tmpSymlink = pathJoin(
    pathDirname(file),
    "." + pathBasename(file) + ".tmp" + utils.randomToken());
  symlink(linkText, tmpSymlink);
  rename(tmpSymlink, file);
}

// Return the result of evaluating `code` using
// `runInThisContext`. `code` will be wrapped in a closure. You can
// pass additional values to bind in the closure in `options.symbols`,
// the keys being the symbols to bind and the values being their
// values. `options.filename` is the filename to use in exceptions
// that come from inside this code. `options.sourceMap` is an optional
// source map that represents the file.
//
// The really special thing about this function is that if a parse
// error occurs, we will raise an exception of type
// files.FancySyntaxError, from which you may read 'message', 'file',
// 'line', and 'column' attributes ... v8 is normally reluctant to
// reveal this information but will write it to stderr if you pass it
// an undocumented flag. Unforunately though node doesn't have dup2 so
// we can't intercept the write. So instead we use a completely
// different parser with a better error handling API. Ah well.  The
// underlying V8 issue is:
//   https://code.google.com/p/v8/issues/detail?id=1281
export function runJavaScript(code: string, {
  symbols = Object.create(null),
  filename = "<anonymous>",
  sourceMap,
  sourceMapRoot,
}: {
  symbols: Record<string, any>;
  filename: string;
  sourceMap?: object;
  sourceMapRoot?: string;
}) {
  return Profile.time('runJavaScript ' + filename, () => {
    const keys: string[] = [], values: any[] = [];
    // don't assume that _.keys and _.values are guaranteed to
    // enumerate in the same order
    _.each(symbols, function (value: any, name: string) {
      keys.push(name);
      values.push(value);
    });

    let stackFilename = filename;
    if (sourceMap) {
      // We want to generate an arbitrary filename that we use to associate the
      // file with its source map.
      stackFilename = "<runJavaScript-" + nextStackFilenameCounter++ + ">";
    }

    const chunks = [];
    const header = "(function(" + keys.join(',') + "){";
    chunks.push(header);
    if (sourceMap) {
      const sourcemapConsumer = Promise.await(new sourcemap.SourceMapConsumer(sourceMap));
      chunks.push(sourcemap.SourceNode.fromStringWithSourceMap(
        code, sourcemapConsumer));
      sourcemapConsumer.destroy();
    } else {
      chunks.push(code);
    }
    // \n is necessary in case final line is a //-comment
    chunks.push("\n})");

    let wrapped;
    let parsedSourceMap = null;
    if (sourceMap) {
      const results = new sourcemap.SourceNode(
        null, null, null, chunks
      ).toStringWithSourceMap({
        file: stackFilename
      });
      wrapped = results.code;
      parsedSourceMap = results.map.toJSON();
      if (sourceMapRoot) {
        // Add the specified root to any root that may be in the file.
        parsedSourceMap.sourceRoot = pathJoin(
          sourceMapRoot, parsedSourceMap.sourceRoot || '');
      }
      // source-map-support doesn't ever look at the sourcesContent field, so
      // there's no point in keeping it in memory.
      delete parsedSourceMap.sourcesContent;
      parsedSourceMaps[stackFilename] = parsedSourceMap;
    } else {
      wrapped = chunks.join('');
    };

    try {
      // See #runInThisContext
      //
      // XXX it'd be nice to runInNewContext so that the code can't mess
      // with our globals, but objects that come out of runInNewContext
      // have bizarro antimatter prototype chains and break 'instanceof
      // Array'. for now, steer clear
      //
      // Pass 'true' as third argument if we want the parse error on
      // stderr (which we don't).
      var script = require('vm').createScript(wrapped, stackFilename);
    } catch (nodeParseError) {
      if (!(nodeParseError instanceof SyntaxError)) {
        throw nodeParseError;
      }
      // Got a parse error. Unfortunately, we can't actually get the
      // location of the parse error from the SyntaxError; Node has some
      // hacky support for displaying it over stderr if you pass an
      // undocumented third argument to stackFilename, but that's not
      // what we want. See
      //    https://github.com/joyent/node/issues/3452
      // for more information. One thing to try (and in fact, what an
      // early version of this function did) is to actually fork a new
      // node to run the code and parse its output. We instead run an
      // entirely different JS parser, from the Babel project, but
      // which at least has a nice API for reporting errors.
      const { parse } = require('meteor-babel');
      try {
        parse(wrapped, { strictMode: false });
      } catch (parseError) {
        if (typeof parseError.loc !== "object") {
          throw parseError;
        }

        const err = new FancySyntaxError;
        err.message = parseError.message;

        if (parsedSourceMap) {
          // XXX this duplicates code in computeGlobalReferences
          var consumer2 = Promise.await(new sourcemap.SourceMapConsumer(parsedSourceMap));
          var original = consumer2.originalPositionFor(parseError.loc);
          consumer2.destroy();
          if (original.source) {
            err.file = original.source;
            err.line = original.line;
            err.column = original.column;
            throw err;
          }
        }

        err.file = filename;  // *not* stackFilename
        err.line = parseError.loc.line;
        err.column = parseError.loc.column;

        // adjust errors on line 1 to account for our header
        if (err.line === 1 && typeof err.column === "number") {
          err.column -= header.length;
        }

        throw err;
      }

      // What? Node thought that this was a parse error and Babel didn't?
      // Eh, just throw Node's error and don't care too much about the line
      // numbers being right.
      throw nodeParseError;
    }

    return buildmessage.markBoundary(
      script.runInThisContext()
    ).apply(null, values);
  });
}

// - message: an error message from the parser
// - file: filename
// - line: 1-based
// - column: 1-based
export class FancySyntaxError {
  public file?: string;
  public line?: number;
  public column?: number;
  constructor(public message?: string) {}
}

export class OfflineError {
  constructor(public error: Error) {}
  toString() {
    return "[Offline: " + this.error.toString() + "]";
  }
}

// Like files.readdir, but skips entries whose names begin with dots, and
// converts ENOENT to [].
export function readdirNoDots(path: string) {
  try {
    var entries = readdir(path);
  } catch (e) {
    if (e.code === 'ENOENT') {
      return [];
    }
    throw e;
  }
  return entries.filter(entry => {
    return entry && entry[0] !== '.';
  });
}

// Read a file in line by line. Returns an array of lines to be
// processed individually. Throws if the file doesn't exist or if
// anything else goes wrong.
export function getLines(file: string) {
  var buffer = readFile(file);
  var lines = exports.splitBufferToLines(buffer);

  // strip blank lines at the end
  while (lines.length) {
    var line = lines[lines.length - 1];
    if (line.match(/\S/)) {
      break;
    }
    lines.pop();
  }

  return lines;
}

export function splitBufferToLines(buffer: Buffer) {
  return buffer.toString('utf8').split(/\r*\n\r*/);
}

// Same as `getLines`, but returns [] if the file doesn't exist.
export function getLinesOrEmpty(file: string) {
  try {
    return getLines(file);
  } catch (e) {
    if (e && e.code === 'ENOENT') {
      return [];
    }
    throw e;
  }
}

// Returns null if the file does not exist, otherwise returns the parsed JSON in
// the file. Throws on errors other than ENOENT (including JSON parse failure).
export function readJSONOrNull(file: string) {
  try {
    var raw = readFile(file, 'utf8');
  } catch (e) {
    if (e && e.code === 'ENOENT') {
      return null;
    }
    throw e;
  }
  return JSON.parse(raw);
}

// Trims whitespace & other filler characters of a line in a project file.
export function trimSpaceAndComments(line: string) {
  var match = line.match(/^([^#]*)#/);
  if (match) {
    line = match[1];
  }
  return trimSpace(line);
}

// Trims leading and trailing whilespace in a project file.
export function trimSpace(line: string) {
  return line.replace(/^\s+|\s+$/g, '');
}

export class KeyValueFile {
  constructor(public path: string) {}

  set(k: string, v: any) {
    const data = (this.readAll() || '').toString("utf8");
    const lines = data.split(/\n/);

    let found = false;
    for (let i = 0; i < lines.length; i++) {
      const trimmed = lines[i].trim();
      if (trimmed.indexOf(k + '=') == 0) {
        lines[i] = k + '=' + v;
        found = true;
      }
    }
    if (!found) {
      lines.push(k + "=" + v);
    }
    const newdata = lines.join('\n') + '\n';
    writeFile(this.path, newdata, 'utf8');
  }

  private readAll() {
    if (exists(this.path)) {
      return readFile(this.path, 'utf8');
    } else {
      return null;
    }
  }
}

export function getHomeDir() {
  if (process.platform === "win32") {
    const MI = process.env.METEOR_INSTALLATION;
    if (typeof MI === "string") {
      return pathDirname(convertToStandardPath(MI));
    }
  }
  return process.env.HOME;
}

export function currentEnvWithPathsAdded(...paths: string[]) {
  const env = {...process.env};

  let pathPropertyName;
  if (process.platform === "win32") {
    // process.env allows for case insensitive access on Windows, but copying it
    // creates a normal JavaScript object with case sensitive property access.
    // This leads to problems, because we would be adding a PATH property instead
    // of setting Path for instance.
    // We want to make sure we're setting the right property, so we
    // lookup the property name case insensitively ourselves.
    pathPropertyName = _.find(Object.keys(env), (key: string) => {
      return key.toUpperCase() === 'PATH';
    });
    if (!pathPropertyName) {
      pathPropertyName = 'Path';
    }
  } else {
    pathPropertyName = 'PATH';
  }

  const convertedPaths = paths.map(path => convertToOSPath(path));
  let pathDecomposed = (env[pathPropertyName] || "").split(pathOsDelimiter);
  pathDecomposed.unshift(...convertedPaths);

  env[pathPropertyName] = pathDecomposed.join(pathOsDelimiter);
  return env;
}

// add .bat extension to link file if not present
function ensureBatExtension(p: string) {
  return p.endsWith(".bat") ? p : p + ".bat";
}

// Windows-only, generates a bat script that calls the destination bat script
export function _generateScriptLinkToMeteorScript(scriptLocation: string) {
  const scriptLocationIsAbsolutePath = scriptLocation.match(/^\//);
  const scriptLocationConverted = scriptLocationIsAbsolutePath
    ? convertToWindowsPath(scriptLocation)
    : "%~dp0\\" + convertToWindowsPath(scriptLocation);

  return [
    "@echo off",
    "SETLOCAL",
    "SET METEOR_INSTALLATION=%~dp0%",

    // always convert to Windows path since this function can also be
    // called on Linux or Mac when we are building bootstrap tarballs
    "\"" + scriptLocationConverted + "\" %*",
    "ENDLOCAL",

    // always exit with the same exit code as the child script
    "EXIT /b %ERRORLEVEL%",

    // add a comment with the destination of the link, so it can be read later
    // by files.readLinkToMeteorScript
    "rem " + scriptLocationConverted,
  ].join(os.EOL);
}

export function _getLocationFromScriptLinkToMeteorScript(script: string | Buffer) {
  const lines = _.compact(script.toString().split('\n'));

  let scriptLocation = _.last(lines).replace(/^rem /g, '');
  let isAbsolute = true;

  if (scriptLocation.match(/^%~dp0/)) {
    isAbsolute = false;
    scriptLocation = scriptLocation.replace(/^%~dp0\\?/g, '');
  }

  if (! scriptLocation) {
    throw new Error('Failed to parse script location from meteor.bat');
  }

  return convertToPosixPath(scriptLocation, ! isAbsolute);
}

export function linkToMeteorScript(
  scriptLocation: string,
  linkLocation: string,
  platform: string,
) {
  platform = platform || process.platform;

  if (platform === 'win32') {
    // Make a meteor batch script that points to current tool
    linkLocation = ensureBatExtension(linkLocation);
    scriptLocation = ensureBatExtension(scriptLocation);
    const script = _generateScriptLinkToMeteorScript(scriptLocation);
    writeFile(linkLocation, script, { encoding: "ascii" });
  } else {
    // Symlink meteor tool
    symlinkOverSync(scriptLocation, linkLocation);
  }
}

export function readLinkToMeteorScript(
  linkLocation: string,
  platform = process.platform,
) {
  if (platform === 'win32') {
    linkLocation = ensureBatExtension(linkLocation);
    const script = readFile(linkLocation);
    return _getLocationFromScriptLinkToMeteorScript(script);
  } else {
    return readlink(linkLocation);
  }
}

// The fs.exists method is deprecated in Node v4:
// https://nodejs.org/api/fs.html#fs_fs_exists_path_callback
export function exists(path: string) {
  return !! statOrNull(path);
}

export function readBufferWithLengthAndOffset(
  filename: string,
  length: number,
  offset: number,
) {
  const data = Buffer.alloc(length);
  // Read the data from disk, if it is non-empty. Avoid doing IO for empty
  // files, because (a) unnecessary and (b) fs.readSync with length 0
  // throws instead of acting like POSIX read:
  // https://github.com/joyent/node/issues/5685
  if (length > 0) {
    const fd = open(filename, "r");
    try {
      const count = read(fd, data, { position: 0, length, offset });
      if (count !== length) {
        throw new Error("couldn't read entire resource");
      }
    } finally {
      close(fd);
    }
  }
  return data;
}

// Summary of cross platform file system handling strategy

// There are three main pain points for handling files on Windows: slashes in
// paths, line endings in text files, and colons/invalid characters in paths.

// 1. Slashes in file paths

//   We have decided to store all paths inside the tool as unix-style paths in
//   the style of CYGWIN. This means that all paths have forward slashes on all
//   platforms, and C:\ is converted to /c/ on Windows.

//   All of the methods in files.js know how to convert from these unixy paths
//   to whatever type of path the underlying system prefers.

//   The reason we chose this strategy because it was easier to make sure to use
//   files.js everywhere instead of node's fs than to make sure every part of
//   the tool correctly uses system-specific path separators. In addition, there
//   are some parts of the tool where it is very hard to tell which strings are
//   used as URLs and which are used as file paths. In some cases, a string can
//   be used as both, meaning it has to have forward slashes no matter what.

// 2. Line endings in text files

//   We have decided to convert all files read by the tool to Unix-style line
//   endings for the same reasons as slashes above. In many parts of the tool,
//   we assume that '\n' is the line separator, and it can be hard to find all
//   of the places and decide whether it is appropriate to use os.EOL. We do not
//   convert anything on write. We will wait and see if anyone complains.

// 3. Colons and other invalid characters in file paths

//   This is not handled automatically by files.js. You need to be careful to
//   escape any colons in package names, etc, before using a string as a file
//   path.

//   A helpful file to import for this purpose is colon-converter.js, which also
//   knows how to convert various configuration file formats.

type wrapFsFuncOptions<TArgs extends any[], TResult> = {
  cached?: boolean;
  modifyReturnValue?: (result: TResult) => any;
  dirty?: (...args: TArgs) => any;
}

function wrapFsFunc<TArgs extends any[], TResult>(
  fnName: string,
  fn: (...args: TArgs) => TResult,
  pathArgIndices: number[],
  options?: wrapFsFuncOptions<TArgs, TResult>,
): typeof fn {
  return Profile("files." + fnName, function (...args: TArgs) {
    for (let j = pathArgIndices.length - 1; j >= 0; --j) {
      const i = pathArgIndices[j];
      args[i] = convertToOSPath(args[i]);
    }

    let cacheKey: string | null = null;
    if (options && options.cached) {
      const cache = withCacheSlot.getValue();
      if (cache) {
        const strings = [fnName];
        const allStrings = args.every(arg => {
          if (typeof arg === "string") {
            strings.push(arg);
            return true;
          }
          return false;
        });
        if (allStrings) {
          cacheKey = JSON.stringify(strings);
          if (hasOwnProperty.call(cache, cacheKey)) {
            return cache[cacheKey];
          }
        }
      }
    }

    const result = fn.apply(fs, args);

    if (options && options.dirty) {
      options.dirty(...args);
    }

    const finalResult = options && options.modifyReturnValue
      ? options.modifyReturnValue(result)
      : result;

    if (cacheKey) {
      withCacheSlot.getValue()![cacheKey] = finalResult;
    }

    return finalResult;
  });
}

const withCacheSlot = new Slot<Record<string, any>>();
export function withCache<R>(fn: () => R): R {
  const cache = withCacheSlot.getValue();
  return cache ? fn() : withCacheSlot.withValue(Object.create(null), fn);
}

export const dependOnPath = dep<string>();

function wrapDestructiveFsFunc<TArgs extends any[], TResult>(
  fnName: string,
  fn: (...args: TArgs) => TResult,
  pathArgIndices: number[] = [0],
  options?: wrapFsFuncOptions<TArgs, TResult>,
): typeof fn {
  return wrapFsFunc<TArgs, TResult>(fnName, fn, pathArgIndices, {
    ...options,
    dirty(...args: TArgs) {
      pathArgIndices.forEach(i => dependOnPath.dirty(args[i]));
    }
  });
}

export const readFile = wrapFsFunc("readFile", fs.readFileSync, [0], {
  modifyReturnValue: function (fileData: Buffer | string) {
    if (typeof fileData === "string") {
      return convertToStandardLineEndings(fileData);
    }
    return fileData;
  }
});

// Copies a file, which is expected to exist. Parent directories of "to" do not
// have to exist. Treats symbolic links transparently (copies the contents, not
// the link itself, and it's an error if the link doesn't point to a file).
const wrappedCopyFile = wrapDestructiveFsFunc("copyFile", fs.copyFileSync, [0, 1]);
export function copyFile(from: string, to: string, flags = 0) {
  mkdir_p(pathDirname(pathResolve(to)), 0o755);
  wrappedCopyFile(from, to, flags);
  const stat = statOrNull(from);
  if (stat && stat.isFile()) {
    // Create the file as readable and writable by everyone, and executable by
    // everyone if the original file is executably by owner. (This mode will be
    // modified by umask.) We don't copy the mode *directly* because this function
    // is used by 'meteor create' which is copying from the read-only tools tree
    // into a writable app.
    chmod(to, (stat.mode & 0o100) ? 0o777 : 0o666);
  }
}

const wrappedRename = wrapDestructiveFsFunc("rename", fs.renameSync, [0, 1]);
export const rename = isWindowsLikeFilesystem() ? function (from: string, to: string) {
  // Retries are necessary only on Windows, because the rename call can
  // fail with EBUSY, which means the file is in use.
  const osTo = convertToOSPath(to);
  const startTimeMs = Date.now();
  const intervalMs = 50;
  const timeLimitMs = 1000;

  return new Promise<void>((resolve, reject) => {
    function attempt() {
      try {
        // Despite previous failures, the top-level destination directory
        // may have been successfully created, so we must remove it to
        // avoid moving the source file *into* the destination directory.
        rimraf.sync(osTo);
        wrappedRename(from, to);
        resolve();
      } catch (err) {
        if (err.code !== 'EPERM' && err.code !== 'EACCES') {
          reject(err);
        } else if (Date.now() - startTimeMs < timeLimitMs) {
          setTimeout(attempt, intervalMs);
        } else {
          reject(err);
        }
      }
    }
    attempt();
  }).catch(error => {
    if (error.code === 'EPERM' ||
        error.code === 'EACCESS') {
      cp_r(from, to, { preserveSymlinks: true });
      rm_recursive(from);
    } else {
      throw error;
    }
  }).await();
} : wrappedRename;

// Warning: doesn't convert slashes in the second 'cache' arg
export const realpath =
wrapFsFunc<[string], string>("realpath", fs.realpathSync, [0], {
  cached: true,
  modifyReturnValue: convertToStandardPath,
});

export const readdir =
wrapFsFunc<[string], string[]>("readdir", fs.readdirSync, [0], {
  cached: true,
  modifyReturnValue(entries: string[]) {
    return entries.map(entry => convertToStandardPath(entry));
  },
});

export const readdirWithTypes = wrapFsFunc<[string], Dirent[]>("readdirWithTypes", (dir) => {
    return fs.readdirSync(dir, {
      withFileTypes: true
    });
  }, [0], {
  cached: true
});

export const appendFile = wrapDestructiveFsFunc("appendFile", fs.appendFileSync);
export const chmod = wrapDestructiveFsFunc("chmod", fs.chmodSync);
export const close = wrapFsFunc("close", fs.closeSync, []);
export const createReadStream = wrapFsFunc("createReadStream", fs.createReadStream, [0]);
export const createWriteStream = wrapFsFunc("createWriteStream", fs.createWriteStream, [0]);
export const lstat = wrapFsFunc("lstat", fs.lstatSync, [0], { cached: true });
export const mkdir = wrapDestructiveFsFunc("mkdir", fs.mkdirSync);
export const open = wrapFsFunc("open", fs.openSync, [0]);
export const read = wrapFsFunc("read", fs.readSync, []);
export const readlink = wrapFsFunc<[string], string>("readlink", fs.readlinkSync, [0]);
export const rmdir = wrapDestructiveFsFunc("rmdir", fs.rmdirSync);
export const stat = wrapFsFunc("stat", fs.statSync as (path: PathLike) => Stats, [0], { cached: true });
export const symlink = wrapFsFunc("symlink", fs.symlinkSync, [0, 1]);
export const unlink = wrapDestructiveFsFunc("unlink", fs.unlinkSync);
export const write = wrapFsFunc("write", fs.writeSync, []);
export const writeFile = wrapDestructiveFsFunc("writeFile", fs.writeFileSync);

type StatListener = (
  current: Stats,
  previous: Stats,
) => void;

type StatWatcherOptions = {
  persistent?: boolean;
  interval?: number;
};

interface StatWatcher extends EventEmitter {
  stop: () => void;
  start: (
    filename: string,
    options: StatWatcherOptions,
    listener: StatListener,
  ) => void;
}

export const watchFile = wrapFsFunc("watchFile", (
  filename: string,
  options: StatWatcherOptions,
  listener: StatListener,
) => {
  return fs.watchFile(
    filename,
    options,
    listener,
  ) as any as StatWatcher;
}, [0]);

export const unwatchFile = wrapFsFunc("unwatchFile", (
  filename: string,
  listener: StatListener,
) => {
  return fs.unwatchFile(filename, listener);
}, [0]);


Package.onUse(function(api) {
  api.use('first-unordered', {unordered: true});
});


// everyone if the original file is executably by owner. (This mode will be

// modified by umask.) We don't copy the mode *directly* because this function

// is used by 'meteor create' which is copying from the read-only tools tree


var _ = require('underscore');
var Fiber = require('fibers');
var files = require('../fs/files');
var runLog = require('./run-log.js');
var utils = require('../utils/utils.js');

// options: runner url browserId xunitOutputFile
var Selenium = function (options) {
  var self = this;
  options = options || {};

  self.driver = null;
  self.server = null;

  self.browserId = options.browserId || 'xunit';
  self.url = options.url || 'http://localhost:3000/' + self.browserId;
  self.xunitOutputFile = options.xunitOutputFile || 'test-results.xml';

  self.runner = options.runner;
  self.browser = options.browser || 'chrome';

  self.xunitLines = null;
};

// The magic prefix for special log output
// Must match packages/test-in-console/driver.js
var MAGIC_PREFIX = '##_meteor_magic##';

// For some reason, we can't see the console.log output
// unless we 'flush' it by sending another console.log via execute()
// Note that this is actually a magic message, so we get it echoed back to us;
// that's not necessary for this to work, but it keeps the output clean for users.
var DUMMY_FLUSH = MAGIC_PREFIX + "flush: flush";

_.extend(Selenium.prototype, {
  // Start the selenium server, block (yield) until it is ready to go
  // (actively listening on outer and proxying to inner), and then
  // return.
  start: function () {
    var self = this;

    if (self.server) {
      throw new Error("already running?");
    }

    self.xunitLines = [];

    var webdriver = require('selenium-webdriver');

    var capabilities;
    var loggingPrefs;
    if (self.browser === 'chrome') {
      capabilities = webdriver.Capabilities.chrome();
      loggingPrefs = {'browser': 'ALL'};
    } else if (self.browser === 'firefox') {
      capabilities = webdriver.Capabilities.firefox();
      loggingPrefs = {'browser': 'ALL'};
    } else {
      throw new Error("Unhandled browser: " + self.browser);
    }

    if (loggingPrefs) {
      capabilities = capabilities.set('loggingPrefs', loggingPrefs);
    }

    var builder = new webdriver.Builder().withCapabilities(capabilities);
    self.driver = builder.build();

    Promise.await(self.driver.getSession());
    Promise.await(self.driver.get(self.url));

    Fiber(function () {
      try {
        self._pollLogs();
      } catch (err) {
        runLog.log("Log polling exited unexpectedly: " + err);
      }
    }).run();
  },

  stop: function () {
    var self = this;

    if (! self.driver) {
      return;
    }

    Promise.await(self.driver.close());
    Promise.await(self.driver.quit());

    self.driver = null;
  },

  _flushLogs: function () {
    var self = this;
    Promise.await(
      self.driver.executeScript("console.log('" + DUMMY_FLUSH + "');", [])
    );
  },

  _getLogs: function () {
    var self = this;

    Promise.await(self.driver.manage().logs().get('browser'));
  },

  _gotStateDone: function () {
    var self = this;

    if (self.xunitOutputFile) {
      runLog.log("Writing xunit output to: " + self.xunitOutputFile);
      files.writeFile(self.xunitOutputFile, self.xunitLines.join('\n'));
    }

    if (self.runner) {
      runLog.log("Shutting down in response to 'done' state");
      self.runner.stop();
      process.exit(0);
    }
  },

  _gotState: function (state) {
    var self = this;

    runLog.log("State -> " + state);

    if (state === "done") {
      self._gotStateDone();
    }
  },

  _gotMagicLog: function (facility, msg) {
    var self = this;

    if (facility === 'xunit') {
      self.xunitLines.push(msg);
    } else if (facility === 'state') {
      self._gotState(msg);
    } else if (facility === 'flush') {
      // Ignore
    } else {
      runLog.log("Unknown magic: " + facility + ": " + msg);
    }
  },

  _pollLogsOnce: function () {
    var self = this;

    self._flushLogs();
    var logs = self._getLogs();
    _.each(logs, function (log) {
      var msg = log.message;
      var regex = /([^\s]*)\s*([^\s]*)\s*(.*)/i;
      var match = regex.exec(msg);
      if (!match) {
        runLog.log("Unknown console.log message format: " + JSON.stringify(log));
        return;
      }
      msg = match[3];
      if (msg === DUMMY_FLUSH) {
        return;
      }
      if (msg.indexOf(MAGIC_PREFIX) === 0) {
        msg = msg.substring(MAGIC_PREFIX.length);
        var colonIndex = msg.indexOf(': ');
        if (colonIndex === -1) {
          self._gotMagicLog('', msg);
        } else {
          var facility = msg.substring(0, colonIndex);
          msg = msg.substring(colonIndex + 2);
          self._gotMagicLog(facility, msg);
        }
      } else {
        runLog.log(msg);
      }
    });
  },

  _pollLogs: function () {
    var self = this;

    while (self.driver) {
      try {
        self._pollLogsOnce();
      } catch (err) {
        runLog.log("Error reading console log: " + err);
      }
      utils.sleepMs(1000);
    }
  },
});

exports.Selenium = Selenium;


# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

px4xdo1uz1w7npyqxfr


import { FSWatcher, Stats, BigIntStats } from "fs";

import {
  isString,
  isObject,
  has,
} from "underscore";

import { matches as archMatches, isLegacyArch } from "../utils/archinfo";
import {
  pathJoin,
  pathRelative,
  pathNormalize,
  pathDirname,
  pathBasename,
  convertToOSPath,
  convertToPosixPath,
} from "../fs/files";
import { Stats } from "fs";
import { wrap } from "optimism";
import {
  optimisticStatOrNull,
  optimisticReadJsonOrNull,
} from "../fs/optimistic";

const nativeModulesMap: Record<string, string> = Object.create(null);
const nativeNames = require('module').builtinModules;

nativeNames.forEach((id: string) => {
  // When a native Node module is imported, we register a dependency on a
  // meteor-node-stubs/deps/* module of the same name, so that the
  // necessary stub modules will be included in the bundle. This alternate
  // identifier will not be imported at runtime, but the modules it
  // depends on are necessary for the original import to succeed.
  nativeModulesMap[id] =  "meteor-node-stubs/deps/" + id;
});

export type ResolverOptions = {
  sourceRoot: string;
  targetArch: string;
  extensions: string[];
  nodeModulesPaths: string[];
  caller?: string;
}

export type Resolution = {
  stat: Stats;
  path: string;
  packageJsonMap?: Record<string, Record<string, any>>;
  id?: string;
} | "missing" | null

export default class Resolver {
  static getOrCreate = wrap(function (options: ResolverOptions) {
    return new Resolver(options);
  }, {
    makeCacheKey(options) {
      return JSON.stringify(options);
    }
  });

  private sourceRoot: string;
  private targetArch: string;
  private extensions: string[];
  private nodeModulesPaths: string[];
  private mainFields: string[];

  public statOrNull = optimisticStatOrNull as (path: string) => Stats | null;

  constructor({
    sourceRoot,
    targetArch,
    extensions = [".js", ".json"],
    nodeModulesPaths = [],
  }: ResolverOptions) {
    this.sourceRoot = sourceRoot;
    this.extensions = extensions;
    this.targetArch = targetArch;
    this.nodeModulesPaths = nodeModulesPaths;
    this.statOrNull = optimisticStatOrNull;

    const { resolve } = this;
    this.resolve = wrap((id, absParentPath) => {
      return resolve.call(this, id, absParentPath);
    }, {
      makeCacheKey(id, absParentPath) {
        // Only the directory of the absParentPath matters for caching.
        return JSON.stringify([id, pathDirname(absParentPath)]);
      }
    });

    const {
      findPkgJsonSubsetForPath,
      getPkgJsonSubsetForDir,
    } = this;

    this.findPkgJsonSubsetForPath = wrap(
      path => findPkgJsonSubsetForPath.call(this, path));

    this.getPkgJsonSubsetForDir = wrap(
      path => getPkgJsonSubsetForDir.call(this, path));

    if (archMatches(this.targetArch, "web")) {
      if (isLegacyArch(this.targetArch)) {
        // The legacy bundle prefers the "main" field over the "module"
        // field, since many npm packages ship modern syntax other than
        // import/export in their "module" dependency trees.
        this.mainFields = ["browser", "main", "module"];
      } else {
        this.mainFields = ["browser", "module", "main"];
      }
    } else {
      this.mainFields = ["main"];
    }
  }

  static isTopLevel(id: string) {
    return "./".indexOf(id.charAt(0)) < 0;
  }

  static isNative(id: string): boolean {
    return has(nativeModulesMap, id);
  }

  static getNativeStubId(id: string) {
    return nativeModulesMap[id] || null;
  }

  // Resolve the given module identifier to an object { path, stat } or
  // null, relative to an absolute parent path. The _seenDirPaths
  // parameter is for internal use only and should be ommitted.
  public resolve(
    id: string,
    absParentPath: string,
    _seenDirPaths?: Set<string>,
  ): Resolution {
    let resolved =
      this.resolveAbsolute(id, absParentPath) ||
      this.resolveRelative(id, absParentPath) ||
      this.resolveNodeModule(id, absParentPath);

    if (resolved === "missing") {
      // The _resolveNodeModule method can return "missing" to indicate
      // that the ImportScanner should look elsewhere for this module,
      // such as in the app node_modules directory.
      return resolved;
    }

    let packageJsonMap = null;

    while (resolved && resolved.stat && resolved.stat.isDirectory()) {
      let dirPath = resolved.path;
      _seenDirPaths = _seenDirPaths || new Set;

      // If the "main" field of a package.json file resolves to a
      // directory we've already considered, then we should not attempt to
      // read the same package.json file again.
      if (! _seenDirPaths.has(dirPath)) {
        _seenDirPaths.add(dirPath);

        const found = this.getPkgJsonSubsetForDir(dirPath);
        const foundPkgJsonMain = found && this.mainFields.some(name => {
          const value = found.pkg[name];
          if (isString(value)) {
            // The "main" field of package.json does not have to begin with ./
            // to be considered relative, so first we try simply appending it
            // to the directory path before falling back to a full resolve,
            // which might return a package from a node_modules directory.
            resolved = this.joinAndStat(dirPath, value) ||
              this.resolve(value, found.path, _seenDirPaths);
            return resolved && typeof resolved === "object";
          }
          return false;
        });

        if (foundPkgJsonMain && found) {
          if (! resolved.packageJsonMap) {
            resolved.packageJsonMap = Object.create(null);
          }

          resolved.packageJsonMap![found.path] = found.pkg;

          // The resolution above may have returned a directory, so we
          // merge resolved.packageJsonMap into packageJsonMap so that we
          // don't forget the package.json we just resolved, then continue
          // the loop to make sure we fully resolve the "main" module
          // identifier to a non-directory.  Technically this could
          // involve even more package.json files, but in practice the
          // "main" property will almost always name a directory
          // containing an index.js file.
          Object.assign(
            packageJsonMap || (packageJsonMap = Object.create(null)),
            resolved.packageJsonMap,
          );

          continue;
        }

        // Include the package.json stub in the bundle even if it was not
        // used to resolve the "main" entry point, per this comment:
        // https://github.com/meteor/meteor/issues/9235#issuecomment-340562285
        if (found) {
          packageJsonMap = packageJsonMap || Object.create(null);
          packageJsonMap[found.path] = found.pkg;
        }
      }

      // If we didn't find a `package.json` file, or it didn't have a
      // resolvable `.main` property, the only possibility left to
      // consider is that this directory contains an `index.js` module.
      // This assignment almost always terminates the while loop, because
      // there's very little chance an `index.js` file will be a
      // directory. However, in principle it is remotely possible that a
      // file called `index.js` could be a directory instead of a file.
      resolved = this.joinAndStat(dirPath, "index");
    }

    if (resolved) {
      if (packageJsonMap) {
        resolved.packageJsonMap = packageJsonMap;
      }

      // If the package.json file that governs resolved.path has a
      // "browser" field, include it in resolved.packageJsonMap so that
      // the ImportScanner can register the appropriate browser aliases.
      const pkgJsonInfo = this.findPkgJsonSubsetForPath(resolved.path);
      if (pkgJsonInfo &&
          isObject(pkgJsonInfo.pkg.browser)) {
        if (! resolved.packageJsonMap) {
          resolved.packageJsonMap = Object.create(null);
        }
        resolved.packageJsonMap![pkgJsonInfo.path] = pkgJsonInfo.pkg;
      }

      resolved.id = convertToPosixPath(
        convertToOSPath(resolved.path),
        true
      );
    }

    return resolved;
  }

  public joinAndStat(...joinArgs: string[]) {
    const joined: string = pathJoin(...joinArgs);
    const path = pathNormalize(joined);
    const exactStat = this.statOrNull(path);
    const exactResult = exactStat && { path, stat: exactStat };

    let result: Resolution = null;

    if (exactResult && exactStat && exactStat.isFile()) {
      result = exactResult;
    } else {
      // No point in trying alternate file extensions if the parent
      // directory does not exist.
      const parentDirStat = this.statOrNull(pathDirname(path));
      if (parentDirStat &&
          parentDirStat.isDirectory()) {
        this.extensions.some(ext => {
          const pathWithExt = path + ext;
          const stat = this.statOrNull(pathWithExt);
          if (stat && ! stat.isDirectory()) {
            return result = { path: pathWithExt, stat };
          }
        });
      }
    }

    if (! result && exactResult && exactStat && exactStat.isDirectory()) {
      // After trying all available file extensions, fall back to the
      // original result if it was a directory.
      result = exactResult;
    }

    return result;
  }

  private resolveAbsolute(id: string, _absParentPath: string): Resolution {
    return id.charAt(0) === "/"
      && this.joinAndStat(this.sourceRoot, id.slice(1))
      || null;
  }

  private resolveRelative(id: string, absParentPath: string): Resolution {
    if (id.charAt(0) === ".") {
      return this.joinAndStat(absParentPath, "..", id);
    }
    return null;
  }

  private resolveNodeModule(id: string, absParentPath: string): Resolution {
    if (! Resolver.isTopLevel(id)) {
      return null;
    }

    if (Resolver.isNative(id) &&
        archMatches(this.targetArch, "os")) {
      // Forbid installing any server module with the same name as a
      // native Node module.
      return null;
    }

    let sourceRoot: string | undefined;
    const relParentPath = pathRelative(this.sourceRoot, absParentPath);
    if (! relParentPath.startsWith("..")) {
      // If the file is contained by this.sourceRoot, then it's safe to
      // use this.sourceRoot as the limiting ancestor directory in the
      // while loop below, but we're still going to check whether the file
      // resides in an external node_modules directory, since "external"
      // .npm/package/node_modules directories are technically contained
      // within the root directory of their packages.
      sourceRoot = this.sourceRoot;
    }

    this.nodeModulesPaths.some(path => {
      if (! pathRelative(path, absParentPath).startsWith("..")) {
        // If the file is inside an external node_modules directory,
        // consider the rootDir to be the parent directory of that
        // node_modules directory, rather than this.sourceRoot.
        return sourceRoot = pathDirname(path);
      }
    });

    let resolved = null;

    if (sourceRoot) {
      let dir = absParentPath; // It's ok for absParentPath to be a directory!
      let dirStat = this.statOrNull(dir);
      if (! (dirStat && dirStat.isDirectory())) {
        dir = pathDirname(dir);
      }

      while (! (resolved = this.joinAndStat(dir, "node_modules", id))) {
        if (dir === sourceRoot) {
          break;
        }

        const parentDir = pathDirname(dir);
        if (dir === parentDir) {
          // We've reached the root of the file system??
          break;
        }

        dir = parentDir;
      }
    }

    if (! resolved) {
      // After checking any local node_modules directories, fall back to
      // the package NPM directory, if one was specified.
      this.nodeModulesPaths.some(path => {
        return resolved = this.joinAndStat(path, id);
      });
    }

    // If the dependency is still not resolved, it might be handled by the
    // fallback function defined in meteor/packages/modules-runtime/[modern|legacy|server].js, or
    // it might be imported in code that will never run on this platform,
    // so there is always the possibility that its absence is not actually
    // a problem. As much as we might like to issue warnings about missing
    // dependencies here, we just don't have enough information to make
    // that determination until the code actually runs.

    return resolved || "missing";
  }

  private getPkgJsonSubsetForDir(dirPath: string) {
    const pkgJsonPath = pathJoin(dirPath, "package.json");
    const pkg = optimisticReadJsonOrNull(pkgJsonPath);
    if (! pkg) {
      return null;
    }

    // Output a JS module that exports just the "name", "version", "main",
    // and "browser" properties (if defined) from the package.json file.
    const pkgSubset: Partial<typeof pkg> = {};

    if (has(pkg, "name")) {
      pkgSubset.name = pkg.name;
    }

    if (has(pkg, "version")) {
      pkgSubset.version = pkg.version;
    }

    this.mainFields.forEach(name => {
      const value = pkg[name];
      if (isString(value) ||
          isObject(value)) {
        pkgSubset[name] = value;
      }
    });

    return {
      path: pkgJsonPath,
      pkg: pkgSubset,
    };
  }

  private findPkgJsonSubsetForPath(
    path: string,
  ): ReturnType<Resolver["getPkgJsonSubsetForDir"]> {
    const stat = this.statOrNull(path);

    if (stat && stat.isDirectory()) {
      const found = this.getPkgJsonSubsetForDir(path);
      if (found) {
        return found;
      }

      if (path === this.sourceRoot) {
        return null;
      }
    }

    const parentDir = pathDirname(path);

    if (parentDir === path) {
      return null;
    }

    if (pathBasename(parentDir) === "node_modules") {
      return null;
    }

    return this.findPkgJsonSubsetForPath(parentDir);
  }
};

import { Profile } from "../tool-env/profile";
const Rp = Resolver.prototype as any;
Object.keys(Rp).forEach(key => {
  if (key === "constructor") return;
  Rp[key] = Profile(`Resolver#${key}`, Rp[key]);
});


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

DDP Rate Limiter package
===

A rate limiter added directly to DDP that provides an API to add rules to
Meteor methods and collections.

For example usage, check out
[the docs](http://docs.meteor.com/api/methods.html#ddpratelimiter).

### Pre-defined Defaults

If the `accounts-base` package is added to your
project, there are default rules added to limit logins, new user registration
and password resets calls to a limit of 5 requests per 10 seconds per
connection.
These provide a basic solution to dictionary attacks where a malicious user
attempts to guess the passwords of legitimate users by attempting all possible
passwords. To remove the default rule, a user can add
`Accounts.removeDefaultRateLimit()` to any server side code and the default
rate limit will be removed.

### Configuration

The `DDPRateLimiter` is configured with a set of rules. Each rule is a set of
keys to be inspected with filters on those keys to specify all DDP messages
that satisfy the rule. Each of these possible messages that satisfy the rule
is given a bucket by creating a unique string composed of all the keys in the
rule and the values from the message. After each rule's specified time
interval, all the buckets are deleted. A rate limit is said to have been hit
when a bucket has reached the rule's capacity, at which point errors will be
returned for that input until the buckets are reset.

A rule is defined as a set of key-value pairs where the keys are one or more
of `userId`, `clientAddress`, `type`, `name`, and `connectionId`. The values
can either be null, primitives or functions. When you want to rate limit some
users but not others, a rule can match invocations using a function in a way
that is determined at run time based on the database or other data. In our
example, we check the database to avoid rate limiting admin users.

When we add the rule to DDPRateLimiter, we also specify the number of messages
that we allow and the time interval on which the rate limit is reset.


# QA Notes
## Hot Code Push Reload

Run the leaderboard example, and click on one of the names.  Make a
change to the leaderboard.html file, see the client reload, and see
that the name is still selected.

## AUTOUPDATE_VERSION

Set the `AUTOUPDATE_VERSION` environment variable when running the
application:

    $ AUTOUPDATE_VERSION=abc meteor

Now when you make an HTML change, it won't appear in the client
automatically.  (Note the leader list flickers when the server
subscription restarts, but that's not a window reload).

Conversely, you can force a client reload (even without making any
client code changes) by restarting the server with a new value for
`AUTOUPDATE_VERSION`.

## No Client Reload on Server-only Change

Revert previous changes and run the example without setting
AUTOUPDATE_VERSION.

Note that it might look like the browser is reloading because the page
content in the leaderboard example will flicker when the server
restarts because the example is using autopublish, but that the window
won't actually be reloading.

In the browser console, assign a variable such as `a = true` so that
you can easily verify that the client hasn't reloaded.

In the leaderboard example directory, create the `server` directory
and add `foo.js`.  See in the browser console that `a` is still
defined, indicating the browser hasn't reloaded.

## Test with the appcache

Add the appcache package:

    $ meteor add appcache

And do the above tests again.

Note that if 1) AUTOUPDATE_VERSION is set so the client doesn't
automatically reload, 2) you make a client change, and 3) you manually
reload the browser page, you usually *won't* see the updated HTML the
*first* time you reload (unless the browser happened to check the app
cache manifest between steps 2 and 3).  This is normal browser app
cache behavior: the browser populates the app cache in the background,
so it doesn't wait for new files to download before displaying the web
page.

## Autoupdate.newClientAvailable

Undo previous changes made, such as by using `git checkout .` Reload
the client, which will cause the browser to stop using the app cache.

It's hard to see the `newClientAvailable` reactive variable when the
client automatically reloads.  Remove the `hot-code-push` package so you can
see the variable without having the client also reload.

    $ meteor remove meteor-base
    $ meteor add meteor webapp ddp autoupdate

Add to leaderboard.js:

    Template.leaderboard.helpers({
      available: function () {
        return Autoupdate.newClientAvailable().toString();
      }
    });

And add `{{available}}` to the leaderboard template in
leaderboard.html.

Initially you'll see `false`, and then when you make a change to the
leaderboard HTML you'll see the variable change to `true`.  (You won't
see the new HTML on the client because you disabled reload).

Amusingly, you can undo the addition you made to the HTML and the "new
client available" variable will go back to `false` (you now don't have
client code available on the server different than what's running in
the browser), because by default the client version is based on a hash
of the client files.

## DDP Version Negotiation Failure

A quick way to test DDP version negotiation failure is to force the
client to use the wrong DDP version.  At the top of
livedata_connection.js:

     var Connection = function (url, options) {
        var self = this;
    +   options.supportedDDPVersions = ['abc'];

You will see the client reload (in the hope that new client code will
be available that can successfully negotiation the DDP version).  Each
reload takes longer than the one before, using an exponential backoff.

If you remove the `options.supportedDDPVersions` line and allow the
client to connect (or manually reload the browser page so you don't
have to wait), this will reset the exponential backoff counter.

You can verify the counter was reset by adding the line back in a
second time, and you'll see the reload cycle start over again with
first reloading quickly, and then again taking longer between tries.


import './home.html';

import '../../components/hello/hello.js';
import '../../components/info/info.js';


function startNewWatcher(absPath: string): Entry {

if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) {

keep keep keep keep replace keep keep keep keep

let stat: Stats | BigIntStats | null = null;


This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.

You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.


Package.describe({
  name: 'extra-package-2',
  version: '0.0.2',
});

Package.onUse(function(api) {
  api.use('ecmascript');
  api.mainModule('extra-package-2.js');
});

Package.onTest(function (api) {
  api.use('ecmascript');
  api.use('tinytest');
  api.use('extra-package-2');
  api.mainModule('extra-package-2-tests.js');
});


import { Stats, FSWatcher, Dirent } from "fs";

import * as safeWatcher from "./safe-watcher";

import { coalesce } from "../utils/func-utils";

import { Stats, BigIntStats, FSWatcher, Dirent } from "fs";


$el2-style = solid
@import "../imports/dotdot.styl"


if (Package['accounts-ui']
    && !Package['service-configuration']
    && !Object.prototype.hasOwnProperty.call(Package, 'google-config-ui')) {
  console.warn(
    "Note: You're using accounts-ui and accounts-google,\n" +
    "but didn't install the configuration UI for the Google\n" +
    "OAuth. You can install it with:\n" +
    "\n" +
    "    meteor add google-config-ui" +
    "\n"
  );
}


// Add slashes to the end of directories.

const contentsWithSlashes: string[] = [];

let stat: Dirent | Stats | null = entry;

// We do stat instead of lstat here, so that we treat symlinks to

keep keep keep replace keep keep keep keep

let stat: Dirent | Stats | BigIntStats | null = entry;