<commit_before>import fs from 'fs';
import path from 'path';
import nsp from 'nsp';
import dedent from 'dedent-js';
import escape from 'js-string-escape';
import broccoli from 'broccoli';
import { Watcher } from 'broccoli/lib';
import rimraf from 'rimraf';
import MergeTree from 'broccoli-merge-trees';
import Funnel from 'broccoli-funnel';
import BabelTree from 'broccoli-babel-transpiler';
import LintTree from 'broccoli-lint-eslint';
import { sync as copyDereferenceSync } from 'copy-dereference';
import tmp from 'tmp';
import chalk from 'chalk';
import identity from 'lodash/identity';
import ui from './ui';
import discoverAddons from '../../utils/discover-addons';
import tryRequire from '../../utils/try-require';

export default class Project {

  constructor(options) {
    this.environment = options.environment || 'development';
    this.dir = options.dir || process.cwd();
    this.lint = options.lint;
    this.audit = options.audit;

    this.addons = discoverAddons(this.dir);
    this.buildTree = this._createBuildTree();
    this.builder = new broccoli.Builder(this.buildTree);
  }

  build(outputDir = 'dist') {
    this.startTime = process.hrtime();
    return this.builder.build()
      .then((results) => {
        this._finishBuild(results.directory, outputDir);
      }).finally(() => {
        return this.builder.cleanup();
      }).catch((err) => {
        if (err.file) {
          ui.error(`File: ${ err.file }`);
        }
        ui.error(err.stack);
        ui.error('\nBuild failed');
        throw err;
      });
  }

  watch({ outputDir, onBuild }) {
    outputDir = outputDir || 'dist';
    if (fs.existsSync(outputDir)) {
      rimraf.sync(outputDir);
    }
    let watcher = new Watcher(this.builder, { interval: 100 });

    let onExit = () => {
      this.builder.cleanup();
      process.exit(1);
    };
    process.on('SIGINT', onExit);
    process.on('SIGTERM', onExit);

    watcher.on('change', (results) => {
      this._finishBuild(results.directory, outputDir);
      onBuild();
    });

    watcher.on('error', (error) => {
      ui.error('\n\nBuild failed.');
      if (error.file) {
        if (error.line && error.column) {
          ui.error(`File: ${ error.treeDir }/${ error.file }:${ error.line }:${ error.column }`);
        } else {
          ui.error(`File: ${ error.treeDir }/${ error.file }`);
        }
      }
      if (error.message) {
        ui.error(`Error: ${ error.message }`);
      }
      if (error.stack) {
        ui.error(`Stack trace:\n${ error.stack.replace(/(^.)/mg, '  $1') }`);
      }
    });
  }

  createApplication() {
    let dist = tmp.dirSync({ unsafeCleanup: true, prefix: 'denali-' }).name;
    return this.build(dist)
      .then(() => {
        let Application = tryRequire(path.join(dist, 'app/application'));
        if (!Application) {
          ui.error(`Error loading your application - expected /app/application.js to exist`);
          throw new Error('Invalid application export');
        }
        return new Application({
          dir: dist,
          environment: this.environment
        });
      }).catch((error) => {
        ui.error('Error instantiating application:');
        ui.error(error.stack);
        throw error;
      });
  }

  _createBuildTree() {
    let dirs = [ 'app', 'config' ];
    if (this.environment === 'test') {
      dirs.push('test');
    }

    let sourceTrees = dirs.map((dir) => {
      return new Funnel(dir, { destDir: dir });
    });

    // We do this first because broccoli-lint-eslint is weird
    // https://github.com/ember-cli/broccoli-lint-eslint/pull/25
    if (this.lint) {
      // If it's in test environment, generate test modules for each linted file
      if (this.environment === 'test') {
        let lintTestTrees = sourceTrees.map((dir) => {
          return new LintTree(dir, {
            testGenerator: mochaLintGenerator,
            format() {
              return '\r';
            }
          });
        });
        let lintTestTree = new MergeTree(lintTestTrees);
        lintTestTree = new Funnel(lintTestTree, { destDir: 'test/lint' });
        sourceTrees.push(lintTestTree);
      // Otherwise, just lint and move on
      } else {
        sourceTrees = sourceTrees.map((dir) => new LintTree(dir));
      }
    }

    let tree = new MergeTree(sourceTrees);

    // Let addons have first crack at the app's build tree. They also get their
    // own absolute path passed in, in case they want to inject their own assets
    // into the app build
    this.addons.forEach((addonDir) => {
      let addonBuild = tryRequire(path.join(addonDir, 'denali-build.js')) || identity;
      tree = addonBuild(tree, this, addonDir);
    });

    // Let the app itself customize it's own build process.
    let appBuild = tryRequire(path.join(this.dir, 'denali-build.js')) || identity;
    tree = appBuild(tree, this);

    // Now the standard Denali build
    let transpiled = new Funnel(tree, {
      include: dirs.map((dir) => `${ dir }/**/*.js`)
    });
    transpiled = new BabelTree(transpiled, {
      presets: [ 'latest' ],
      plugins: [
        'transform-class-properties'
      ],
      browserPolyfill: true
    });
    return new MergeTree([ tree, transpiled ], { overwrite: true });
  }

  _finishBuild(results, outputDir) {
    rimraf.sync(outputDir);
    copyDereferenceSync(results, outputDir);
    [
      'package.json',
      'node_modules'
    ].forEach((p) => {
      fs.symlinkSync(path.join(this.dir, p), path.join(outputDir, p));
    });
    ui.info('Build successful');
    if (this.audit) {
      let pkg = path.join(this.dir, 'package.json');
      nsp.check({ package: pkg }, (err, vulnerabilities) => {
        if (err && [ 'ENOTFOUND', 'ECONNRESET' ].includes(err.code)) {
          ui.warn('Error trying to scan package dependencies for vulnerabilities with nsp, unable to reach server. Skipping scan ...');
          ui.warn(err);
        }
        if (vulnerabilities && vulnerabilities.length > 0) {
          ui.warn('WARNING: Some packages in your package.json may have security vulnerabilities:');
          vulnerabilities.forEach((item) => {
            let module = `** ${ item.module }@${ item.version } **`;
            ui.warn(`${ chalk.bold(module) } ${ chalk.reset.cyan(item.recommendation.replace(/\n/g, ' ')) }`);
          });
        }
      });
    }
  }


}

function mochaLintGenerator(relativePath, errors, results) {<commit_msg>fix(cli): prevent errors if nsp items do not have recommendations<commit_after>import fs from 'fs';
import path from 'path';
import nsp from 'nsp';
import dedent from 'dedent-js';
import escape from 'js-string-escape';
import broccoli from 'broccoli';
import { Watcher } from 'broccoli/lib';
import rimraf from 'rimraf';
import MergeTree from 'broccoli-merge-trees';
import Funnel from 'broccoli-funnel';
import BabelTree from 'broccoli-babel-transpiler';
import LintTree from 'broccoli-lint-eslint';
import { sync as copyDereferenceSync } from 'copy-dereference';
import tmp from 'tmp';
import chalk from 'chalk';
import identity from 'lodash/identity';
import ui from './ui';
import discoverAddons from '../../utils/discover-addons';
import tryRequire from '../../utils/try-require';

export default class Project {

  constructor(options) {
    this.environment = options.environment || 'development';
    this.dir = options.dir || process.cwd();
    this.lint = options.lint;
    this.audit = options.audit;

    this.addons = discoverAddons(this.dir);
    this.buildTree = this._createBuildTree();
    this.builder = new broccoli.Builder(this.buildTree);
  }

  build(outputDir = 'dist') {
    this.startTime = process.hrtime();
    return this.builder.build()
      .then((results) => {
        this._finishBuild(results.directory, outputDir);
      }).finally(() => {
        return this.builder.cleanup();
      }).catch((err) => {
        if (err.file) {
          ui.error(`File: ${ err.file }`);
        }
        ui.error(err.stack);
        ui.error('\nBuild failed');
        throw err;
      });
  }

  watch({ outputDir, onBuild }) {
    outputDir = outputDir || 'dist';
    if (fs.existsSync(outputDir)) {
      rimraf.sync(outputDir);
    }
    let watcher = new Watcher(this.builder, { interval: 100 });

    let onExit = () => {
      this.builder.cleanup();
      process.exit(1);
    };
    process.on('SIGINT', onExit);
    process.on('SIGTERM', onExit);

    watcher.on('change', (results) => {
      this._finishBuild(results.directory, outputDir);
      onBuild();
    });

    watcher.on('error', (error) => {
      ui.error('\n\nBuild failed.');
      if (error.file) {
        if (error.line && error.column) {
          ui.error(`File: ${ error.treeDir }/${ error.file }:${ error.line }:${ error.column }`);
        } else {
          ui.error(`File: ${ error.treeDir }/${ error.file }`);
        }
      }
      if (error.message) {
        ui.error(`Error: ${ error.message }`);
      }
      if (error.stack) {
        ui.error(`Stack trace:\n${ error.stack.replace(/(^.)/mg, '  $1') }`);
      }
    });
  }

  createApplication() {
    let dist = tmp.dirSync({ unsafeCleanup: true, prefix: 'denali-' }).name;
    return this.build(dist)
      .then(() => {
        let Application = tryRequire(path.join(dist, 'app/application'));
        if (!Application) {
          ui.error(`Error loading your application - expected /app/application.js to exist`);
          throw new Error('Invalid application export');
        }
        return new Application({
          dir: dist,
          environment: this.environment
        });
      }).catch((error) => {
        ui.error('Error instantiating application:');
        ui.error(error.stack);
        throw error;
      });
  }

  _createBuildTree() {
    let dirs = [ 'app', 'config' ];
    if (this.environment === 'test') {
      dirs.push('test');
    }

    let sourceTrees = dirs.map((dir) => {
      return new Funnel(dir, { destDir: dir });
    });

    // We do this first because broccoli-lint-eslint is weird
    // https://github.com/ember-cli/broccoli-lint-eslint/pull/25
    if (this.lint) {
      // If it's in test environment, generate test modules for each linted file
      if (this.environment === 'test') {
        let lintTestTrees = sourceTrees.map((dir) => {
          return new LintTree(dir, {
            testGenerator: mochaLintGenerator,
            format() {
              return '\r';
            }
          });
        });
        let lintTestTree = new MergeTree(lintTestTrees);
        lintTestTree = new Funnel(lintTestTree, { destDir: 'test/lint' });
        sourceTrees.push(lintTestTree);
      // Otherwise, just lint and move on
      } else {
        sourceTrees = sourceTrees.map((dir) => new LintTree(dir));
      }
    }

    let tree = new MergeTree(sourceTrees);

    // Let addons have first crack at the app's build tree. They also get their
    // own absolute path passed in, in case they want to inject their own assets
    // into the app build
    this.addons.forEach((addonDir) => {
      let addonBuild = tryRequire(path.join(addonDir, 'denali-build.js')) || identity;
      tree = addonBuild(tree, this, addonDir);
    });

    // Let the app itself customize it's own build process.
    let appBuild = tryRequire(path.join(this.dir, 'denali-build.js')) || identity;
    tree = appBuild(tree, this);

    // Now the standard Denali build
    let transpiled = new Funnel(tree, {
      include: dirs.map((dir) => `${ dir }/**/*.js`)
    });
    transpiled = new BabelTree(transpiled, {
      presets: [ 'latest' ],
      plugins: [
        'transform-class-properties'
      ],
      browserPolyfill: true
    });
    return new MergeTree([ tree, transpiled ], { overwrite: true });
  }

  _finishBuild(results, outputDir) {
    rimraf.sync(outputDir);
    copyDereferenceSync(results, outputDir);
    [
      'package.json',
      'node_modules'
    ].forEach((p) => {
      fs.symlinkSync(path.join(this.dir, p), path.join(outputDir, p));
    });
    ui.info('Build successful');
    if (this.audit) {
      let pkg = path.join(this.dir, 'package.json');
      nsp.check({ package: pkg }, (err, vulnerabilities) => {
        if (err && [ 'ENOTFOUND', 'ECONNRESET' ].includes(err.code)) {
          ui.warn('Error trying to scan package dependencies for vulnerabilities with nsp, unable to reach server. Skipping scan ...');
          ui.warn(err);
        }
        if (vulnerabilities && vulnerabilities.length > 0) {
          ui.warn('WARNING: Some packages in your package.json may have security vulnerabilities:');
          vulnerabilities.forEach((item) => {
            let module = `** ${ item.module }@${ item.version } **`;
            let recommendation = (item.recommendation || '').replace(/\n/g, ' ');
            ui.warn(`${ chalk.bold(module) } ${ chalk.reset.cyan(recommendation) }`);
          });
        }
      });
    }
  }


}

function mochaLintGenerator(relativePath, errors, results) {
{'fraction_non_alphanumeric': 0.15446779034864927, 'fraction_numerical': 0.0012314323097052258, 'mean_word_length': 2.517596101786681, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 37, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3673207', 'n_tokens_mistral': 4039, 'n_tokens_neox': 3961, 'n_words': 939}