<commit_before>// @flow

const path = require('path'),
  spawn = require('cross-spawn'),
  fs = require('fs-extra'),
  _ = require('lodash'),
  Murmur = require('murmur-hash-js'),
  Version = require('./version.js'),
  supportsColor = require('supports-color');

function prepareCompiledJsFile(pipeFilename /*: string */, dest /*: string */) {
  return Promise.all([
    readUtf8(path.join(__dirname, '..', 'templates', 'before.js')),
    readUtf8(dest),
    readUtf8(path.join(__dirname, '..', 'templates', 'after.js')),
  ]).then(([before, content, after]) => {
    return new Promise((resolve, reject) => {
      const finalContent = [
        before,
        'var Elm = (function(module) { ',
        content,
        'return this.Elm;',
        '})({});',
        'var pipeFilename = ' + JSON.stringify(pipeFilename) + ';',
        after,
      ].join('\n');
      return fs.writeFile(dest, finalContent, err => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  });
}

function readUtf8(filepath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filepath, { encoding: 'utf8' }, (err, contents) => {
      if (err) {
        reject(err);
      } else {
        resolve(contents);
      }
    });
  });
}

function generateElmJson(
  projectRootDir /*: string */,
  testRootDir /*: string */,
  pathToElmBinary /*: string */,
  filePaths /*: Array<string> */,
  hasBeenGivenCustomGlobs /*: boolean */
) {
  const generatedCodeDir = path.resolve(
    path.join(
      projectRootDir,
      'elm-stuff',
      'generated-code',
      'elm-explorations',
      'test'
    )
  );
  const elmJsonPath = path.resolve(path.join(projectRootDir, 'elm.json'));
  const generatedSrc = path.join(generatedCodeDir, 'src');

  var projectElmJson = {};

  try {
    projectElmJson = fs.readJsonSync(elmJsonPath);
  } catch (err) {
    console.error('Error reading elm.json: ' + err);
    process.exit(1);
  }
  var isPackageProject = projectElmJson.type === 'package';

  // if we were given file globs, we don't need to check the tests/ directory exists
  // this is only for elm applications, which people may need to introduce slowly into their apps
  // for packages, we stick with the existing behaviour and assume tests are in tests/ so do the check always
  const needToCareAboutTestsDir =
    hasBeenGivenCustomGlobs === false || isPackageProject === true;

  // we add the tests dir as a source if:
  // - we decided above we need to care about it
  // - if it exists on disk: this supports the case where we have all our tests in tests/ but
  //   want to pass a glob to only run one of the test files
  const shouldAddTestsDirAsSource =
    needToCareAboutTestsDir ||
    fs.existsSync(path.join(projectRootDir, 'tests'));

  if (needToCareAboutTestsDir) {
    if (!fs.existsSync(testRootDir)) {
      console.error(
        'Error: ' +
          testRootDir +
          ' does not exist. Please create a tests/ directory in your project root!'
      );
      process.exit(1);
    }

    if (!fs.lstatSync(testRootDir).isDirectory()) {
      console.error(
        'Error: ' +
          testRootDir +
          ' exists, but it is not a directory. Please create a tests/ directory in your project root!'
      );
      process.exit(1);
    }
  }

  fs.mkdirpSync(generatedCodeDir);
  fs.mkdirpSync(generatedSrc);

  var testElmJson = {
    type: 'application',
    'source-directories': [], // these are added below
    'elm-version': '0.19.1',
    dependencies: {
      direct: {},
      indirect: {},
    },
    'test-dependencies': {
      direct: {},
      indirect: {},
    },
  };

  var nodeTestRunnerElmJsonPath = path.resolve(
    path.join(__dirname, '..', 'elm', 'elm.json')
  );
  var nodeTestRunnerElmJson = fs.readJsonSync(nodeTestRunnerElmJsonPath);
  // we want to use the version of elm-explorations/test that the user
  // specifies in their own test-dependencies. everything else needs to
  // be included for the test runner to compile.
  delete nodeTestRunnerElmJson.dependencies.direct['elm-explorations/test'];
  addDirectDependencies(
    nodeTestRunnerElmJson['dependencies']['direct'],
    isPackageProject,
    testElmJson
  );
  addIndirectDependencies(
    nodeTestRunnerElmJson['dependencies']['indirect'],
    testElmJson
  );

  if (isPackageProject) {
    addDirectDependencies(
      projectElmJson['dependencies'],
      isPackageProject,
      testElmJson
    );
    addDirectDependencies(
      projectElmJson['test-dependencies'],
      isPackageProject,
      testElmJson
    );
    // package projects don't explicitly list their transitive dependencies,
    // to we have to figure out what they are.  We write the elm.json that
    // we have so far, and run elm to see what it thinks is missing.
    fs.writeFileSync(
      path.join(generatedCodeDir, 'elm.json'),
      JSON.stringify(testElmJson, null, 4)
    );
    var missingDeps = askElmForMissingTransitiveDependencies(
      pathToElmBinary,
      generatedCodeDir
    );
    addIndirectDependencies(missingDeps, testElmJson);
  } else {
    addDirectDependencies(
      projectElmJson['dependencies']['direct'],
      isPackageProject,
      testElmJson
    );
    addIndirectDependencies(
      projectElmJson['dependencies']['indirect'],
      testElmJson
    );
    addDirectDependencies(
      projectElmJson['test-dependencies']['direct'],
      isPackageProject,
      testElmJson
    );
    addIndirectDependencies(
      projectElmJson['test-dependencies']['indirect'],
      testElmJson
    );
  }

  // Make all the source-directories absolute, and introduce a new one.
  var projectSourceDirs;
  if (isPackageProject) {
    projectSourceDirs = ['./src'];
  } else {
    projectSourceDirs = projectElmJson['source-directories'];
  }
  var sourceDirs /*: Array<string> */ = projectSourceDirs
    .map(function(src) {
      return path.resolve(path.join(projectRootDir, src));
    })
    .concat(shouldAddTestsDirAsSource ? [testRootDir] : []);

  testElmJson['source-directories'] = [
    // Include elm-stuff/generated-sources - since we'll be generating sources in there.
    generatedSrc,

    // NOTE: we must include node-test-runner's Elm source as a source-directory
    // instead of adding it as a dependency so that it can include port modules
    path.resolve(path.join(__dirname, '..', 'elm', 'src')),
  ]
  .concat(sourceDirs)
  .filter(
    // When running node-test-runner's own test suite, the node-test-runner/src folder
    // will get added twice: once because it's the source-directory of the packge being tested,
    // and once because elm-test will always add it.
    // To prevent elm from being confused, we need to remove the duplicate when this happens.
    function(value, index, self) {
      return self.indexOf(value) === index;
    }
  ).map(function(absolutePath) {
    // These all need to be relative paths. Otherwise, there's a bug in elm make
    // (as of 0.19.1-alpha4) where certain .elmi files wouldn't get generated.
    //
    // SSCCE: https://gist.github.com/rtfeldman/c0a068794b2e36d350c00357f458c50f
    //
    // Relative paths also have the nice benefit that if the user moves their
    // directory, this doesn't break.
    return path.relative(generatedCodeDir, absolutePath);
  });

  // Generate the new elm.json
  fs.writeFileSync(
    path.join(generatedCodeDir, 'elm.json'),
    JSON.stringify(testElmJson, null, 4)
  );

  return [generatedCodeDir, generatedSrc, sourceDirs];
}

function addDirectDependencies(
  deps /*: Object */,
  isPackageProject /*: boolean */,
  testElmJson /*: Object */
) {
  Object.keys(deps).forEach(function(name) {
    var version = deps[name];
    if (isPackageProject) {
      // Use the lowest version in the range.
      // NOTE: technically this doesn't work if someone does something weird like:
      //
      // "2.0.0 < v < 3.0.0"
      //
      // ...but we're choosing not to support that right now.
      version = version.split(' ')[0];
    }
    if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
      var existingVersion = testElmJson['dependencies']['direct'][name];

      // If we have a clash, choose the higher of the two versions.
      // This may not work! It's entirely possible that the result won't
      // compile. We're going to try it and see what happens.
      version = Version.getHigherVersion(version, existingVersion);
    }
    testElmJson['dependencies']['direct'][name] = version;
  });
}

function addIndirectDependencies(deps, testElmJson /*: Object */) {
  Object.keys(deps).forEach(function(name) {
    if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
      // already a normal dep
    } else {
      var version = deps[name];

      if (testElmJson['dependencies']['indirect'].hasOwnProperty(name)) {
        var existingVersion = testElmJson['dependencies']['indirect'][name];

        // If we have a clash, choose the higher of the two versions.
        // This may not work! It's entirely possible that the result won't
        // compile. We're going to try it and see what happens.
        version = Version.getHigherVersion(version, existingVersion);
      }

      testElmJson['dependencies']['indirect'][name] = version;
    }
  });
}
function askElmForMissingTransitiveDependencies(
  pathToElmBinary,
  pathToElmProject
) {
  var result = spawn.sync(pathToElmBinary, ['make', '--report=json'], {
    silent: true,
    env: process.env,
    cwd: pathToElmProject,
  });

  if (result.stderr == null) {
    console.error(
      'No output received from elm make when searching for indirect dependencies.'
    );
    process.exit(1);
  }

  // TODO: hopefully the next Elm 0.19 beta will print the JSON to stdout instead
  var output = result.stderr.toString();

  // TODO: hopefully the next Elm 0.19 beta will not have this message (only occurs on linux) mixed in with the JSON output
  output = output.replace(
    /^elm:.*no version information available \(required by elm\)\n/,
    ''
  );

  var report = JSON.parse(output);
  if (report.type === 'error' && report.title === 'NO INPUT') {
    // all transtive dependencies are listed already (elm is complaining that we didn't tell it which .elm file to build, which happens after the dependency validations)
    return {};
  } else if (
    report.type === 'error' &&
    report.title === 'MISSING DEPENDENCIES'
  ) {
    var missingDeps = {};
    // parse the missing dependencies and versions from the error report
    report.message[1].string.replace(/"([^"]*)": "([^"]*)"/g, function(
      _,
      name,
      version
    ) {
      missingDeps[name] = version;
    });
    return missingDeps;
  } else {
    console.error(
      "elm-test internal error: got an unexpected result from 'elm make' when validating transitive dependencies.  Please report this at https://github.com/rtfeldman/node-test-runner/issues"
    );
    process.exit(1);
    return {}; // This makes flow happy because it doesn't know process.exit will stop everything
  }
}

function generateMainModule(
  fuzz /*: number */,
  seed /*: number */,
  report /*: string */,
  testFilePaths /*: Array<string> */,
  tests /*: Array<Object> */,
  generatedSrc /*: string */,
  processes /*: number */
) {
  // Building things like:
  //
  // import MyTests
  //
  // MyTests.suite
  const imports = _.map(tests, function(test) {
    return 'import ' + test.name;
  });
  const testList = _.map(tests, function(mod) {
    return (
      '    Test.describe "' +
      mod.name +
      '" [' +
      _.map(mod.tests, function(test) {
        return mod.name + '.' + test;
      }).join(',\n    ') +
      ']'
    );
  });

  if (testList.length === 0) {
    const errorMessage =
      testFilePaths.length > 0
        ? 'I couldn\'t find any exposed values of type Test in files matching "' +
          testFilePaths.toString() +
          '"\n\nMaybe try running elm-test with no arguments?'
        : "I couldn't find any exposed values of type Test in any *.elm files in the tests/ directory of your project's root directory.\n\nTo generate some initial tests to get things going, run elm-test init";

    console.error(errorMessage);
    process.exit(1);
  }

  function sanitizedToString(str) {
    return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
  }

  function getReportCode() {
    switch (report) {
      case 'json':
        return 'JsonReport';
      case 'junit':
        return 'JUnitReport';
      default:
        if (supportsColor) {
          return '(ConsoleReport UseColor)';
        } else {
          return '(ConsoleReport Monochrome)';
        }
    }
  }

  if (isNaN(seed)) {
    seed = Math.floor(Math.random() * 407199254740991) + 1000;
  }

  var opts = {
    fuzz: isNaN(fuzz) ? 'Nothing' : 'Just ' + fuzz,
    seed: seed,
    report: getReportCode(),
    paths: testFilePaths.map(sanitizedToString).join(','),
  };

  var optsCode =
    '{ runs = ' +
    opts.fuzz +
    ', report = ' +
    opts.report +
    ', seed = ' +
    opts.seed +
    ', processes = ' +
    processes +
    ', paths = [' +
    opts.paths +
    ']}';

  const testFileBody = [
    imports.join('\n'),
    '',
    'import Test.Reporter.Reporter exposing (Report(..))',
    'import Console.Text exposing (UseColor(..))',
    'import Test.Runner.Node',
    'import Test',
    '',
    'main : Test.Runner.Node.TestProgram',
    'main =',
    '    [ ' + testList.join(', ') + ' ]',
    '        |> Test.concat',
    '        |> Test.Runner.Node.run ' + optsCode,
  ].join('\n');

  // Generate a filename that incorporates the hash of file contents.
  // This way, if you run e.g. `elm-test Foo.elm` and then `elm-test Bar.elm`
  // and then re-run `elm-test Foo.elm` we still have a cached `Main` for
  // `Foo.elm` (assuming none of its necessary imports have changed - and
  // why would they?) so we don't have to recompile it.
  const salt = Murmur.murmur3(testFileBody);
  const moduleName = 'Main' + salt;
  const mainPath = path.join(generatedSrc, 'Test', 'Generated');
  const mainFile = path.join(mainPath, moduleName + '.elm');
  // We'll be putting the generated Main in something like this:
  //
  // my-project-name/elm-stuff/generated-code/elm-community/elm-test/src/Test/Generated/Main123456.elm
  const testFileContents = [
    'module Test.Generated.' + moduleName + ' exposing (main)',
    testFileBody,
  ].join('\n\n');

  // Make sure src/Test/Generated/ exists so we can write the file there.
  fs.mkdirpSync(mainPath);

  // Always write the file, in order to update its timestamp. This is important,
  // because if we run `elm-make Main123456.elm` and that file's timestamp did
  // not change, elm-make will short-circuit and not recompile *anything* - even
  // if some of Main's dependencies (such as an individual test file) changed.
  fs.writeFileSync(mainFile, testFileContents);

  return mainFile;
}

module.exports = { prepareCompiledJsFile, generateElmJson, generateMainModule };
<commit_msg>Include 0.19.1 in the Generate path<commit_after>// @flow

const path = require('path'),
  spawn = require('cross-spawn'),
  fs = require('fs-extra'),
  _ = require('lodash'),
  Murmur = require('murmur-hash-js'),
  Version = require('./version.js'),
  supportsColor = require('supports-color');

function prepareCompiledJsFile(pipeFilename /*: string */, dest /*: string */) {
  return Promise.all([
    readUtf8(path.join(__dirname, '..', 'templates', 'before.js')),
    readUtf8(dest),
    readUtf8(path.join(__dirname, '..', 'templates', 'after.js')),
  ]).then(([before, content, after]) => {
    return new Promise((resolve, reject) => {
      const finalContent = [
        before,
        'var Elm = (function(module) { ',
        content,
        'return this.Elm;',
        '})({});',
        'var pipeFilename = ' + JSON.stringify(pipeFilename) + ';',
        after,
      ].join('\n');
      return fs.writeFile(dest, finalContent, err => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  });
}

function readUtf8(filepath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filepath, { encoding: 'utf8' }, (err, contents) => {
      if (err) {
        reject(err);
      } else {
        resolve(contents);
      }
    });
  });
}

function generateElmJson(
  projectRootDir /*: string */,
  testRootDir /*: string */,
  pathToElmBinary /*: string */,
  filePaths /*: Array<string> */,
  hasBeenGivenCustomGlobs /*: boolean */
) {
  const generatedCodeDir = path.resolve(
    path.join(
      projectRootDir,
      'elm-stuff',
      'generated-code',
      'elm-explorations',
      'test',
      '0.19.1'
    )
  );
  const elmJsonPath = path.resolve(path.join(projectRootDir, 'elm.json'));
  const generatedSrc = path.join(generatedCodeDir, 'src');

  var projectElmJson = {};

  try {
    projectElmJson = fs.readJsonSync(elmJsonPath);
  } catch (err) {
    console.error('Error reading elm.json: ' + err);
    process.exit(1);
  }
  var isPackageProject = projectElmJson.type === 'package';

  // if we were given file globs, we don't need to check the tests/ directory exists
  // this is only for elm applications, which people may need to introduce slowly into their apps
  // for packages, we stick with the existing behaviour and assume tests are in tests/ so do the check always
  const needToCareAboutTestsDir =
    hasBeenGivenCustomGlobs === false || isPackageProject === true;

  // we add the tests dir as a source if:
  // - we decided above we need to care about it
  // - if it exists on disk: this supports the case where we have all our tests in tests/ but
  //   want to pass a glob to only run one of the test files
  const shouldAddTestsDirAsSource =
    needToCareAboutTestsDir ||
    fs.existsSync(path.join(projectRootDir, 'tests'));

  if (needToCareAboutTestsDir) {
    if (!fs.existsSync(testRootDir)) {
      console.error(
        'Error: ' +
          testRootDir +
          ' does not exist. Please create a tests/ directory in your project root!'
      );
      process.exit(1);
    }

    if (!fs.lstatSync(testRootDir).isDirectory()) {
      console.error(
        'Error: ' +
          testRootDir +
          ' exists, but it is not a directory. Please create a tests/ directory in your project root!'
      );
      process.exit(1);
    }
  }

  fs.mkdirpSync(generatedCodeDir);
  fs.mkdirpSync(generatedSrc);

  var testElmJson = {
    type: 'application',
    'source-directories': [], // these are added below
    'elm-version': '0.19.1',
    dependencies: {
      direct: {},
      indirect: {},
    },
    'test-dependencies': {
      direct: {},
      indirect: {},
    },
  };

  var nodeTestRunnerElmJsonPath = path.resolve(
    path.join(__dirname, '..', 'elm', 'elm.json')
  );
  var nodeTestRunnerElmJson = fs.readJsonSync(nodeTestRunnerElmJsonPath);
  // we want to use the version of elm-explorations/test that the user
  // specifies in their own test-dependencies. everything else needs to
  // be included for the test runner to compile.
  delete nodeTestRunnerElmJson.dependencies.direct['elm-explorations/test'];
  addDirectDependencies(
    nodeTestRunnerElmJson['dependencies']['direct'],
    isPackageProject,
    testElmJson
  );
  addIndirectDependencies(
    nodeTestRunnerElmJson['dependencies']['indirect'],
    testElmJson
  );

  if (isPackageProject) {
    addDirectDependencies(
      projectElmJson['dependencies'],
      isPackageProject,
      testElmJson
    );
    addDirectDependencies(
      projectElmJson['test-dependencies'],
      isPackageProject,
      testElmJson
    );
    // package projects don't explicitly list their transitive dependencies,
    // to we have to figure out what they are.  We write the elm.json that
    // we have so far, and run elm to see what it thinks is missing.
    fs.writeFileSync(
      path.join(generatedCodeDir, 'elm.json'),
      JSON.stringify(testElmJson, null, 4)
    );
    var missingDeps = askElmForMissingTransitiveDependencies(
      pathToElmBinary,
      generatedCodeDir
    );
    addIndirectDependencies(missingDeps, testElmJson);
  } else {
    addDirectDependencies(
      projectElmJson['dependencies']['direct'],
      isPackageProject,
      testElmJson
    );
    addIndirectDependencies(
      projectElmJson['dependencies']['indirect'],
      testElmJson
    );
    addDirectDependencies(
      projectElmJson['test-dependencies']['direct'],
      isPackageProject,
      testElmJson
    );
    addIndirectDependencies(
      projectElmJson['test-dependencies']['indirect'],
      testElmJson
    );
  }

  // Make all the source-directories absolute, and introduce a new one.
  var projectSourceDirs;
  if (isPackageProject) {
    projectSourceDirs = ['./src'];
  } else {
    projectSourceDirs = projectElmJson['source-directories'];
  }
  var sourceDirs /*: Array<string> */ = projectSourceDirs
    .map(function(src) {
      return path.resolve(path.join(projectRootDir, src));
    })
    .concat(shouldAddTestsDirAsSource ? [testRootDir] : []);

  testElmJson['source-directories'] = [
    // Include elm-stuff/generated-sources - since we'll be generating sources in there.
    generatedSrc,

    // NOTE: we must include node-test-runner's Elm source as a source-directory
    // instead of adding it as a dependency so that it can include port modules
    path.resolve(path.join(__dirname, '..', 'elm', 'src')),
  ]
  .concat(sourceDirs)
  .filter(
    // When running node-test-runner's own test suite, the node-test-runner/src folder
    // will get added twice: once because it's the source-directory of the packge being tested,
    // and once because elm-test will always add it.
    // To prevent elm from being confused, we need to remove the duplicate when this happens.
    function(value, index, self) {
      return self.indexOf(value) === index;
    }
  ).map(function(absolutePath) {
    // These all need to be relative paths. Otherwise, there's a bug in elm make
    // (as of 0.19.1-alpha4) where certain .elmi files wouldn't get generated.
    //
    // SSCCE: https://gist.github.com/rtfeldman/c0a068794b2e36d350c00357f458c50f
    //
    // Relative paths also have the nice benefit that if the user moves their
    // directory, this doesn't break.
    return path.relative(generatedCodeDir, absolutePath);
  });

  // Generate the new elm.json
  fs.writeFileSync(
    path.join(generatedCodeDir, 'elm.json'),
    JSON.stringify(testElmJson, null, 4)
  );

  return [generatedCodeDir, generatedSrc, sourceDirs];
}

function addDirectDependencies(
  deps /*: Object */,
  isPackageProject /*: boolean */,
  testElmJson /*: Object */
) {
  Object.keys(deps).forEach(function(name) {
    var version = deps[name];
    if (isPackageProject) {
      // Use the lowest version in the range.
      // NOTE: technically this doesn't work if someone does something weird like:
      //
      // "2.0.0 < v < 3.0.0"
      //
      // ...but we're choosing not to support that right now.
      version = version.split(' ')[0];
    }
    if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
      var existingVersion = testElmJson['dependencies']['direct'][name];

      // If we have a clash, choose the higher of the two versions.
      // This may not work! It's entirely possible that the result won't
      // compile. We're going to try it and see what happens.
      version = Version.getHigherVersion(version, existingVersion);
    }
    testElmJson['dependencies']['direct'][name] = version;
  });
}

function addIndirectDependencies(deps, testElmJson /*: Object */) {
  Object.keys(deps).forEach(function(name) {
    if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
      // already a normal dep
    } else {
      var version = deps[name];

      if (testElmJson['dependencies']['indirect'].hasOwnProperty(name)) {
        var existingVersion = testElmJson['dependencies']['indirect'][name];

        // If we have a clash, choose the higher of the two versions.
        // This may not work! It's entirely possible that the result won't
        // compile. We're going to try it and see what happens.
        version = Version.getHigherVersion(version, existingVersion);
      }

      testElmJson['dependencies']['indirect'][name] = version;
    }
  });
}
function askElmForMissingTransitiveDependencies(
  pathToElmBinary,
  pathToElmProject
) {
  var result = spawn.sync(pathToElmBinary, ['make', '--report=json'], {
    silent: true,
    env: process.env,
    cwd: pathToElmProject,
  });

  if (result.stderr == null) {
    console.error(
      'No output received from elm make when searching for indirect dependencies.'
    );
    process.exit(1);
  }

  // TODO: hopefully the next Elm 0.19 beta will print the JSON to stdout instead
  var output = result.stderr.toString();

  // TODO: hopefully the next Elm 0.19 beta will not have this message (only occurs on linux) mixed in with the JSON output
  output = output.replace(
    /^elm:.*no version information available \(required by elm\)\n/,
    ''
  );

  var report = JSON.parse(output);
  if (report.type === 'error' && report.title === 'NO INPUT') {
    // all transtive dependencies are listed already (elm is complaining that we didn't tell it which .elm file to build, which happens after the dependency validations)
    return {};
  } else if (
    report.type === 'error' &&
    report.title === 'MISSING DEPENDENCIES'
  ) {
    var missingDeps = {};
    // parse the missing dependencies and versions from the error report
    report.message[1].string.replace(/"([^"]*)": "([^"]*)"/g, function(
      _,
      name,
      version
    ) {
      missingDeps[name] = version;
    });
    return missingDeps;
  } else {
    console.error(
      "elm-test internal error: got an unexpected result from 'elm make' when validating transitive dependencies.  Please report this at https://github.com/rtfeldman/node-test-runner/issues"
    );
    process.exit(1);
    return {}; // This makes flow happy because it doesn't know process.exit will stop everything
  }
}

function generateMainModule(
  fuzz /*: number */,
  seed /*: number */,
  report /*: string */,
  testFilePaths /*: Array<string> */,
  tests /*: Array<Object> */,
  generatedSrc /*: string */,
  processes /*: number */
) {
  // Building things like:
  //
  // import MyTests
  //
  // MyTests.suite
  const imports = _.map(tests, function(test) {
    return 'import ' + test.name;
  });
  const testList = _.map(tests, function(mod) {
    return (
      '    Test.describe "' +
      mod.name +
      '" [' +
      _.map(mod.tests, function(test) {
        return mod.name + '.' + test;
      }).join(',\n    ') +
      ']'
    );
  });

  if (testList.length === 0) {
    const errorMessage =
      testFilePaths.length > 0
        ? 'I couldn\'t find any exposed values of type Test in files matching "' +
          testFilePaths.toString() +
          '"\n\nMaybe try running elm-test with no arguments?'
        : "I couldn't find any exposed values of type Test in any *.elm files in the tests/ directory of your project's root directory.\n\nTo generate some initial tests to get things going, run elm-test init";

    console.error(errorMessage);
    process.exit(1);
  }

  function sanitizedToString(str) {
    return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
  }

  function getReportCode() {
    switch (report) {
      case 'json':
        return 'JsonReport';
      case 'junit':
        return 'JUnitReport';
      default:
        if (supportsColor) {
          return '(ConsoleReport UseColor)';
        } else {
          return '(ConsoleReport Monochrome)';
        }
    }
  }

  if (isNaN(seed)) {
    seed = Math.floor(Math.random() * 407199254740991) + 1000;
  }

  var opts = {
    fuzz: isNaN(fuzz) ? 'Nothing' : 'Just ' + fuzz,
    seed: seed,
    report: getReportCode(),
    paths: testFilePaths.map(sanitizedToString).join(','),
  };

  var optsCode =
    '{ runs = ' +
    opts.fuzz +
    ', report = ' +
    opts.report +
    ', seed = ' +
    opts.seed +
    ', processes = ' +
    processes +
    ', paths = [' +
    opts.paths +
    ']}';

  const testFileBody = [
    imports.join('\n'),
    '',
    'import Test.Reporter.Reporter exposing (Report(..))',
    'import Console.Text exposing (UseColor(..))',
    'import Test.Runner.Node',
    'import Test',
    '',
    'main : Test.Runner.Node.TestProgram',
    'main =',
    '    [ ' + testList.join(', ') + ' ]',
    '        |> Test.concat',
    '        |> Test.Runner.Node.run ' + optsCode,
  ].join('\n');

  // Generate a filename that incorporates the hash of file contents.
  // This way, if you run e.g. `elm-test Foo.elm` and then `elm-test Bar.elm`
  // and then re-run `elm-test Foo.elm` we still have a cached `Main` for
  // `Foo.elm` (assuming none of its necessary imports have changed - and
  // why would they?) so we don't have to recompile it.
  const salt = Murmur.murmur3(testFileBody);
  const moduleName = 'Main' + salt;
  const mainPath = path.join(generatedSrc, 'Test', 'Generated');
  const mainFile = path.join(mainPath, moduleName + '.elm');
  // We'll be putting the generated Main in something like this:
  //
  // my-project-name/elm-stuff/generated-code/elm-community/elm-test/src/Test/Generated/Main123456.elm
  const testFileContents = [
    'module Test.Generated.' + moduleName + ' exposing (main)',
    testFileBody,
  ].join('\n\n');

  // Make sure src/Test/Generated/ exists so we can write the file there.
  fs.mkdirpSync(mainPath);

  // Always write the file, in order to update its timestamp. This is important,
  // because if we run `elm-make Main123456.elm` and that file's timestamp did
  // not change, elm-make will short-circuit and not recompile *anything* - even
  // if some of Main's dependencies (such as an individual test file) changed.
  fs.writeFileSync(mainFile, testFileContents);

  return mainFile;
}

module.exports = { prepareCompiledJsFile, generateElmJson, generateMainModule };
<|endoftext|>


<commit_before>'use strict';

const rr = require('neat-rr');
const send = require('./send.js');
const {
  extend,
  filter,
  parseTarget,
  dftHealthCheckFn,
  findHostItem,
} = require('./utils.js');
const DEFAULT_CYCLE = 5000;

// opts, ext is common;
// ext.rr is individual opt;
// send_opts, send_ext is dynamic;

module.exports = class LBClient {
  constructor(opts = {}, ext = {}) {
    // 1. parse url
    if (typeof opts === 'string') {
      if (!opts.startsWith('http')) throw new Error('If opts if represent url, it must be absolute.');
      opts = parseTarget(opts);
    }
    // 2. filter ext
    this.ext = filter(ext, ['rr', 'healthCheckOpts', 'healthCheckFn', 'healthCheckCycle']);
    // 3. rr
    if (!rr || rr.length === 0) {
      throw new Error('ext.rr cannot be empty!');
    }
    this.rr = ext.rr.map(item => {
      return extend({}, item, opts);
    });
    this.ava = this.rr.map(item => Object.assign({}, item));
    this.getRR = rr(this.rr);
    // 4. HealthCheck(opts, cres) return bool
    if (ext.healthCheckOpts) {
      const healthCheckFn = ext.healthCheckFn || dftHealthCheckFn;
      const getHRR = rr(this.rr.map(item => {
        return extend({}, ext.healthCheckOpts, item);
      }));
      this.healthCheck = async() => {
        const opts = getHRR();
        const cres = await send(opts, this.ext); // two ext is different !
        const bool = healthCheckFn(cres);
        if (bool) {
          // if ava is complete, done
          if (this.ava.length === this.rr.length) return bool;
          // else add to ava, refresh getRR.
          this.ava.push(findHostItem(opts, this.rr).item);
          this.getRR = rr(this.ava);
        } else {
          // if in ava, delete in ,refresh getRR
          const index = findHostItem(opts, this.ava).index;
          if (index) {
            this.ava = this.ava.slice(0, index).concat(this.ava.slice(index + 1));
            this.getRR = rr(this.ava);
          }
        }
        return bool;
      }
      setInterval(this.healthCheck.bind(this), ((ext.healthCheckCycle || DEFAULT_CYCLE) / this.rr.length) >> 0);
    }
    // 5. send
    this.send = this.send.bind(this);
  }

  send(send_opts = {}, send_ext = {}) {
    return send(extend(send_opts, this.getRR()), extend(send_ext, this.ext));
  }
}<commit_msg>fix when check is Health, not find if in ava.<commit_after>'use strict';

const rr = require('neat-rr');
const send = require('./send.js');
const {
  extend,
  filter,
  parseTarget,
  dftHealthCheckFn,
  findHostItem,
} = require('./utils.js');
const DEFAULT_CYCLE = 5000;

// opts, ext is common;
// ext.rr is individual opt;
// send_opts, send_ext is dynamic;

module.exports = class LBClient {
  constructor(opts = {}, ext = {}) {
    // 1. parse url
    if (typeof opts === 'string') {
      if (!opts.startsWith('http')) throw new Error('If opts if represent url, it must be absolute.');
      opts = parseTarget(opts);
    }
    // 2. filter ext
    this.ext = filter(ext, ['rr', 'healthCheckOpts', 'healthCheckFn', 'healthCheckCycle']);
    // 3. rr
    if (!rr || rr.length === 0) {
      throw new Error('ext.rr cannot be empty!');
    }
    this.rr = ext.rr.map(item => {
      return extend({}, item, opts);
    });
    this.ava = this.rr.map(item => Object.assign({}, item));
    this.getRR = rr(this.rr);
    // 4. HealthCheck(opts, cres) return bool
    if (ext.healthCheckOpts) {
      const healthCheckFn = ext.healthCheckFn || dftHealthCheckFn;
      const getHRR = rr(this.rr.map(item => {
        return extend({}, ext.healthCheckOpts, item);
      }));
      this.healthCheck = async() => {
        const opts = getHRR();
        const cres = await send(opts, this.ext); // two ext is different !
        const bool = healthCheckFn(cres);
        if (bool) {
          // if ava is complete, done
          if (this.ava.length === this.rr.length) return bool;
          // else if not in ava, add it, refresh getRR.
          const index = findHostItem(opts, this.ava).index;
          if (!index) {
            this.ava.push(findHostItem(opts, this.rr).item);
            this.getRR = rr(this.ava);
          }
        } else {
          // if in ava, delete in ,refresh getRR
          const index = findHostItem(opts, this.ava).index;
          if (index) {
            this.ava = this.ava.slice(0, index).concat(this.ava.slice(index + 1));
            this.getRR = rr(this.ava);
          }
        }
        return bool;
      }
      setInterval(this.healthCheck.bind(this), ((ext.healthCheckCycle || DEFAULT_CYCLE) / this.rr.length) >> 0);
    }
    // 5. send
    this.send = this.send.bind(this);
  }

  send(send_opts = {}, send_ext = {}) {
    return send(extend(send_opts, this.getRR()), extend(send_ext, this.ext));
  }
}<|endoftext|>


<commit_before>"use strict";var Look={_setAttributes:function(t,e){for(var n in e)t.setAttribute(n,e[n])},_setContent:function(t,e){e.forEach(function(e){Look.create(e,t)})},_for:function(t,e,n){if(0!=t.length)for(var o in t)if(t[o].getAttribute(e)===n)return t[o];return!1},create:function(t,e){var n=this.newElement(t.tag);return this._setAttributes(n,t.attr),t.content&&this._setContent(n,t.content),e?e.appendChild(n):document.body.appendChild(n),n},createMany:function(t,e,n){var o=0;for(o;t>o;o++)Look.create(e,n)},copy:function(t,e){return this.create(Look._clone(t),e)},_clone:function(t){var e=t;return e.id=e.id+"_copy",e},newElement:function(t){return document.createElement(t)},getBy:{id:function(t){return document.getElementById(t)},className:function(t,e){return e?document.getElementsByClassName(t)[e]:document.getElementsByClassName(t)},tagName:function(t,e){return e?document.getElementsByTagName(t)[e]:document.getElementsByTagName(t)},href:function(t,e){return e?Look._for(document.getElementsByTagName("a"),"href",t,e):Look._for(document.getElementsByTagName("a"),"href",t)},src:function(t,e){return e?Look._for(document.getElementsByTagName("img"),"src",t,e):Look._for(document.getElementsByTagName("img"),"src",t)}},setText:{to:{oneElement:function(t,e){t.textContent=e},manyElements:function(t,e,n){var o=0;for(o;o<t.length;o++)t[o].textContent=e[o][n]},allElements:function(t,e){var n=0;for(n;n<t.length;n++)t[n].textContent=e}}},changeLanguage:function(t,e){if(e.id)for(var n in e.id)Look.setText.to.oneElement(Look.getBy.id(n),e.id[n][t]);if(e["class"])for(var n in e["class"])Look.setText.to.manyElements(Look.getBy.className(n),e["class"][n],t);if(e.tag)for(var n in e.tag)Look.setText.to.manyElements(Look.getBy.tagName(n),e.tag[n],t);if(e.allClass)for(var n in e.allClass)Look.setText.to.allElements(Look.getBy.className(n),e.allClass[n][t]);if(e.allTag)for(var n in e.allTag)Look.setText.to.allElements(Look.getBy.className(n),e.allTag[n][t])}};<commit_msg>compiled added<commit_after>"use strict";var Look={_setAttributes:function(t,e){for(var n in e)t.setAttribute(n,e[n])},_setContent:function(t,e){e.forEach(function(e){Look.create(e,t)})},_for:function(t,e,n){if(0!=t.length)for(var o in t)if(t[o].getAttribute(e)===n)return t[o];return!1},create:function(t,e){var n=this.newElement(t.tag);return this._setAttributes(n,t.attr),t.content&&this._setContent(n,t.content),e?e.appendChild(n):document.body.appendChild(n),n},createMany:function(t,e,n){var o=0;for(o;t>o;o++)Look.create(e,n)},copy:function(t,e){return this.create(Look._clone(t),e)},_clone:function(t){var e=t;return e.id=e.id+"_copy",e},newElement:function(t){return document.createElement(t)},getBy:{id:function(t){return document.getElementById(t)},className:function(t,e){return e?document.getElementsByClassName(t)[e]:document.getElementsByClassName(t)},tagName:function(t,e){return e?document.getElementsByTagName(t)[e]:document.getElementsByTagName(t)},href:function(t,e){return e?Look._for(document.getElementsByTagName("a"),"href",t,e):Look._for(document.getElementsByTagName("a"),"href",t)},src:function(t,e){return e?Look._for(document.getElementsByTagName("img"),"src",t,e):Look._for(document.getElementsByTagName("img"),"src",t)}},setText:{to:{oneElement:function(t,e){t.textContent=e},manyElements:function(t,e,n){var o=0;for(o;o<t.length;o++)t[o]&&(t[o].textContent=e[o][n])},allElements:function(t,e){var n=0;for(n;n<t.length;n++)t[n]&&(t[n].textContent=e)}}},changeLanguage:function(t,e){if(e.id)for(var n in e.id)Look.setText.to.oneElement(Look.getBy.id(n),e.id[n][t]);if(e["class"])for(var n in e["class"])Look.setText.to.manyElements(Look.getBy.className(n),e["class"][n],t);if(e.tag)for(var n in e.tag)Look.setText.to.manyElements(Look.getBy.tagName(n),e.tag[n],t);if(e.allClass)for(var n in e.allClass)Look.setText.to.allElements(Look.getBy.className(n),e.allClass[n][t]);if(e.allTag)for(var n in e.allTag)Look.setText.to.allElements(Look.getBy.className(n),e.allTag[n][t])}};<|endoftext|>


<commit_before>// A mock Date implementation.
Timecop.MockDate = function() {
  if (arguments.length > 0 || !Timecop.topOfStack()) {
    this._underlyingDate = Timecop.buildNativeDate.apply(Timecop, Array.prototype.slice.apply(arguments));
  } else {
    this._underlyingDate = Timecop.topOfStack();
  }
};

Timecop.MockDate.prototype = {
  getDate:            function() { return this._underlyingDate.getDate() },
  getDay:             function() { return this._underlyingDate.getDay()() },
  getFullYear:        function() { return this._underlyingDate.getFullYear() },
  getHours:           function() { return this._underlyingDate.getHours() },
  getMilliseconds:    function() { return this._underlyingDate.getMilliseconds() },
  getMinutes:         function() { return this._underlyingDate.getMinutes() },
  getMonth:           function() { return this._underlyingDate.getMonth() },
  getSeconds:         function() { return this._underlyingDate.getSeconds() },
  getTime:            function() { return this._underlyingDate.getTime() },
  getTimezoneOffset:  function() { return this._underlyingDate.getTimezoneOffset() },
  getUTCDate:         function() { return this._underlyingDate.getUTCDate() },
  getUTCDay:          function() { return this._underlyingDate.getUTCDay() },
  getUTCFullYear:     function() { return this._underlyingDate.getUTCFullYear() },
  getUTCHours:        function() { return this._underlyingDate.getUTCHours() },
  getUTCMilliseconds: function() { return this._underlyingDate.getUTCMilliseconds() },
  getUTCMinutes:      function() { return this._underlyingDate.getUTCMinutes() },
  getUTCMonth:        function() { return this._underlyingDate.getUTCMonth() },
  getUTCSeconds:      function() { return this._underlyingDate.getUTCSeconds() },
  getYear:            function() { return this._underlyingDate.getYear() }
};
<commit_msg>added Timecop.MockDate#toString<commit_after>// A mock Date implementation.
Timecop.MockDate = function() {
  if (arguments.length > 0 || !Timecop.topOfStack()) {
    this._underlyingDate = Timecop.buildNativeDate.apply(Timecop, Array.prototype.slice.apply(arguments));
  } else {
    this._underlyingDate = Timecop.topOfStack();
  }
};

Timecop.MockDate.prototype = {
  getDate:            function() { return this._underlyingDate.getDate() },
  getDay:             function() { return this._underlyingDate.getDay()() },
  getFullYear:        function() { return this._underlyingDate.getFullYear() },
  getHours:           function() { return this._underlyingDate.getHours() },
  getMilliseconds:    function() { return this._underlyingDate.getMilliseconds() },
  getMinutes:         function() { return this._underlyingDate.getMinutes() },
  getMonth:           function() { return this._underlyingDate.getMonth() },
  getSeconds:         function() { return this._underlyingDate.getSeconds() },
  getTime:            function() { return this._underlyingDate.getTime() },
  getTimezoneOffset:  function() { return this._underlyingDate.getTimezoneOffset() },
  getUTCDate:         function() { return this._underlyingDate.getUTCDate() },
  getUTCDay:          function() { return this._underlyingDate.getUTCDay() },
  getUTCFullYear:     function() { return this._underlyingDate.getUTCFullYear() },
  getUTCHours:        function() { return this._underlyingDate.getUTCHours() },
  getUTCMilliseconds: function() { return this._underlyingDate.getUTCMilliseconds() },
  getUTCMinutes:      function() { return this._underlyingDate.getUTCMinutes() },
  getUTCMonth:        function() { return this._underlyingDate.getUTCMonth() },
  getUTCSeconds:      function() { return this._underlyingDate.getUTCSeconds() },
  getYear:            function() { return this._underlyingDate.getYear() },
  toString:           function() { return this._underlyingDate.toString() }
};
<|endoftext|>


<commit_before>/*!
 * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('./constants');
const jsonld = require('jsonld');
const {extendContextLoader, strictDocumentLoader} = require('./documentLoader');
const {serializeError} = require('serialize-error');
const strictExpansionMap = require('./expansionMap');

module.exports = class ProofSet {
  /**
   * Adds a Linked Data proof to a document. If the document contains other
   * proofs, the new proof will be appended to the existing set of proofs.
   *
   * Important note: This method assumes that the term `proof` in the given
   * document has the same definition as the `https://w3id.org/security/v2`
   * JSON-LD @context.
   *
   * @param document {object} - JSON-LD Document to be signed.
   * @param options {object} Options hashmap.
   *
   * A `suite` option is required:
   *
   * @param options.suite {LinkedDataSignature} a signature suite instance
   *   that will create the proof.
   *
   * A `purpose` option is required:
   *
   * @param options.purpose {ProofPurpose} a proof purpose instance that will
   *   augment the proof with information describing its intended purpose.
   *
   * Advanced optional parameters and overrides:
   *
   * @param [documentLoader] {function} a custom document loader,
   *   `Promise<RemoteDocument> documentLoader(url)`.
   * @param [expansionMap] {function} A custom expansion map that is
   *   passed to the JSON-LD processor; by default a function that will throw
   *   an error when unmapped properties are detected in the input, use `false`
   *   to turn this off and allow unmapped properties to be dropped or use a
   *   custom function.
   *
   * @return {Promise<object>} resolves with the signed document, with
   *   the signature in the top-level `proof` property.
   */
  async add(document, {suite, purpose, documentLoader, expansionMap} = {}) {
    if(!suite) {
      throw new TypeError('"options.suite" is required.');
    }
    if(!purpose) {
      throw new TypeError('"options.purpose" is required.');
    }

    if(documentLoader) {
      documentLoader = extendContextLoader(documentLoader);
    } else {
      documentLoader = strictDocumentLoader;
    }
    if(expansionMap !== false) {
      expansionMap = strictExpansionMap;
    }

    // preprocess document to prepare to remove existing proofs
    // let input;
    // shallow copy document to allow removal of existing proofs
    const input = {...document};

    delete input.proof;

    // create the new proof (suites MUST output a proof using the security-v2
    // `@context`)
    const proof = await suite.createProof({
      document: input, purpose, documentLoader, expansionMap
    });

    jsonld.addValue(document, 'proof', proof);

    return document;
  }

  /**
   * Verifies Linked Data proof(s) on a document. The proofs to be verified
   * must match the given proof purpose.
   *
   * Important note: This method assumes that the term `proof` in the given
   * document has the same definition as the `https://w3id.org/security/v2`
   * JSON-LD @context.
   *
   * @param {object} document - The JSON-LD document with one or more proofs to
   *   be verified.
   *
   * @param {LinkedDataSignature|LinkedDataSignature[]} suite -
   *   Acceptable signature suite instances for verifying the proof(s).
   *
   * @param {ProofPurpose} purpose - A proof purpose instance that will
   *   match proofs to be verified and ensure they were created according to
   *   the appropriate purpose.
   *
   * Advanced optional parameters and overrides:
   *
   * @param {function} [documentLoader]  a custom document loader,
   *   `Promise<RemoteDocument> documentLoader(url)`.
   * @param {function} [expansionMap] - A custom expansion map that is
   *   passed to the JSON-LD processor; by default a function that will throw
   *   an error when unmapped properties are detected in the input, use `false`
   *   to turn this off and allow unmapped properties to be dropped or use a
   *   custom function.
   *
   * @return {Promise<{verified: boolean, results: Array, error: *}>} resolves
   *   with an object with a `verified`boolean property that is `true` if at
   *   least one proof matching the given purpose and suite verifies and `false`
   *   otherwise; a `results` property with an array of detailed results;
   *   if `false` an `error` property will be present.
   */
  async verify(document, {suite, purpose, documentLoader, expansionMap} = {}) {
    if(!suite) {
      throw new TypeError('"options.suite" is required.');
    }
    if(!purpose) {
      throw new TypeError('"options.purpose" is required.');
    }
    const suites = Array.isArray(suite) ? suite : [suite];
    if(suites.length === 0) {
      throw new TypeError('At least one suite is required.');
    }

    if(documentLoader) {
      documentLoader = extendContextLoader(documentLoader);
    } else {
      documentLoader = strictDocumentLoader;
    }
    if(expansionMap !== false) {
      expansionMap = strictExpansionMap;
    }

    try {
      // shallow copy to allow for removal of proof set prior to canonize
      document = {...document};

      // get proofs from document
      const {proofSet, document: doc} = await _getProofs({
        document, documentLoader, expansionMap
      });
      document = doc;

      // verify proofs
      const results = await _verify({
        document, suites, proofSet, purpose, documentLoader, expansionMap,
      });
      if(results.length === 0) {
        const error = new Error(
          'Did not verify any proofs; insufficient proofs matched the ' +
          'acceptable suite(s) and required purpose(s).');
        error.name = 'NotFoundError';
        throw error;
      }

      // combine results
      const verified = results.some(r => r.verified);
      if(!verified) {
        const errors = [].concat(
          ...results.filter(r => r.error).map(r => r.error));
        const result = {verified, results};
        if(errors.length > 0) {
          result.error = errors;
        }
        return result;
      }
      return {verified, results};
    } catch(error) {
      _makeSerializable(error);
      return {verified: false, error};
    }
  }
};

async function _getProofs({document}) {
  // handle document preprocessing to find proofs
  let proofSet;
  proofSet = jsonld.getValues(document, 'proof');
  delete document.proof;

  if(proofSet.length === 0) {
    // no possible matches
    throw new Error('No matching proofs found in the given document.');
  }

  // shallow copy proofs and add document context or SECURITY_CONTEXT_URL
  const context = document['@context'] || constants.SECURITY_CONTEXT_URL;
  proofSet = proofSet.map(proof => ({
    '@context': context,
    ...proof
  }));

  return {proofSet, document};
}

async function _verify({
  document, suites, proofSet, purpose, documentLoader, expansionMap
}) {
  // map each purpose to at least one proof to verify
  const purposes = Array.isArray(purpose) ? purpose : [purpose];
  const purposeToProofs = new Map();
  const proofToSuite = new Map();
  const suiteMatchQueue = new Map();
  await Promise.all(purposes.map(purpose => _matchProofSet({
    purposeToProofs, proofToSuite, purpose, proofSet, suites,
    suiteMatchQueue, document, documentLoader, expansionMap
  })));

  // every purpose must have at least one matching proof or verify will fail
  if(purposeToProofs.length < purposes.length) {
    // insufficient proofs to verify, so don't bother verifying any
    return [];
  }

  // verify every proof in `proofToSuite`; these proofs matched a purpose
  const verifyResults = new Map();
  await Promise.all([...proofToSuite.entries()].map(async ([proof, suite]) => {
    let result;
    try {
      // create backwards-compatible deferred proof purpose to capture
      // verification method from old-style suites
      let vm;
      const purpose = {
        async validate(proof, {verificationMethod}) {
          vm = verificationMethod;
          return {valid: true};
        }
      };
      const {verified, verificationMethod, error} = await suite.verifyProof({
        proof, document, purpose, documentLoader, expansionMap
      });
      if(!vm) {
        vm = verificationMethod;
      }
      result = {proof, verified, verificationMethod: vm, error};
    } catch(error) {
      result = {proof, verified: false, error};
    }

    if(result.error) {
      // ensure error is serializable
      _makeSerializable(result.error);
    }

    verifyResults.set(proof, result);
  }));

  // validate proof against each purpose that matched it
  await Promise.all([...purposeToProofs.entries()].map(
    async ([purpose, proofs]) => {
      for(const proof of proofs) {
        const result = verifyResults.get(proof);
        if(!result.verified) {
          // if proof was not verified, so not bother validating purpose
          continue;
        }

        // validate purpose
        const {verificationMethod} = result;
        const suite = proofToSuite.get(proof);
        let purposeResult;
        try {
          purposeResult = await purpose.validate(proof, {
            document, suite, verificationMethod, documentLoader, expansionMap
          });
        } catch(error) {
          purposeResult = {valid: false, error};
        }

        // add `purposeResult` to verification result regardless of validity
        // to ensure that all purposes are represented
        if(result.purposeResult) {
          if(Array.isArray(result.purposeResult)) {
            result.purposeResult.push(purposeResult);
          } else {
            result.purposeResult = [result.purposeResult, purposeResult];
          }
        } else {
          result.purposeResult = purposeResult;
        }

        if(!purposeResult.valid) {
          // ensure error is serializable
          _makeSerializable(purposeResult.error);

          // if no top level error set yet, set it
          if(!result.error) {
            result.verified = false;
            result.error = purposeResult.error;
          }
        }
      }
    }));

  return [...verifyResults.values()];
}

// add a `toJSON` method to an error which allows for errors in validation
// reports to be serialized properly by `JSON.stringify`.
function _makeSerializable(error) {
  Object.defineProperty(error, 'toJSON', {
    value: function() {
      return serializeError(this);
    },
    configurable: true,
    writable: true
  });
}

async function _matchProofSet({
  purposeToProofs, proofToSuite, purpose, proofSet, suites,
  suiteMatchQueue, document, documentLoader, expansionMap
}) {
  for(const proof of proofSet) {
    // first check if the proof matches the purpose; if it doesn't continue
    if(!await purpose.match(proof, {document, documentLoader, expansionMap})) {
      continue;
    }

    // next, find the suite that can verify the proof; if found, `matched`
    // will be set to `true` and the proof will be added to `purposeToProofs`
    // and `proofToSuite` to be processed -- otherwise it will not be; if
    // no proofs are added for a given purpose, an exception will be thrown
    let matched = false;
    for(const s of suites) {
      // `matchingProofs` is a map of promises that resolve to whether a
      // proof matches a suite; multiple purposes and suites may be checked
      // in parallel so a promise queue is used to prevent duplicate work
      let matchingProofs = suiteMatchQueue.get(s);
      if(!matchingProofs) {
        suiteMatchQueue.set(s, matchingProofs = new Map());
      }
      let promise = matchingProofs.get(proof);
      if(!promise) {
        promise = s.matchProof({proof, document, documentLoader, expansionMap});
        matchingProofs.set(proof, promise);
      }
      if(await promise) {
        // found the matching suite for the proof; there should only be one
        // suite that can verify a particular proof; add the proof to the
        // map of proofs to be verified along with the matching suite
        matched = true;
        proofToSuite.set(proof, s);
        break;
      }
    }

    if(matched) {
      // note proof was a match for the purpose and an acceptable suite; it
      // will need to be verified by the suite and then validated against the
      // purpose
      const matches = purposeToProofs.get(purpose);
      if(matches) {
        matches.push(proof);
      } else {
        purposeToProofs.set(purpose, [proof]);
      }
    }
  }
}
<commit_msg>Remove dangling comma.<commit_after>/*!
 * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('./constants');
const jsonld = require('jsonld');
const {extendContextLoader, strictDocumentLoader} = require('./documentLoader');
const {serializeError} = require('serialize-error');
const strictExpansionMap = require('./expansionMap');

module.exports = class ProofSet {
  /**
   * Adds a Linked Data proof to a document. If the document contains other
   * proofs, the new proof will be appended to the existing set of proofs.
   *
   * Important note: This method assumes that the term `proof` in the given
   * document has the same definition as the `https://w3id.org/security/v2`
   * JSON-LD @context.
   *
   * @param document {object} - JSON-LD Document to be signed.
   * @param options {object} Options hashmap.
   *
   * A `suite` option is required:
   *
   * @param options.suite {LinkedDataSignature} a signature suite instance
   *   that will create the proof.
   *
   * A `purpose` option is required:
   *
   * @param options.purpose {ProofPurpose} a proof purpose instance that will
   *   augment the proof with information describing its intended purpose.
   *
   * Advanced optional parameters and overrides:
   *
   * @param [documentLoader] {function} a custom document loader,
   *   `Promise<RemoteDocument> documentLoader(url)`.
   * @param [expansionMap] {function} A custom expansion map that is
   *   passed to the JSON-LD processor; by default a function that will throw
   *   an error when unmapped properties are detected in the input, use `false`
   *   to turn this off and allow unmapped properties to be dropped or use a
   *   custom function.
   *
   * @return {Promise<object>} resolves with the signed document, with
   *   the signature in the top-level `proof` property.
   */
  async add(document, {suite, purpose, documentLoader, expansionMap} = {}) {
    if(!suite) {
      throw new TypeError('"options.suite" is required.');
    }
    if(!purpose) {
      throw new TypeError('"options.purpose" is required.');
    }

    if(documentLoader) {
      documentLoader = extendContextLoader(documentLoader);
    } else {
      documentLoader = strictDocumentLoader;
    }
    if(expansionMap !== false) {
      expansionMap = strictExpansionMap;
    }

    // preprocess document to prepare to remove existing proofs
    // let input;
    // shallow copy document to allow removal of existing proofs
    const input = {...document};

    delete input.proof;

    // create the new proof (suites MUST output a proof using the security-v2
    // `@context`)
    const proof = await suite.createProof({
      document: input, purpose, documentLoader, expansionMap
    });

    jsonld.addValue(document, 'proof', proof);

    return document;
  }

  /**
   * Verifies Linked Data proof(s) on a document. The proofs to be verified
   * must match the given proof purpose.
   *
   * Important note: This method assumes that the term `proof` in the given
   * document has the same definition as the `https://w3id.org/security/v2`
   * JSON-LD @context.
   *
   * @param {object} document - The JSON-LD document with one or more proofs to
   *   be verified.
   *
   * @param {LinkedDataSignature|LinkedDataSignature[]} suite -
   *   Acceptable signature suite instances for verifying the proof(s).
   *
   * @param {ProofPurpose} purpose - A proof purpose instance that will
   *   match proofs to be verified and ensure they were created according to
   *   the appropriate purpose.
   *
   * Advanced optional parameters and overrides:
   *
   * @param {function} [documentLoader]  a custom document loader,
   *   `Promise<RemoteDocument> documentLoader(url)`.
   * @param {function} [expansionMap] - A custom expansion map that is
   *   passed to the JSON-LD processor; by default a function that will throw
   *   an error when unmapped properties are detected in the input, use `false`
   *   to turn this off and allow unmapped properties to be dropped or use a
   *   custom function.
   *
   * @return {Promise<{verified: boolean, results: Array, error: *}>} resolves
   *   with an object with a `verified`boolean property that is `true` if at
   *   least one proof matching the given purpose and suite verifies and `false`
   *   otherwise; a `results` property with an array of detailed results;
   *   if `false` an `error` property will be present.
   */
  async verify(document, {suite, purpose, documentLoader, expansionMap} = {}) {
    if(!suite) {
      throw new TypeError('"options.suite" is required.');
    }
    if(!purpose) {
      throw new TypeError('"options.purpose" is required.');
    }
    const suites = Array.isArray(suite) ? suite : [suite];
    if(suites.length === 0) {
      throw new TypeError('At least one suite is required.');
    }

    if(documentLoader) {
      documentLoader = extendContextLoader(documentLoader);
    } else {
      documentLoader = strictDocumentLoader;
    }
    if(expansionMap !== false) {
      expansionMap = strictExpansionMap;
    }

    try {
      // shallow copy to allow for removal of proof set prior to canonize
      document = {...document};

      // get proofs from document
      const {proofSet, document: doc} = await _getProofs({
        document, documentLoader, expansionMap
      });
      document = doc;

      // verify proofs
      const results = await _verify({
        document, suites, proofSet, purpose, documentLoader, expansionMap
      });
      if(results.length === 0) {
        const error = new Error(
          'Did not verify any proofs; insufficient proofs matched the ' +
          'acceptable suite(s) and required purpose(s).');
        error.name = 'NotFoundError';
        throw error;
      }

      // combine results
      const verified = results.some(r => r.verified);
      if(!verified) {
        const errors = [].concat(
          ...results.filter(r => r.error).map(r => r.error));
        const result = {verified, results};
        if(errors.length > 0) {
          result.error = errors;
        }
        return result;
      }
      return {verified, results};
    } catch(error) {
      _makeSerializable(error);
      return {verified: false, error};
    }
  }
};

async function _getProofs({document}) {
  // handle document preprocessing to find proofs
  let proofSet;
  proofSet = jsonld.getValues(document, 'proof');
  delete document.proof;

  if(proofSet.length === 0) {
    // no possible matches
    throw new Error('No matching proofs found in the given document.');
  }

  // shallow copy proofs and add document context or SECURITY_CONTEXT_URL
  const context = document['@context'] || constants.SECURITY_CONTEXT_URL;
  proofSet = proofSet.map(proof => ({
    '@context': context,
    ...proof
  }));

  return {proofSet, document};
}

async function _verify({
  document, suites, proofSet, purpose, documentLoader, expansionMap
}) {
  // map each purpose to at least one proof to verify
  const purposes = Array.isArray(purpose) ? purpose : [purpose];
  const purposeToProofs = new Map();
  const proofToSuite = new Map();
  const suiteMatchQueue = new Map();
  await Promise.all(purposes.map(purpose => _matchProofSet({
    purposeToProofs, proofToSuite, purpose, proofSet, suites,
    suiteMatchQueue, document, documentLoader, expansionMap
  })));

  // every purpose must have at least one matching proof or verify will fail
  if(purposeToProofs.length < purposes.length) {
    // insufficient proofs to verify, so don't bother verifying any
    return [];
  }

  // verify every proof in `proofToSuite`; these proofs matched a purpose
  const verifyResults = new Map();
  await Promise.all([...proofToSuite.entries()].map(async ([proof, suite]) => {
    let result;
    try {
      // create backwards-compatible deferred proof purpose to capture
      // verification method from old-style suites
      let vm;
      const purpose = {
        async validate(proof, {verificationMethod}) {
          vm = verificationMethod;
          return {valid: true};
        }
      };
      const {verified, verificationMethod, error} = await suite.verifyProof({
        proof, document, purpose, documentLoader, expansionMap
      });
      if(!vm) {
        vm = verificationMethod;
      }
      result = {proof, verified, verificationMethod: vm, error};
    } catch(error) {
      result = {proof, verified: false, error};
    }

    if(result.error) {
      // ensure error is serializable
      _makeSerializable(result.error);
    }

    verifyResults.set(proof, result);
  }));

  // validate proof against each purpose that matched it
  await Promise.all([...purposeToProofs.entries()].map(
    async ([purpose, proofs]) => {
      for(const proof of proofs) {
        const result = verifyResults.get(proof);
        if(!result.verified) {
          // if proof was not verified, so not bother validating purpose
          continue;
        }

        // validate purpose
        const {verificationMethod} = result;
        const suite = proofToSuite.get(proof);
        let purposeResult;
        try {
          purposeResult = await purpose.validate(proof, {
            document, suite, verificationMethod, documentLoader, expansionMap
          });
        } catch(error) {
          purposeResult = {valid: false, error};
        }

        // add `purposeResult` to verification result regardless of validity
        // to ensure that all purposes are represented
        if(result.purposeResult) {
          if(Array.isArray(result.purposeResult)) {
            result.purposeResult.push(purposeResult);
          } else {
            result.purposeResult = [result.purposeResult, purposeResult];
          }
        } else {
          result.purposeResult = purposeResult;
        }

        if(!purposeResult.valid) {
          // ensure error is serializable
          _makeSerializable(purposeResult.error);

          // if no top level error set yet, set it
          if(!result.error) {
            result.verified = false;
            result.error = purposeResult.error;
          }
        }
      }
    }));

  return [...verifyResults.values()];
}

// add a `toJSON` method to an error which allows for errors in validation
// reports to be serialized properly by `JSON.stringify`.
function _makeSerializable(error) {
  Object.defineProperty(error, 'toJSON', {
    value: function() {
      return serializeError(this);
    },
    configurable: true,
    writable: true
  });
}

async function _matchProofSet({
  purposeToProofs, proofToSuite, purpose, proofSet, suites,
  suiteMatchQueue, document, documentLoader, expansionMap
}) {
  for(const proof of proofSet) {
    // first check if the proof matches the purpose; if it doesn't continue
    if(!await purpose.match(proof, {document, documentLoader, expansionMap})) {
      continue;
    }

    // next, find the suite that can verify the proof; if found, `matched`
    // will be set to `true` and the proof will be added to `purposeToProofs`
    // and `proofToSuite` to be processed -- otherwise it will not be; if
    // no proofs are added for a given purpose, an exception will be thrown
    let matched = false;
    for(const s of suites) {
      // `matchingProofs` is a map of promises that resolve to whether a
      // proof matches a suite; multiple purposes and suites may be checked
      // in parallel so a promise queue is used to prevent duplicate work
      let matchingProofs = suiteMatchQueue.get(s);
      if(!matchingProofs) {
        suiteMatchQueue.set(s, matchingProofs = new Map());
      }
      let promise = matchingProofs.get(proof);
      if(!promise) {
        promise = s.matchProof({proof, document, documentLoader, expansionMap});
        matchingProofs.set(proof, promise);
      }
      if(await promise) {
        // found the matching suite for the proof; there should only be one
        // suite that can verify a particular proof; add the proof to the
        // map of proofs to be verified along with the matching suite
        matched = true;
        proofToSuite.set(proof, s);
        break;
      }
    }

    if(matched) {
      // note proof was a match for the purpose and an acceptable suite; it
      // will need to be verified by the suite and then validated against the
      // purpose
      const matches = purposeToProofs.get(purpose);
      if(matches) {
        matches.push(proof);
      } else {
        purposeToProofs.set(purpose, [proof]);
      }
    }
  }
}
<|endoftext|>


<commit_before>"use strict";
const usm = require("./url-state-machine");

exports.implementation = class URLImpl {
  constructor(constructorArgs) {
    const url = constructorArgs[0];
    const base = constructorArgs[1];

    let parsedBase = null;
    if (base !== undefined) {
      parsedBase = usm.basicURLParse(base);
      if (parsedBase.failure) {
        throw new TypeError("Invalid base URL");
      }
    }

    const parsedURL = usm.basicURLParse(url, { baseURL: parsedBase });
    if (parsedURL.failure) {
      throw new TypeError("Invalid URL");
    }

    this._url = parsedURL;

    // TODO: query stuff
  }

  static domainToASCII(domain) {
    const asciiDomain = usm.parseHost(domain);
    if (typeof asciiDomain !== "string") {
      return "";
    }
    return asciiDomain;
  }

  static domainToUnicode(domain) {
    const unicodeDomain = usm.parseHost(domain, true);
    if (typeof unicodeDomain !== "string") {
      return "";
    }
    return unicodeDomain;
  }

  get href() {
    return usm.serializeURL(this._url);
  }

  set href(v) {
    this._url = usm.basicURLParse(v);
  }

  get origin() {
    return usm.serializeURLToUnicodeOrigin(this._url);
  }

  get protocol() {
    return this._url.scheme + ":";
  }

  set protocol(v) {
    usm.basicURLParse(v + ":", { url: this._url, stateOverride: "scheme start" });
  }

  get username() {
    return this._url.username;
  }

  set username(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL) {
      return;
    }

    usm.setTheUsername(this._url, v);
  }

  get password() {
    if (this._url.password === null) {
      return "";
    }

    return this._url.password;
  }

  set password(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL) {
      return;
    }

    usm.setThePassword(this._url, v);
  }

  get host() {
    const url = this._url;

    if (url.host === null) {
      return "";
    }

    if (url.port === null) {
      return usm.serializeHost(url.host);
    }

    return usm.serializeHost(url.host) + ":" + usm.serializeInteger(url.port);
  }

  set host(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "host" });
  }

  get hostname() {
    if (this._url.host === null) {
      return "";
    }

    return usm.serializeHost(this._url.host);
  }

  set hostname(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "hostname" });
  }

  get port() {
    if (this._url.port === null) {
      return "";
    }

    return usm.serializeInteger(this._url.port);
  }

  set port(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL || this._url.scheme === "file") {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "port" });
  }

  get pathname() {
    if (this._url.cannotBeABaseURL) {
      return this._url.path[0];
    }

    return "/" + this._url.path.join("/");
  }

  set pathname(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    this._url.path = [];
    usm.basicURLParse(v, { url: this._url, stateOverride: "path start" });
  }

  get search() {
    if (this._url.query === null || this._url.query === "") {
      return "";
    }

    return "?" + this._url.query;
  }

  set search(v) {
    // TODO: query stuff

    const url = this._url;

    if (v === "") {
      url.query = null;
    }

    const input = v[0] === "?" ? v.substring(1) : v;
    url.query = "";
    usm.basicURLParse(input, { url, stateOverride: "query" });
  }

  get hash() {
    if (this._url.fragment === null || this._url.fragment === "") {
      return "";
    }

    return "#" + this._url.fragment;
  }

  set hash(v) {
    if (this._url.scheme === "javascript") {
      return;
    }

    if (v === "") {
      this._url.fragment = null;
      return;
    }

    const input = v[0] === "#" ? v.substring(1) : v;
    this._url.fragment = "";
    usm.basicURLParse(input, { url: this._url, stateOverride: "fragment" });
  }
};
<commit_msg>Update search setter for whatwg/url#114<commit_after>"use strict";
const usm = require("./url-state-machine");

exports.implementation = class URLImpl {
  constructor(constructorArgs) {
    const url = constructorArgs[0];
    const base = constructorArgs[1];

    let parsedBase = null;
    if (base !== undefined) {
      parsedBase = usm.basicURLParse(base);
      if (parsedBase.failure) {
        throw new TypeError("Invalid base URL");
      }
    }

    const parsedURL = usm.basicURLParse(url, { baseURL: parsedBase });
    if (parsedURL.failure) {
      throw new TypeError("Invalid URL");
    }

    this._url = parsedURL;

    // TODO: query stuff
  }

  static domainToASCII(domain) {
    const asciiDomain = usm.parseHost(domain);
    if (typeof asciiDomain !== "string") {
      return "";
    }
    return asciiDomain;
  }

  static domainToUnicode(domain) {
    const unicodeDomain = usm.parseHost(domain, true);
    if (typeof unicodeDomain !== "string") {
      return "";
    }
    return unicodeDomain;
  }

  get href() {
    return usm.serializeURL(this._url);
  }

  set href(v) {
    this._url = usm.basicURLParse(v);
  }

  get origin() {
    return usm.serializeURLToUnicodeOrigin(this._url);
  }

  get protocol() {
    return this._url.scheme + ":";
  }

  set protocol(v) {
    usm.basicURLParse(v + ":", { url: this._url, stateOverride: "scheme start" });
  }

  get username() {
    return this._url.username;
  }

  set username(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL) {
      return;
    }

    usm.setTheUsername(this._url, v);
  }

  get password() {
    if (this._url.password === null) {
      return "";
    }

    return this._url.password;
  }

  set password(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL) {
      return;
    }

    usm.setThePassword(this._url, v);
  }

  get host() {
    const url = this._url;

    if (url.host === null) {
      return "";
    }

    if (url.port === null) {
      return usm.serializeHost(url.host);
    }

    return usm.serializeHost(url.host) + ":" + usm.serializeInteger(url.port);
  }

  set host(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "host" });
  }

  get hostname() {
    if (this._url.host === null) {
      return "";
    }

    return usm.serializeHost(this._url.host);
  }

  set hostname(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "hostname" });
  }

  get port() {
    if (this._url.port === null) {
      return "";
    }

    return usm.serializeInteger(this._url.port);
  }

  set port(v) {
    if (this._url.host === null || this._url.cannotBeABaseURL || this._url.scheme === "file") {
      return;
    }

    usm.basicURLParse(v, { url: this._url, stateOverride: "port" });
  }

  get pathname() {
    if (this._url.cannotBeABaseURL) {
      return this._url.path[0];
    }

    return "/" + this._url.path.join("/");
  }

  set pathname(v) {
    if (this._url.cannotBeABaseURL) {
      return;
    }

    this._url.path = [];
    usm.basicURLParse(v, { url: this._url, stateOverride: "path start" });
  }

  get search() {
    if (this._url.query === null || this._url.query === "") {
      return "";
    }

    return "?" + this._url.query;
  }

  set search(v) {
    // TODO: query stuff

    const url = this._url;

    if (v === "") {
      url.query = null;
      return;
    }

    const input = v[0] === "?" ? v.substring(1) : v;
    url.query = "";
    usm.basicURLParse(input, { url, stateOverride: "query" });
  }

  get hash() {
    if (this._url.fragment === null || this._url.fragment === "") {
      return "";
    }

    return "#" + this._url.fragment;
  }

  set hash(v) {
    if (this._url.scheme === "javascript") {
      return;
    }

    if (v === "") {
      this._url.fragment = null;
      return;
    }

    const input = v[0] === "#" ? v.substring(1) : v;
    this._url.fragment = "";
    usm.basicURLParse(input, { url: this._url, stateOverride: "fragment" });
  }
};
<|endoftext|>


<commit_before>var gulp = require('gulp');
var runSequence = require('run-sequence');

module.exports = function(task) {
    // create the unique subtasks
    task.subtasks.forEach(function (subtask) {
        gulp.task(subtask.name, subtask.parm1, subtask.parm2);
    });

    var args = task.subtasks.map(function (subtask) { return subtask.name; });

    // create the master task which will run all the subtasks in sequence
    gulp.task(task.name, function() {
        runSequence.apply(null, args);
    });
};
<commit_msg>Document add-task module<commit_after>var gulp = require('gulp');
var runSequence = require('run-sequence');

/**
 * Register the subtasks of the given tasks, and configure the master task to
 * run them.
 *
 * @param  {object} task - A task object from the task registry
 */
module.exports = function(task) {
    // create the unique subtasks
    task.subtasks.forEach(function (subtask) {
        gulp.task(subtask.name, subtask.parm1, subtask.parm2);
    });

    var args = task.subtasks.map(function (subtask) { return subtask.name; });

    // create the master task which will run all the subtasks in sequence
    gulp.task(task.name, function() {
        runSequence.apply(null, args);
    });
};
<|endoftext|>


<commit_before>var HTTP_STATUS_CODES = require('http').STATUS_CODES;

var fs = require('fs');
var os = require('os');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var request = require('request');
var xmlbuilder = require('xmlbuilder');
var stackTrace = require('stack-trace');
var hashish = require('hashish');
var querystring = require('querystring');

module.exports = Airbrake;
util.inherits(Airbrake, EventEmitter);
function Airbrake() {
  this.key = null;

  this.host = 'http://' + os.hostname();
  this.env = process.env.NODE_ENV || 'development';
  this.projectRoot = null;
  this.appVersion = null;
  this.timeout = 30 * 1000;
  this.developmentEnvironments = ['development', 'test'];

  this.protocol = 'http';
  this.serviceHost = 'api.airbrake.io';
}

Airbrake.PACKAGE = (function() {
  var json = fs.readFileSync(__dirname + '/../package.json', 'utf8');
  return JSON.parse(json);
})();

Airbrake.createClient = function(key, env) {
  var instance = new this();
  instance.key = key;
  instance.env = env || instance.env;
  return instance;
};

Airbrake.prototype.expressHandler = function() {
  var self = this;

  process.on('uncaughtException', function(err) {
    self._onError(err, true)
  });

  return function errorHandler(err, req, res, next) {
    if (res.statusCode < 400) res.statusCode = 500;
    err.url = req.url;
    err.component = req.url;
    err.action = req.method;
    err.params = req.params;
    err.session = req.session;

    self._onError(err, false);
    next(err);
  }
}

Airbrake.prototype._onError = function(err, die) {
  var self = this;
  self.log('Airbrake: Uncaught exception, sending notification for:');
  self.log(err.stack || err);

  self.notify(err, function(notifyErr, url, devMode) {
    if (notifyErr) {
      self.log('Airbrake: Could not notify service.');
      self.log(notifyErr.stack);
    } else if (url) {
      self.log('Airbrake: Notified service: ' + url);
    } else {
      self.log('Airbrake: Dev mode, did not send.');
    }

    if (die) {
      process.exit(1);
    }
  });
}

Airbrake.prototype.handleExceptions = function() {
  var self = this;
  process.on('uncaughtException', function(err) {
    self._onError(err, true)
  });
};

Airbrake.prototype.log = function(str) {
  console.error(str);
};

Airbrake.prototype.notify = function(err, cb) {
  var callback = this._callback(cb);
  // log errors instead of posting to airbrake if a dev enviroment
  if (this.developmentEnvironments.indexOf(this.env) != -1) {
    this.log(err);
    return callback(null, null, true);
  }

  var body = this.notifyXml(err);

  var options = {
    method: 'POST',
    url: this.url('/notifier_api/v2/notices'),
    body: body,
    timeout: this.timeout,
    headers: {
      'Content-Length': body.length,
      'Content-Type': 'text/xml',
      'Accept': 'text/xml',
    },
  };

  request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }

    if (undefined === body) {
      return callback(new Error('invalid body'));
    }

    if (res.statusCode >= 300) {
      var status = HTTP_STATUS_CODES[res.statusCode];

      var explanation = body.match(/<error>([^<]+)/i);
      explanation = (explanation)
        ? ': ' + explanation[1]
        : ': ' + body;

      return callback(new Error(
        'Notification failed: ' + res.statusCode + ' ' + status + explanation
      ));
    }

    // Give me a break, this is legit : )
    var m = body.match(/<url>([^<]+)/i);
    var url = (m)
      ? m[1]
      : null;

    callback(null, url);
  });
};

Airbrake.prototype._callback = function(cb) {
  var self = this;
  return function(err) {
    if (cb) {
      cb.apply(self, arguments);
      return;
    }

    if (err) {
      self.emit('error', err);
    }
  };
};

Airbrake.prototype.url = function(path) {
  return this.protocol + '://' + this.serviceHost + path;
};

Airbrake.prototype.notifyXml = function(err, pretty) {
  var notice = xmlbuilder.create().begin('notice', {
    version: '1.0',
    encoding: 'UTF-8'
  });

  this.appendHeaderXml(notice);
  this.appendErrorXml(notice, err);
  this.appendRequestXml(notice, err);
  this.appendServerEnvironmentXml(notice);

  return notice.up().toString({pretty: pretty});
};

Airbrake.prototype.appendHeaderXml = function(notice) {
  notice
    .att('version', '2.2')
    .ele('api-key')
      .txt(this.key || '-')
    .up()
    .ele('notifier')
      .ele('name')
        .txt(Airbrake.PACKAGE.name)
      .up()
      .ele('version')
        .txt(Airbrake.PACKAGE.version)
      .up()
      .ele('url')
        .txt(Airbrake.PACKAGE.homepage)
      .up()
    .up();
};

Airbrake.prototype.appendErrorXml = function(notice, err) {
  var trace = stackTrace.parse(err);
  var error = notice
    .ele('error')
      .ele('class')
        .txt(err.type || 'Error')
      .up()
      .ele('message')
        .txt(err.message || '-')
      .up()
      .ele('backtrace')

  trace.forEach(function(callSite) {
    error
      .ele('line')
        .att('method', callSite.getFunctionName() || '')
        .att('file', callSite.getFileName() || '')
        .att('number', callSite.getLineNumber() || '')
  });
};

Airbrake.prototype.appendRequestXml = function(notice, err) {
  var request = notice.ele('request');

  var self = this;
  ['url', 'component', 'action'].forEach(function(nodeName) {
    var node = request.ele(nodeName);
    var val = err[nodeName];

    if (nodeName === 'url') {
      if (!val) {
        val = self.host;
      } else if (val.substr(0, 1) === '/') {
        val = self.host + val;
      }
    }

    if (val) {
      node.txt(val);
    }
  });

  this.addRequestVars(request, 'cgi-data', this.cgiDataVars(err));
  this.addRequestVars(request, 'session', this.sessionVars(err));
  this.addRequestVars(request, 'params', this.paramsVars(err));
};

Airbrake.prototype.addRequestVars = function(request, type, vars) {
  this.emit('vars', type, vars);

  var node;
  Object.keys(vars).forEach(function(key) {
    node = node || request.ele(type);
    node
      .ele('var')
      .att('key', key)
      .txt(util.inspect(vars[key]));
  });
};

Airbrake.prototype.cgiDataVars = function(err) {
  var cgiData = {};
  Object.keys(process.env).forEach(function(key) {
    cgiData[key] = process.env[key];
  });

  var exclude = [
    'type',
    'message',
    'arguments',
    'stack',
    'url',
    'session',
    'params',
    'component',
    'action',
  ];

  Object.keys(err).forEach(function(key) {
    if (exclude.indexOf(key) >= 0) {
      return;
    }

    cgiData['err.' + key] = err[key];
  });

  cgiData['process.pid'] = process.pid;
  cgiData['process.uid'] = process.getuid();
  cgiData['process.gid'] = process.getgid();
  cgiData['process.cwd'] = process.cwd();
  cgiData['process.execPath'] = process.execPath;
  cgiData['process.version'] = process.version;
  cgiData['process.argv'] = process.argv;
  cgiData['process.memoryUsage'] = process.memoryUsage();
  cgiData['os.loadavg'] = os.loadavg();
  cgiData['os.uptime'] = os.uptime();

  return cgiData;
};

Airbrake.prototype.sessionVars = function(err) {
  return (err.session instanceof Object)
    ? err.session
    : {};
};

Airbrake.prototype.paramsVars = function(err) {
  return (err.params instanceof Object)
    ? err.params
    : {};
};

Airbrake.prototype.appendServerEnvironmentXml = function(notice) {
  var serverEnvironment = notice.ele('server-environment');

  if (this.projectRoot) {
    serverEnvironment
      .ele('project-root')
      .txt(this.projectRoot);
  }

  serverEnvironment
      .ele('environment-name')
      .txt(this.env)

  if (this.appVersion) {
    serverEnvironment
      .ele('app-version')
      .txt(this.appVersion);
  }
};

Airbrake.prototype.trackDeployment = function(params, cb) {
  if (typeof params === 'function') {
    cb = params;
    params = {};
  }

  params = hashish.merge({
    key: this.key,
    env: this.env,
    user: process.env.USER,
    rev: '',
    repo: '',
  }, params);

  var body = this.deploymentPostData(params);

  var options = {
    method: 'POST',
    url: this.url('/deploys.txt'),
    body: body,
    timeout: this.timeout,
    headers: {
      'Content-Length': body.length,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  };

  var callback = this._callback(cb);

  request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }

    if (res.statusCode >= 300) {
      var status = HTTP_STATUS_CODES[res.statusCode];
      return callback(new Error(
        'Deployment failed: ' + res.statusCode + ' ' + status + ': ' + body
      ));
    }

    callback(null, params);
  });
};

Airbrake.prototype.deploymentPostData = function(params) {
  return querystring.stringify({
    'api_key': params.key,
    'deploy[rails_env]': params.env,
    'deploy[local_username]': params.user,
    'deploy[scm_revision]': params.rev,
    'deploy[scm_repository]': params.repo,
  });
};
<commit_msg>Fixed reverse-compatibility breakage introduced with 7325c76ea35b4e257a3623da677c4cea4d645d19<commit_after>var HTTP_STATUS_CODES = require('http').STATUS_CODES;

var fs = require('fs');
var os = require('os');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var request = require('request');
var xmlbuilder = require('xmlbuilder');
var stackTrace = require('stack-trace');
var hashish = require('hashish');
var querystring = require('querystring');

module.exports = Airbrake;
util.inherits(Airbrake, EventEmitter);
function Airbrake() {
  this.key = null;

  this.host = 'http://' + os.hostname();
  this.env = process.env.NODE_ENV || 'development';
  this.projectRoot = null;
  this.appVersion = null;
  this.timeout = 30 * 1000;
  this.developmentEnvironments = ['development', 'test'];

  this.protocol = 'http';
  this.serviceHost = 'api.airbrake.io';
}

Airbrake.PACKAGE = (function() {
  var json = fs.readFileSync(__dirname + '/../package.json', 'utf8');
  return JSON.parse(json);
})();

Airbrake.createClient = function(key, env) {
  var instance = new this();
  instance.key = key;
  instance.env = env || instance.env;
  return instance;
};

Airbrake.prototype.expressHandler = function() {
  var self = this;

  process.on('uncaughtException', function(err) {
    self._onError(err, true)
  });

  return function errorHandler(err, req, res, next) {
    if (res.statusCode < 400) res.statusCode = 500;
    err.url = req.url;
    err.component = req.url;
    err.action = req.method;
    err.params = req.params;
    err.session = req.session;

    self._onError(err, false);
    next(err);
  }
}

Airbrake.prototype._onError = function(err, die) {
  var self = this;
  self.log('Airbrake: Uncaught exception, sending notification for:');
  self.log(err.stack || err);

  self.notify(err, function(notifyErr, url, devMode) {
    if (notifyErr) {
      self.log('Airbrake: Could not notify service.');
      self.log(notifyErr.stack);
    } else if (devMode) {
      self.log('Airbrake: Dev mode, did not send.');
    } else {
      self.log('Airbrake: Notified service: ' + url);
    }

    if (die) {
      process.exit(1);
    }
  });
}

Airbrake.prototype.handleExceptions = function() {
  var self = this;
  process.on('uncaughtException', function(err) {
    self._onError(err, true)
  });
};

Airbrake.prototype.log = function(str) {
  console.error(str);
};

Airbrake.prototype.notify = function(err, cb) {
  var callback = this._callback(cb);
  // log errors instead of posting to airbrake if a dev enviroment
  if (this.developmentEnvironments.indexOf(this.env) != -1) {
    this.log(err);
    return callback(null, null, true);
  }

  var body = this.notifyXml(err);

  var options = {
    method: 'POST',
    url: this.url('/notifier_api/v2/notices'),
    body: body,
    timeout: this.timeout,
    headers: {
      'Content-Length': body.length,
      'Content-Type': 'text/xml',
      'Accept': 'text/xml',
    },
  };

  request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }

    if (undefined === body) {
      return callback(new Error('invalid body'));
    }

    if (res.statusCode >= 300) {
      var status = HTTP_STATUS_CODES[res.statusCode];

      var explanation = body.match(/<error>([^<]+)/i);
      explanation = (explanation)
        ? ': ' + explanation[1]
        : ': ' + body;

      return callback(new Error(
        'Notification failed: ' + res.statusCode + ' ' + status + explanation
      ));
    }

    // Give me a break, this is legit : )
    var m = body.match(/<url>([^<]+)/i);
    var url = (m)
      ? m[1]
      : null;

    callback(null, url);
  });
};

Airbrake.prototype._callback = function(cb) {
  var self = this;
  return function(err) {
    if (cb) {
      cb.apply(self, arguments);
      return;
    }

    if (err) {
      self.emit('error', err);
    }
  };
};

Airbrake.prototype.url = function(path) {
  return this.protocol + '://' + this.serviceHost + path;
};

Airbrake.prototype.notifyXml = function(err, pretty) {
  var notice = xmlbuilder.create().begin('notice', {
    version: '1.0',
    encoding: 'UTF-8'
  });

  this.appendHeaderXml(notice);
  this.appendErrorXml(notice, err);
  this.appendRequestXml(notice, err);
  this.appendServerEnvironmentXml(notice);

  return notice.up().toString({pretty: pretty});
};

Airbrake.prototype.appendHeaderXml = function(notice) {
  notice
    .att('version', '2.2')
    .ele('api-key')
      .txt(this.key || '-')
    .up()
    .ele('notifier')
      .ele('name')
        .txt(Airbrake.PACKAGE.name)
      .up()
      .ele('version')
        .txt(Airbrake.PACKAGE.version)
      .up()
      .ele('url')
        .txt(Airbrake.PACKAGE.homepage)
      .up()
    .up();
};

Airbrake.prototype.appendErrorXml = function(notice, err) {
  var trace = stackTrace.parse(err);
  var error = notice
    .ele('error')
      .ele('class')
        .txt(err.type || 'Error')
      .up()
      .ele('message')
        .txt(err.message || '-')
      .up()
      .ele('backtrace')

  trace.forEach(function(callSite) {
    error
      .ele('line')
        .att('method', callSite.getFunctionName() || '')
        .att('file', callSite.getFileName() || '')
        .att('number', callSite.getLineNumber() || '')
  });
};

Airbrake.prototype.appendRequestXml = function(notice, err) {
  var request = notice.ele('request');

  var self = this;
  ['url', 'component', 'action'].forEach(function(nodeName) {
    var node = request.ele(nodeName);
    var val = err[nodeName];

    if (nodeName === 'url') {
      if (!val) {
        val = self.host;
      } else if (val.substr(0, 1) === '/') {
        val = self.host + val;
      }
    }

    if (val) {
      node.txt(val);
    }
  });

  this.addRequestVars(request, 'cgi-data', this.cgiDataVars(err));
  this.addRequestVars(request, 'session', this.sessionVars(err));
  this.addRequestVars(request, 'params', this.paramsVars(err));
};

Airbrake.prototype.addRequestVars = function(request, type, vars) {
  this.emit('vars', type, vars);

  var node;
  Object.keys(vars).forEach(function(key) {
    node = node || request.ele(type);
    node
      .ele('var')
      .att('key', key)
      .txt(util.inspect(vars[key]));
  });
};

Airbrake.prototype.cgiDataVars = function(err) {
  var cgiData = {};
  Object.keys(process.env).forEach(function(key) {
    cgiData[key] = process.env[key];
  });

  var exclude = [
    'type',
    'message',
    'arguments',
    'stack',
    'url',
    'session',
    'params',
    'component',
    'action',
  ];

  Object.keys(err).forEach(function(key) {
    if (exclude.indexOf(key) >= 0) {
      return;
    }

    cgiData['err.' + key] = err[key];
  });

  cgiData['process.pid'] = process.pid;
  cgiData['process.uid'] = process.getuid();
  cgiData['process.gid'] = process.getgid();
  cgiData['process.cwd'] = process.cwd();
  cgiData['process.execPath'] = process.execPath;
  cgiData['process.version'] = process.version;
  cgiData['process.argv'] = process.argv;
  cgiData['process.memoryUsage'] = process.memoryUsage();
  cgiData['os.loadavg'] = os.loadavg();
  cgiData['os.uptime'] = os.uptime();

  return cgiData;
};

Airbrake.prototype.sessionVars = function(err) {
  return (err.session instanceof Object)
    ? err.session
    : {};
};

Airbrake.prototype.paramsVars = function(err) {
  return (err.params instanceof Object)
    ? err.params
    : {};
};

Airbrake.prototype.appendServerEnvironmentXml = function(notice) {
  var serverEnvironment = notice.ele('server-environment');

  if (this.projectRoot) {
    serverEnvironment
      .ele('project-root')
      .txt(this.projectRoot);
  }

  serverEnvironment
      .ele('environment-name')
      .txt(this.env)

  if (this.appVersion) {
    serverEnvironment
      .ele('app-version')
      .txt(this.appVersion);
  }
};

Airbrake.prototype.trackDeployment = function(params, cb) {
  if (typeof params === 'function') {
    cb = params;
    params = {};
  }

  params = hashish.merge({
    key: this.key,
    env: this.env,
    user: process.env.USER,
    rev: '',
    repo: '',
  }, params);

  var body = this.deploymentPostData(params);

  var options = {
    method: 'POST',
    url: this.url('/deploys.txt'),
    body: body,
    timeout: this.timeout,
    headers: {
      'Content-Length': body.length,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  };

  var callback = this._callback(cb);

  request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }

    if (res.statusCode >= 300) {
      var status = HTTP_STATUS_CODES[res.statusCode];
      return callback(new Error(
        'Deployment failed: ' + res.statusCode + ' ' + status + ': ' + body
      ));
    }

    callback(null, params);
  });
};

Airbrake.prototype.deploymentPostData = function(params) {
  return querystring.stringify({
    'api_key': params.key,
    'deploy[rails_env]': params.env,
    'deploy[local_username]': params.user,
    'deploy[scm_revision]': params.rev,
    'deploy[scm_repository]': params.repo,
  });
};
<|endoftext|>