<commit_before>/* global require, module */
'use strict';

var p = require('../preprocessors');

var preprocessJs  = p.preprocessJs;
var preprocessCss = p.preprocessCss;

var preprocessTemplates = p.preprocessTemplates;

var replace     = require('broccoli-replace');
var compileES6  = require('broccoli-es6-concatenator');
var validateES6 = require('broccoli-es6-import-validate');
var pickFiles   = require('broccoli-static-compiler');
var mergeTrees  = require('broccoli-merge-trees');
var jshintTrees = require('broccoli-jshint');

var uglifyJavaScript = require('broccoli-uglify-js');

var memoize    = require('lodash-node/modern/functions').memoize;
var assign     = require('lodash-node/modern/objects/assign');
var path       = require('path');

module.exports = EmberApp;

function EmberApp(options) {
  this.env  = process.env.EMBER_ENV;
  this.name = options.name;

  this.options = options;

  this.importWhitelist     = {};
  this.legacyFilesToAppend = [];

  this.populateLegacyFiles();

  this.trees = this.options.trees || { };

  this.tests = this.hinting = (this.env !== 'production');
  this.wrapInEval = this.options.wrapInEval || this.tests;

  this.trees.app    = this.trees.app    || 'app';
  this.trees.styles = this.trees.styles || 'app/styles';
  this.trees.tests  = this.trees.tests  || 'tests';
  this.trees.vendor = this.trees.vendor || 'vendor';
}

EmberApp.prototype.populateLegacyFiles = function () {

  this.import('vendor/jquery/jquery.js');
  this.import('vendor/handlebars/handlebars.js');
  this.import({
    development: 'vendor/ember/ember.js',
    production:  'vendor/ember/ember.prod.js'
  });

  this.import('vendor/ember-cli-shims/app-shims.js', {
    ember: ['default']
  });

  this.import('vendor/ember-resolver/dist/modules/ember-resolver.js', {
    'ember/resolver': ['default']
  });

  this.import('vendor/ember-load-initializers/ember-load-initializers.js', {
    'ember/load-initializers': ['default']
  });
};

EmberApp.prototype.index = memoize(function() {
  var files = [
    'index.html'
  ];

  var index = pickFiles(this.trees.app, {
    srcDir: '/',
    files: files,
    destDir: '/'
  });

  return injectENVJson(this.options.getEnvJSON, this.env, index, files);
});

EmberApp.prototype.testIndex = memoize(function() {
  var index = pickFiles('tests', {
    srcDir: '/',
    files: ['index.html'],
    destDir: '/tests'
  });

  return injectENVJson(this.options.getEnvJSON, this.env, index, [
      'tests/index.html'
    ]);
});

EmberApp.prototype.publicFolder = function() {
  return 'public';
};

EmberApp.prototype.appAndDependencies = memoize(function() {
  var app = pickFiles(this.trees.app, {
    srcDir: '/',
    destDir: this.name
  });

  var vendor = pickFiles(this.trees.vendor, {
    srcDir: '/',
    destDir: 'vendor/'
  });

  var appAndTemplates = preprocessTemplates(app);

  var sourceTrees = [ appAndTemplates, vendor ];

  if (this.tests) {
    var tests = pickFiles(this.trees.tests, {
      srcDir: '/',
      destDir: this.name + '/tests'
    });

    sourceTrees.push(tests);

    if (this.hinting) {
      var importWhitelist = this.importWhitelist;

      var validatedJS = validateES6(mergeTrees([app, tests]), {
        whitelist: importWhitelist
      });
      sourceTrees.push(validatedJS);

      var jshintedApp = jshintTrees(app, {
        destFile: this.name + '/tests/app-jshint-test.js'
      });
      sourceTrees.push(jshintedApp);
      var jshintedTests = jshintTrees(tests, {
        jshintrcRoot: 'tests/',
        destFile: this.name + '/tests/tests-jshint-test.js'
      });
      sourceTrees.push(jshintedTests);
    }
  }

  return mergeTrees(sourceTrees, {
      overwrite: true
    });
});

EmberApp.prototype.javascript = memoize(function() {
  var applicationJs       = preprocessJs(this.appAndDependencies(), '/', this.name);
  var legacyFilesToAppend = this.legacyFilesToAppend;

  if (this.env !== 'production') {
    this.import('vendor/ember-qunit/dist/named-amd/main.js', {
      'ember-qunit': [
        'globalize',
        'moduleFor',
        'moduleForComponent',
        'moduleForModel',
        'test',
        'setResolver'
      ]
    });

    this.import('vendor/ember-cli-shims/test-shims.js', {
      'qunit': ['default']
    });
  }

  var es6 = compileES6(applicationJs, {
    loaderFile: 'vendor/loader/loader.js',
    ignoredModules: Object.keys(this.importWhitelist),
    inputFiles: [
      this.name + '/**/*.js'
    ],
    wrapInEval: this.wrapInEval,
    outputFile: '/assets/app.js',
    legacyFilesToAppend: legacyFilesToAppend
  });

  if (this.env === 'production') {
    return uglifyJavaScript(es6, {
      mangle: true,
      compress: true
    });
  } else {
    return es6;
  }
});

EmberApp.prototype.styles = memoize(function() {
  return preprocessCss(this.appAndDependencies(), this.name + '/styles', '/assets');
});

EmberApp.prototype.testFiles = memoize(function() {
  var qunitFiles = pickFiles('vendor', {
      srcDir: '/qunit/qunit',
      files: [
        'qunit.css', 'qunit.js'
      ],
      destDir: '/assets/'
    });

  var testemPath = path.join(__dirname, 'testem');
  testemPath = path.dirname(testemPath);

  var testemTree = pickFiles(testemPath, {
      files: ['testem.js'],
      srcDir: '/',
      destDir: '/'
    });

  var sourceTrees = [qunitFiles, testemTree];

  return mergeTrees(sourceTrees, {
      overwrite: true
    });
});

EmberApp.prototype.import = function(asset, modules) {
  if (typeof asset === 'object') {
    this.legacyFilesToAppend.push(asset[this.env]);
  } else {
    this.legacyFilesToAppend.push(asset);
  }

  this.importWhitelist = assign(this.importWhitelist, modules || {});
};

EmberApp.prototype.toArray = function() {
  var sourceTrees = [
    this.index(),
    this.javascript(),
    this.publicFolder(),
    this.styles()
  ];

  if (this.tests) {
    sourceTrees = sourceTrees.concat(this.testIndex(), this.testFiles());
  }

  return sourceTrees;
};

EmberApp.prototype.toTree = memoize(function() {
  return mergeTrees(this.toArray(), {
    overwrite: true
  });
});

function injectENVJson(fn, env, tree, files) {
  // TODO: real templating
  var envJsonString = function(){
    return JSON.stringify(fn(env));
  };

  var baseTag = function(){
    var baseURL = fn(env).baseURL;

    if (baseURL && baseURL !== '') {
      return '<base href="' + baseURL + '" />';
    } else {
      return '';
    }
  };

  return replace(tree, {
    files: files,
    patterns: [{
      match: /\{\{ENV\}\}/g,
      replacement: envJsonString
    },
    {
      match: /\{\{BASE_TAG\}\}/g,
      replacement: baseTag
    }]
  });
}
<commit_msg>better implementation of defaults in `EmberApp`<commit_after>/* global require, module */
'use strict';

var p = require('../preprocessors');

var preprocessJs  = p.preprocessJs;
var preprocessCss = p.preprocessCss;

var preprocessTemplates = p.preprocessTemplates;

var replace     = require('broccoli-replace');
var compileES6  = require('broccoli-es6-concatenator');
var validateES6 = require('broccoli-es6-import-validate');
var pickFiles   = require('broccoli-static-compiler');
var mergeTrees  = require('broccoli-merge-trees');
var jshintTrees = require('broccoli-jshint');

var uglifyJavaScript = require('broccoli-uglify-js');

var memoize    = require('lodash-node/modern/functions').memoize;
var assign     = require('lodash-node/modern/objects/assign');
var defaults   = require('lodash-node/modern/objects/defaults');
var path       = require('path');

module.exports = EmberApp;

function EmberApp(options) {
  this.env  = process.env.EMBER_ENV;
  this.name = options.name;

  var isProduction = this.env === 'production';

  this.tests = this.hinting = !isProduction;

  this.options = defaults(options, {
    wrapInEval: !isProduction,
    loader: 'vendor/loader/loader.js',
    trees: {
      app:    'app',
      styles: 'app/styles',
      tests:  'tests',
      vendor: 'vendor'
    },
  });

  this.importWhitelist     = {};
  this.legacyFilesToAppend = [];

  this.trees = this.options.trees;

  this.populateLegacyFiles();
}

EmberApp.prototype.populateLegacyFiles = function () {

  this.import('vendor/jquery/jquery.js');
  this.import('vendor/handlebars/handlebars.js');
  this.import({
    development: 'vendor/ember/ember.js',
    production:  'vendor/ember/ember.prod.js'
  });

  this.import('vendor/ember-cli-shims/app-shims.js', {
    ember: ['default']
  });

  this.import('vendor/ember-resolver/dist/modules/ember-resolver.js', {
    'ember/resolver': ['default']
  });

  this.import('vendor/ember-load-initializers/ember-load-initializers.js', {
    'ember/load-initializers': ['default']
  });
};

EmberApp.prototype.index = memoize(function() {
  var files = [
    'index.html'
  ];

  var index = pickFiles(this.trees.app, {
    srcDir: '/',
    files: files,
    destDir: '/'
  });

  return injectENVJson(this.options.getEnvJSON, this.env, index, files);
});

EmberApp.prototype.testIndex = memoize(function() {
  var index = pickFiles('tests', {
    srcDir: '/',
    files: ['index.html'],
    destDir: '/tests'
  });

  return injectENVJson(this.options.getEnvJSON, this.env, index, [
      'tests/index.html'
    ]);
});

EmberApp.prototype.publicFolder = function() {
  return 'public';
};

EmberApp.prototype.appAndDependencies = memoize(function() {
  var app = pickFiles(this.trees.app, {
    srcDir: '/',
    destDir: this.name
  });

  var vendor = pickFiles(this.trees.vendor, {
    srcDir: '/',
    destDir: 'vendor/'
  });

  var appAndTemplates = preprocessTemplates(app);

  var sourceTrees = [ appAndTemplates, vendor ];

  if (this.tests) {
    var tests = pickFiles(this.trees.tests, {
      srcDir: '/',
      destDir: this.name + '/tests'
    });

    sourceTrees.push(tests);

    if (this.hinting) {
      var importWhitelist = this.importWhitelist;

      var validatedJS = validateES6(mergeTrees([app, tests]), {
        whitelist: importWhitelist
      });
      sourceTrees.push(validatedJS);

      var jshintedApp = jshintTrees(app, {
        destFile: this.name + '/tests/app-jshint-test.js'
      });
      sourceTrees.push(jshintedApp);
      var jshintedTests = jshintTrees(tests, {
        jshintrcRoot: 'tests/',
        destFile: this.name + '/tests/tests-jshint-test.js'
      });
      sourceTrees.push(jshintedTests);
    }
  }

  return mergeTrees(sourceTrees, {
      overwrite: true
    });
});

EmberApp.prototype.javascript = memoize(function() {
  var applicationJs       = preprocessJs(this.appAndDependencies(), '/', this.name);
  var legacyFilesToAppend = this.legacyFilesToAppend;

  if (this.env !== 'production') {
    this.import('vendor/ember-qunit/dist/named-amd/main.js', {
      'ember-qunit': [
        'globalize',
        'moduleFor',
        'moduleForComponent',
        'moduleForModel',
        'test',
        'setResolver'
      ]
    });

    this.import('vendor/ember-cli-shims/test-shims.js', {
      'qunit': ['default']
    });
  }

  var es6 = compileES6(applicationJs, {
    loaderFile: this.options.loader,
    ignoredModules: Object.keys(this.importWhitelist),
    inputFiles: [
      this.name + '/**/*.js'
    ],
    wrapInEval: this.options.wrapInEval,
    outputFile: '/assets/app.js',
    legacyFilesToAppend: legacyFilesToAppend
  });

  if (this.env === 'production') {
    return uglifyJavaScript(es6, {
      mangle: true,
      compress: true
    });
  } else {
    return es6;
  }
});

EmberApp.prototype.styles = memoize(function() {
  return preprocessCss(this.appAndDependencies(), this.name + '/styles', '/assets');
});

EmberApp.prototype.testFiles = memoize(function() {
  var qunitFiles = pickFiles('vendor', {
      srcDir: '/qunit/qunit',
      files: [
        'qunit.css', 'qunit.js'
      ],
      destDir: '/assets/'
    });

  var testemPath = path.join(__dirname, 'testem');
  testemPath = path.dirname(testemPath);

  var testemTree = pickFiles(testemPath, {
      files: ['testem.js'],
      srcDir: '/',
      destDir: '/'
    });

  var sourceTrees = [qunitFiles, testemTree];

  return mergeTrees(sourceTrees, {
      overwrite: true
    });
});

EmberApp.prototype.import = function(asset, modules) {
  if (typeof asset === 'object') {
    this.legacyFilesToAppend.push(asset[this.env]);
  } else {
    this.legacyFilesToAppend.push(asset);
  }

  this.importWhitelist = assign(this.importWhitelist, modules || {});
};

EmberApp.prototype.toArray = function() {
  var sourceTrees = [
    this.index(),
    this.javascript(),
    this.publicFolder(),
    this.styles()
  ];

  if (this.tests) {
    sourceTrees = sourceTrees.concat(this.testIndex(), this.testFiles());
  }

  return sourceTrees;
};

EmberApp.prototype.toTree = memoize(function() {
  return mergeTrees(this.toArray(), {
    overwrite: true
  });
});

function injectENVJson(fn, env, tree, files) {
  // TODO: real templating
  var envJsonString = function(){
    return JSON.stringify(fn(env));
  };

  var baseTag = function(){
    var baseURL = fn(env).baseURL;

    if (baseURL && baseURL !== '') {
      return '<base href="' + baseURL + '" />';
    } else {
      return '';
    }
  };

  return replace(tree, {
    files: files,
    patterns: [{
      match: /\{\{ENV\}\}/g,
      replacement: envJsonString
    },
    {
      match: /\{\{BASE_TAG\}\}/g,
      replacement: baseTag
    }]
  });
}
<|endoftext|>


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

'use strict';

const assert = require('assert');
const flatMap = require('array.prototype.flatmap');

const lighthouse = require('lighthouse');

/**
 * @typedef {import('lighthouse/types/lhr')} LH
 * @typedef {import('lighthouse/types/config').LH.Config} LH_Config
 * @typedef {import('lighthouse/types/externs').SharedFlagsSettings} LH_Flags
 * @typedef {import('lighthouse/types/artifacts').Artifacts} LH_Artifacts
 *
 * @typedef {Object} parsedLHResults
 * @property {number} score
 * @property {LH.Result.audits} audits
 * @property {function(string?): Record<string, any>|Record<string, any>[]} error
 * @property {function(): string} errorString
 * @property {function(number): boolean} isSuccess
 * @property {function(string?): Record<string, auditInfo>|Record<string, auditInfo>[]} success
 */

const defaultConfig = {
  extends: 'lighthouse:default',
  settings: {
    onlyCategories: [],
    throttlingMethod: 'simulate',
  },
};

const defaultFlags = {
  chromeFlags: [
    '--disable-gpu',
    '--headless',
    '--disable-storage-reset',
    '--enable-logging',
    '--disable-device-emulation',
    '--no-sandbox',
  ],
};

/**
 * @param {string} type
 * @param {Array<string>} [skipAudits]
 * @returns {Object}
 */
function getConfigByType(type, skipAudits) {
  const config = JSON.parse(JSON.stringify(defaultConfig));
  if (type) {
    config.settings.onlyCategories.push(type);
    if (Array.isArray(skipAudits) && skipAudits.length) {
      config.settings = { ...config.settings, skipAudits };
    }
  }
  return config;
}

/**
 *
 * @param {Record<string, any>} success
 * @param {Record<string, any>} failure
 * @returns {number}
 */
function getTotalScore(success, failure) {
  const successCount = Object.keys(success).length;
  const errorCount = Object.keys(failure).length;

  if (successCount + errorCount === 0) {
    return 0;
  }
  return (successCount * 100) / (successCount + errorCount);
}

/**
 *
 * @param {LH.RunnerResult} results
 * @returns {parsedLHResults}
 */
function parseLhResult(results) {
  if (!results || !results.lhr || !results.lhr.audits) {
    throw new Error(`Error fetching lighthouse audit results`);
  }

  const { audits } = results.lhr;
  const errorsObj = {};
  const notApplicableObj = {};
  const successObj = {};

  /**
   *
   * @typedef {{description: LH.Audit.Result.description, details: LH.Audit.Result.details}} auditInfo
   */

  Object.values(audits).forEach(audit => {
    const { id, description, details, score, scoreDisplayMode } = audit;

    /**
     *
     * @type {auditInfo}
     */
    const auditInfo = { description, details };

    if (score) {
      successObj[id] = auditInfo;
    } else if (scoreDisplayMode === 'notApplicable') {
      notApplicableObj[id] = auditInfo;
    } else if (scoreDisplayMode !== 'manual') {
      const snippets = [];
      (details && details.items || []).forEach(item =>
          snippets.push((item.node && item.node.snippet) || '')
        );
      }

      errorsObj[id] = { description, snippets };
    }
  });

  const score = getTotalScore(successObj, errorsObj);

  return {
    audits,
    score,
    /**
     * @param {number} cutoff
     */
    isSuccess(cutoff) {
      assert.ok(
        score >= cutoff,
        `Score ${score} is smaller than expected cutoff score ${cutoff}`
      );
    },
    /**
     * @param {string?} audit
     * @returns {Record<string, auditInfo>}
     */
    success(audit) {
      return audit ? successObj[audit] : successObj;
    },
    /**
     * @param {string?} audit
     * @returns {Record<string, any>}
     */
    errors(audit) {
      return audit ? errorsObj[audit] : errorsObj;
    },
    /**
     * @returns {string}
     */
    errorString() {
      const errorList = [];

      for (const id in errorsObj) {
        const description = errorsObj[id].description;
        const snippets = errorsObj[id].snippets;
        errorList.push(`${description}:\n\t${snippets.join('\n\t')}`);
      }

      return errorList.join('\n\n');
    },
  };
}

/**
 *
 * @param {string} category
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
async function assertScoreByCategory(category, minScore, skipAudits = []) {
  minScore = minScore || 100;

  category = category.toLowerCase();

  const config = getConfigByType(category, skipAudits);

  const results = await this.getLighthouseData(defaultFlags, config).then(
    parseLhResult
  );

  assert.ok(
    results.score >= minScore,
    `${category} score ${results.score} to be greater than ${minScore}`
  );

  return results;
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertPerformanceScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('performance', minScore, [
    ...skipAudits,
    'final-screenshot',
    'is-on-https',
    'screenshot-thumbnails',
  ]);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertAccessibilityScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('accessibility', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} skipAudits
 * @returns {Promise<parsedLHResults>}
 */
function assertBestPracticesScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('best-practices', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertSeoScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('seo', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertPwaScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('pwa', minScore, skipAudits);
}

/**
 *
 * @param {LH_Flags.Flags?} flags
 * @param {LH_Config.Json?} lhConfig
 * @returns {Promise<parsedLHResults>}
 * @legacy
 */
function runLighthouseAudit(flags, lhConfig) {
  return this.getLighthouseData(flags, lhConfig).then(parseLhResult);
}

/**
 * @param {{flags?: LH_Flags.Flags, config?: LH_Config.Json, ignore?:boolean}} options
 * @returns {Promise<{Object[]|[]>}
 */
async function a11yAudit(options = {}) {
  const { flags, config, ignore } = options;

  let violationsFilter; // function to filter violations
  if (typeof ignore === 'function') {
    violationsFilter = violation => !ignore(violation);
  } else if (Array.isArray(ignore)) {
    // e.g. ignore: ['html-has-lang']
    violationsFilter = violation => !ignore.includes(violation.id);
  } else {
    violationsFilter = () => true;
  }

  const result = await this.getLighthouseData(
    flags || defaultFlags,
    config || getConfigByType('accessibility')
  );

  // TODO: with node 12 use Array.prototype.flatMap
  return flatMap(
    result.artifacts.Accessibility.violations.filter(violationsFilter),
    violation => {
      const { nodes, ...violationData } = violation;
      return nodes.map(node => {
        return {
          ...violationData,
          auditId: violationData.id,
          selector: Array.isArray(node.target) ? node.target.join(' ') : '',
          path: node.path,
          snippet: node.snippet,
        };
      });
    }
  );
}

/**
 * @param {LH_Flags.Flags?} flags
 * @param {LH_Config.Json?} config
 * @returns {Promise<LH.RunnerResult>}
 */
async function getLighthouseData(flags, config) {
  if (!lighthouse) {
    throw new Error('Lighthouse requires a newer version of node');
  }
  const devtoolsPort = await this.getChromeDevtoolsPort();
  const options = { port: devtoolsPort, ...flags };
  const url = await this.url();

  return lighthouse(url, options, config);
}

module.exports = {
  a11yAudit,
  assertAccessibilityScore,
  assertBestPracticesScore,
  assertPerformanceScore,
  assertPwaScore,
  assertSeoScore,
  assertScoreByCategory,
  getLighthouseData,
  runLighthouseAudit,
};
<commit_msg>fix: lint tweaks<commit_after>/*
 * Copyright (c) 2015, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

const assert = require('assert');
const flatMap = require('array.prototype.flatmap');

const lighthouse = require('lighthouse');

/**
 * @typedef {import('lighthouse/types/lhr')} LH
 * @typedef {import('lighthouse/types/config').LH.Config} LH_Config
 * @typedef {import('lighthouse/types/externs').SharedFlagsSettings} LH_Flags
 * @typedef {import('lighthouse/types/artifacts').Artifacts} LH_Artifacts
 *
 * @typedef {Object} parsedLHResults
 * @property {number} score
 * @property {LH.Result.audits} audits
 * @property {function(string?): Record<string, any>|Record<string, any>[]} error
 * @property {function(): string} errorString
 * @property {function(number): boolean} isSuccess
 * @property {function(string?): Record<string, auditInfo>|Record<string, auditInfo>[]} success
 */

const defaultConfig = {
  extends: 'lighthouse:default',
  settings: {
    onlyCategories: [],
    throttlingMethod: 'simulate',
  },
};

const defaultFlags = {
  chromeFlags: [
    '--disable-gpu',
    '--headless',
    '--disable-storage-reset',
    '--enable-logging',
    '--disable-device-emulation',
    '--no-sandbox',
  ],
};

/**
 * @param {string} type
 * @param {Array<string>} [skipAudits]
 * @returns {Object}
 */
function getConfigByType(type, skipAudits) {
  const config = JSON.parse(JSON.stringify(defaultConfig));
  if (type) {
    config.settings.onlyCategories.push(type);
    if (Array.isArray(skipAudits) && skipAudits.length) {
      config.settings = { ...config.settings, skipAudits };
    }
  }
  return config;
}

/**
 *
 * @param {Record<string, any>} success
 * @param {Record<string, any>} failure
 * @returns {number}
 */
function getTotalScore(success, failure) {
  const successCount = Object.keys(success).length;
  const errorCount = Object.keys(failure).length;

  if (successCount + errorCount === 0) {
    return 0;
  }
  return (successCount * 100) / (successCount + errorCount);
}

/**
 *
 * @param {LH.RunnerResult} results
 * @returns {parsedLHResults}
 */
function parseLhResult(results) {
  if (!results || !results.lhr || !results.lhr.audits) {
    throw new Error(`Error fetching lighthouse audit results`);
  }

  const { audits } = results.lhr;
  const errorsObj = {};
  const notApplicableObj = {};
  const successObj = {};

  /**
   *
   * @typedef {{description: LH.Audit.Result.description, details: LH.Audit.Result.details}} auditInfo
   */

  Object.values(audits).forEach(audit => {
    const { id, description, details, score, scoreDisplayMode } = audit;

    /**
     *
     * @type {auditInfo}
     */
    const auditInfo = { description, details };
    let snippets;

    if (score) {
      successObj[id] = auditInfo;
    } else if (scoreDisplayMode === 'notApplicable') {
      notApplicableObj[id] = auditInfo;
    } else if (scoreDisplayMode !== 'manual') {
      snippets = [];
      ((details && details.items) || []).forEach(item =>
        snippets.push((item.node && item.node.snippet) || '')
      );
    }

    errorsObj[id] = { description, snippets };
  });

  const score = getTotalScore(successObj, errorsObj);

  return {
    audits,
    score,
    /**
     * @param {number} cutoff
     */
    isSuccess(cutoff) {
      assert.ok(
        score >= cutoff,
        `Score ${score} is smaller than expected cutoff score ${cutoff}`
      );
    },
    /**
     * @param {string?} audit
     * @returns {Record<string, auditInfo>}
     */
    success(audit) {
      return audit ? successObj[audit] : successObj;
    },
    /**
     * @param {string?} audit
     * @returns {Record<string, any>}
     */
    errors(audit) {
      return audit ? errorsObj[audit] : errorsObj;
    },
    /**
     * @returns {string}
     */
    errorString() {
      const errorList = [];

      for (const id in errorsObj) {
        const description = errorsObj[id].description;
        const snippets = errorsObj[id].snippets;
        errorList.push(`${description}:\n\t${snippets.join('\n\t')}`);
      }

      return errorList.join('\n\n');
    },
  };
}

/**
 *
 * @param {string} category
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
async function assertScoreByCategory(category, minScore, skipAudits = []) {
  minScore = minScore || 100;

  category = category.toLowerCase();

  const config = getConfigByType(category, skipAudits);

  const results = await this.getLighthouseData(defaultFlags, config).then(
    parseLhResult
  );

  assert.ok(
    results.score >= minScore,
    `${category} score ${results.score} to be greater than ${minScore}`
  );

  return results;
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertPerformanceScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('performance', minScore, [
    ...skipAudits,
    'final-screenshot',
    'is-on-https',
    'screenshot-thumbnails',
  ]);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertAccessibilityScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('accessibility', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} skipAudits
 * @returns {Promise<parsedLHResults>}
 */
function assertBestPracticesScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('best-practices', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertSeoScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('seo', minScore, skipAudits);
}

/**
 *
 * @param {number} minScore
 * @param {Array<string>} [skipAudits]
 * @returns {Promise<parsedLHResults>}
 */
function assertPwaScore(minScore, skipAudits = []) {
  return this.assertScoreByCategory('pwa', minScore, skipAudits);
}

/**
 *
 * @param {LH_Flags.Flags?} flags
 * @param {LH_Config.Json?} lhConfig
 * @returns {Promise<parsedLHResults>}
 * @legacy
 */
function runLighthouseAudit(flags, lhConfig) {
  return this.getLighthouseData(flags, lhConfig).then(parseLhResult);
}

/**
 * @param {{flags?: LH_Flags.Flags, config?: LH_Config.Json, ignore?:boolean}} options
 * @returns {Promise<{Object[]|[]>}
 */
async function a11yAudit(options = {}) {
  const { flags, config, ignore } = options;

  let violationsFilter; // function to filter violations
  if (typeof ignore === 'function') {
    violationsFilter = violation => !ignore(violation);
  } else if (Array.isArray(ignore)) {
    // e.g. ignore: ['html-has-lang']
    violationsFilter = violation => !ignore.includes(violation.id);
  } else {
    violationsFilter = () => true;
  }

  const result = await this.getLighthouseData(
    flags || defaultFlags,
    config || getConfigByType('accessibility')
  );

  // TODO: with node 12 use Array.prototype.flatMap
  return flatMap(
    result.artifacts.Accessibility.violations.filter(violationsFilter),
    violation => {
      const { nodes, ...violationData } = violation;
      return nodes.map(node => {
        return {
          ...violationData,
          auditId: violationData.id,
          selector: Array.isArray(node.target) ? node.target.join(' ') : '',
          path: node.path,
          snippet: node.snippet,
        };
      });
    }
  );
}

/**
 * @param {LH_Flags.Flags?} flags
 * @param {LH_Config.Json?} config
 * @returns {Promise<LH.RunnerResult>}
 */
async function getLighthouseData(flags, config) {
  if (!lighthouse) {
    throw new Error('Lighthouse requires a newer version of node');
  }
  const devtoolsPort = await this.getChromeDevtoolsPort();
  const options = { port: devtoolsPort, ...flags };
  const url = await this.url();

  return lighthouse(url, options, config);
}

module.exports = {
  a11yAudit,
  assertAccessibilityScore,
  assertBestPracticesScore,
  assertPerformanceScore,
  assertPwaScore,
  assertSeoScore,
  assertScoreByCategory,
  getLighthouseData,
  runLighthouseAudit,
};
<|endoftext|>


<commit_before>var _ = require('lodash')
var async = require('async')

module.exports = function (testModules, helper) {
  var stats = {suiteCount: 0, fileCounts: {}}
  var suite = {
    name: 'global',
    type: 'suite',
    children: _.flatten([
      actionForHook(helper.beforeAll, 'global', 'beforeAll', null, helper.file),
      _.map(testModules, function (exampleGroup) {
        return actionsForExampleGroup(exampleGroup, [], helper, stats)
      }),
      actionForHook(helper.afterAll, 'global', 'afterAll', null, helper.file)
    ]),
    callable: function (cb) {
      async.series(_.map(suite.children, 'callable'), cb)
    }
  }

  return suite
}

var actionsForExampleGroup = function (exampleGroup, ancestors, helper, stats) {
  var name = exampleGroup.name || 'module'
  var suite = {
    name: name,
    type: 'suite',
    children: _.flatten([
      actionForHook(exampleGroup.beforeAll, name, 'beforeAll', null, exampleGroup.file),
      _.map(exampleGroup.items, function (item) {
        if (item.__teenytest__type === 'group') {
          return actionsForExampleGroup(item, ancestors.concat(exampleGroup), helper, stats)
        } else {
          return actionsForTestFunctions(item, ancestors.concat(exampleGroup), helper, stats)
        }
      }),
      actionForHook(exampleGroup.afterAll, name, 'afterAll', null, exampleGroup.file)
    ]),
    callable: function (cb) {
      async.series(_.map(suite.children, 'callable'), cb)
    }
  }

  return suite
}

var actionsForTestFunctions = function (test, ancestors, helper, stats) {
  incrementCounts(stats, test)
  var testActions = {
    name: test.name,
    type: 'test',
    description: test.description(stats.suiteCount, stats.fileCounts[test.file]),
    suiteNames: test.ancestorNames,
    children: _.flatten([
      actionForHook(helper.beforeEach, 'global', 'beforeEach', test.context, helper.file),
      _.map(ancestors, function (ancestor, i) {
        return actionForAncestorHook('beforeEach', ancestor, i, test)
      }),
      actionForTest(test, stats),
      actionForHook(helper.afterEach, 'global', 'afterEach', test.context, helper.file),
      _(ancestors).reverse().map(function (ancestor, i) {
        return actionForAncestorHook('afterEach', ancestor, i, test)
      }).value()
    ]),
    callable: function (cb) {
      async.series(_.map(testActions.children, 'callable'), cb)
    }
  }

  return testActions
}

function actionForAncestorHook (type, ancestor, distance, test) {
  return actionForHook(ancestor[type], (ancestor.name || 'module'),
                       type, test.context, ancestor.file, distance)
}

var actionForHook = function (helperFunc, name, hookType, context, file, distance) {
  var action = {
    name: name,
    type: 'userFunction',
    subType: 'hook',
    hookType: hookType,
    description: 'test hook: ' + name + ' ' + hookType +
                 (file ? ' defined in `' + file + '`' : ''),
    callable: helperFunc.bind(context || {}),
    isAssociatedWithATest: !_.isNil(context),
    distanceFromTest: distance || 0
  }

  return action
}

var actionForTest = function (test, stats) {
  var action = {
    name: test.name,
    type: 'userFunction',
    subType: 'test',
    description: test.description(stats.suiteCount, stats.fileCounts[test.file]),
    callable: test.testFunction.bind(test.context),
    isAssociatedWithATest: true,
    distanceFromTest: 0
  }

  return action
}

function incrementCounts (stats, test) {
  stats.suiteCount++
  if (_.hasIn(stats.fileCounts, test.file)) {
    stats.fileCounts[test.file]++
  } else {
    stats.fileCounts[test.file] = 1
  }
}
<commit_msg>test is associated with tests—ensure errors print<commit_after>var _ = require('lodash')
var async = require('async')

module.exports = function (testModules, helper) {
  var stats = {suiteCount: 0, fileCounts: {}}
  var suite = {
    name: 'global',
    type: 'suite',
    children: _.flatten([
      actionForHook(helper.beforeAll, 'global', 'beforeAll', null, helper.file),
      _.map(testModules, function (exampleGroup) {
        return actionsForExampleGroup(exampleGroup, [], helper, stats)
      }),
      actionForHook(helper.afterAll, 'global', 'afterAll', null, helper.file)
    ]),
    callable: function (cb) {
      async.series(_.map(suite.children, 'callable'), cb)
    }
  }

  return suite
}

var actionsForExampleGroup = function (exampleGroup, ancestors, helper, stats) {
  var name = exampleGroup.name || 'module'
  var suite = {
    name: name,
    type: 'suite',
    children: _.flatten([
      actionForHook(exampleGroup.beforeAll, name, 'beforeAll', null, exampleGroup.file),
      _.map(exampleGroup.items, function (item) {
        if (item.__teenytest__type === 'group') {
          return actionsForExampleGroup(item, ancestors.concat(exampleGroup), helper, stats)
        } else {
          return actionsForTestFunctions(item, ancestors.concat(exampleGroup), helper, stats)
        }
      }),
      actionForHook(exampleGroup.afterAll, name, 'afterAll', null, exampleGroup.file)
    ]),
    callable: function (cb) {
      async.series(_.map(suite.children, 'callable'), cb)
    }
  }

  return suite
}

var actionsForTestFunctions = function (test, ancestors, helper, stats) {
  incrementCounts(stats, test)
  var testActions = {
    name: test.name,
    type: 'test',
    description: test.description(stats.suiteCount, stats.fileCounts[test.file]),
    suiteNames: test.ancestorNames,
    isAssociatedWithATest: true,
    children: _.flatten([
      actionForHook(helper.beforeEach, 'global', 'beforeEach', test.context, helper.file),
      _.map(ancestors, function (ancestor, i) {
        return actionForAncestorHook('beforeEach', ancestor, i, test)
      }),
      actionForTest(test, stats),
      actionForHook(helper.afterEach, 'global', 'afterEach', test.context, helper.file),
      _(ancestors).reverse().map(function (ancestor, i) {
        return actionForAncestorHook('afterEach', ancestor, i, test)
      }).value()
    ]),
    callable: function (cb) {
      async.series(_.map(testActions.children, 'callable'), cb)
    }
  }

  return testActions
}

function actionForAncestorHook (type, ancestor, distanceFromTest, test) {
  return actionForHook(ancestor[type], (ancestor.name || 'module'),
                       type, test.context, ancestor.file, distanceFromTest)
}

var actionForHook = function (helperFunc, name, hookType, context, file, distance) {
  var action = {
    name: name,
    type: 'userFunction',
    subType: 'hook',
    hookType: hookType,
    description: 'test hook: ' + name + ' ' + hookType +
                 (file ? ' defined in `' + file + '`' : ''),
    callable: helperFunc.bind(context || {}),
    isAssociatedWithATest: !_.isNil(context),
    distanceFromTest: distance || 0
  }

  return action
}

var actionForTest = function (test, stats) {
  var action = {
    name: test.name,
    type: 'userFunction',
    subType: 'test',
    description: test.description(stats.suiteCount, stats.fileCounts[test.file]),
    callable: test.testFunction.bind(test.context),
    isAssociatedWithATest: true,
    distanceFromTest: 0
  }

  return action
}

function incrementCounts (stats, test) {
  stats.suiteCount++
  if (_.hasIn(stats.fileCounts, test.file)) {
    stats.fileCounts[test.file]++
  } else {
    stats.fileCounts[test.file] = 1
  }
}
<|endoftext|>


<commit_before>'use strict'

var path = require('path')
var fs = require('fs')
var Promise = require('bluebird')
var childProcess = require('child_process')
var platform = require('os').platform()

var description = 'WINDOWS ONLY - Stop and uninstall service'
var command = 'uninstall'

exports.command = command
exports.description = description

exports.builder = function (yargs) {
  return (
    yargs
    .usage(description + '\nUsage: $0 ' + command)
    .check(function (argv, hash) {
      if (argv.serverUrl) {
        throw new Error('serverUrl option is not supported in this command')
      }

      return true
    })
  )
}

exports.handler = function (argv) {
  return new Promise(function (resolve, reject) {
    var cwd = argv.context.cwd
    var existsPackageJson = fs.existsSync(path.join(cwd, './package.json'))
    var userPkg
    var serviceName
    var pathToWinser

    console.log('Platform is ' + platform)

    if (platform !== 'win32') {
      console.log('Unstalling windows service for app only works on windows platforms..')
      return resolve()
    }

    if (!existsPackageJson) {
      return reject(new Error('To uninstall windows service for app you need a package.json file..'))
    }

    userPkg = require(path.join(cwd, './package.json'))

    if (!userPkg.name) {
      return reject(new Error('To uninstall windows service for app you need a "name" field in package.json file..'))
    }

    serviceName = userPkg.name

    console.log('Uninstalling windows service "' + serviceName + '".')

    try {
      pathToWinser = path.dirname(require.resolve('winser'))
      pathToWinser = path.resolve(pathToWinser, '../.bin/winser.cmd')
      pathToWinser = '"' + pathToWinser + '"'
    } catch (e) {
      if (e.code === 'MODULE_NOT_FOUND') {
        return reject(new Error('couldn\'t find "winser" package'))
      }

      return reject(new Error('Unexpected error happened: ' + e.message))
    }

    childProcess.exec(pathToWinser + ' -r -x', {
      cwd: cwd
    }, function (error, stdout, stderr) {
      if (error) {
        return reject(error)
      }

      console.log('Windows service "' + serviceName + '" uninstalled')
      resolve()
    })
  })
}
<commit_msg>update uninstall command to get winser path from passed cwd<commit_after>'use strict'

var path = require('path')
var fs = require('fs')
var Promise = require('bluebird')
var childProcess = require('child_process')
var platform = require('os').platform()

var description = 'WINDOWS ONLY - Stop and uninstall service'
var command = 'uninstall'

exports.command = command
exports.description = description

exports.builder = function (yargs) {
  return (
    yargs
    .usage(description + '\nUsage: $0 ' + command)
    .check(function (argv, hash) {
      if (argv.serverUrl) {
        throw new Error('serverUrl option is not supported in this command')
      }

      return true
    })
  )
}

exports.handler = function (argv) {
  return new Promise(function (resolve, reject) {
    var cwd = argv.context.cwd
    var existsPackageJson = fs.existsSync(path.join(cwd, './package.json'))
    var userPkg
    var serviceName
    var pathToWinser

    console.log('Platform is ' + platform)

    if (platform !== 'win32') {
      console.log('Unstalling windows service for app only works on windows platforms..')

      return resolve({
        uninstalled: false,
        serviceName: null
      })
    }

    if (!existsPackageJson) {
      return reject(new Error('To uninstall windows service for app you need a package.json file..'))
    }

    userPkg = require(path.join(cwd, './package.json'))

    if (!userPkg.name) {
      return reject(new Error('To uninstall windows service for app you need a "name" field in package.json file..'))
    }

    serviceName = userPkg.name

    console.log('Uninstalling windows service "' + serviceName + '".')

    try {
      pathToWinser = path.join(cwd, 'node_modules/winser/bin/winser')

      pathToWinser = require.resolve(pathToWinser)

      pathToWinser = '"' + process.execPath + '" "' + pathToWinser + '"'
    } catch (e) {
      if (e.code === 'MODULE_NOT_FOUND') {
        return reject(new Error('couldn\'t find "winser" package'))
      }

      return reject(new Error('Unexpected error happened: ' + e.message))
    }

    childProcess.exec(pathToWinser + ' -r -x', {
      cwd: cwd
    }, function (error, stdout, stderr) {
      if (error) {
        return reject(error)
      }

      console.log('Windows service "' + serviceName + '" uninstalled')

      resolve({
        uninstalled: true,
        serviceName: serviceName
      })
    })
  })
}
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var block = require('./comments-block');
var item = require('./comment-item');
var serialize = require('serialize');
var request = require('request');
var toArray = require('to-array');
var Emitter = require('emitter');
var domify = require('domify');
var events = require('events');
var truncate = require('truncate');
var o = require('query');
var classes = require('classes');
var closest = require('closest');
var citizen = require('citizen');
var t = require('t');
var log = require('debug')('democracyos:comments-view');

/**
 * Expose comments view
 */

module.exports = CommentsView;

/**
 * View constructor
 *
 * @param {String} context
 * @param {String} reference
 * @constructor
 */

function CommentsView(context, reference) {
  if (!(this instanceof CommentsView)) {
    return new CommentsView(context, reference);
  };

  this.context = context;
  this.reference = reference;

  this.el = domify(block({
    citizen: citizen,
    context: context,
    reference: reference,
    t: t,
    truncate: truncate
  }));

  this.comments = [];

  this.events = events(this.el, this);
  this.switchOn();
}

/**
 * Mixin Emitter
 */

Emitter(CommentsView.prototype);

/**
 * Description
 */

CommentsView.prototype.render = function(el) {
  if (1 === arguments.length) {

    // if string, then query element
    if ('string' === typeof el) {
      el = document.querySelector(el);
    };

    // if it's not currently inserted
    // at `el`, then append to `el`
    if (el !== this.el.parentNode) {
      el.appendChild(this.el);
    };

    // !!!: Should we return different things
    // on different conditions?
    // Or should we be consistent with
    // render returning always `this.el`
    return this;
  };

  return this.el;
}

/**
 * Switch on events
 *
 * @api public
 */

CommentsView.prototype.switchOn = function() {
  this.events.bind('submit form.comment-form');
  this.events.bind('click .toggle-deleted-comments', 'toggleDeletedComments');
  this.events.bind('click a.like', 'onlike');
  this.events.bind('click a.dislike', 'ondislike');
  this.events.bind('click a.flag', 'onflag');
  this.events.bind('click span.show-spam a', 'onshowspam');
  this.events.bind('click .comment-text a.read-more', 'readmore');
  this.on('fetch', this.load.bind(this));
  this.on('post', this.add.bind(this));
}

/**
 * Read full comment
 *
 * @api public
 */

CommentsView.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-id]');
  var id = commentContainer.getAttribute('data-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('.comment-text', commentContainer);

  commentText.innerHTML = comment.text;
}

/**
 * Switch off events
 *
 * @api public
 */

CommentsView.prototype.switchOff = function() {
  this.off('fetch');
  this.off('post');
  this.events.unbind();
}

/**
 * Submit event handler
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.onsubmit = function(ev) {
  ev.preventDefault();

  var data = serialize.object(ev.target);
  var errors = this.validate(data);
  this.errors(errors)
  if (errors.length) return log('Found errors: %o', errors);
  this.emit('submit', data);
  this.post(data);
}

/**
 * Validate form's fields
 *
 * @param {Object} data
 * @return {Array} of Errors
 * @api public
 */
CommentsView.prototype.validate = function(data) {
  var errors = [];
  if (!data.text) {
    errors.push('Comment cannot be empty');
  };
  if (data.text.length > 4096) {
    errors.push('Comment is limited to 4096 characters');
  };
  return errors;
}

/**
 * Fill errors list
 *
 * @param {Array} errors
 * @api public
 */

CommentsView.prototype.errors = function(errors) {
  var span = o('span.help-text.form-errors', this.el);
  errors = errors || [];
  classes(o('form', this.el)).remove('has-error');

  span.innerText = '';

  span.innerText = '';
  errors.forEach(function(err) {
    span.innerText += t(err);
  });

  classes(o('form', this.el)).add('has-error');
}

/**
 * Action like comment
 */

CommentsView.prototype.onlike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(comment.querySelector('a.like')).has('selected');
  var disliked = classes(comment.querySelector('a.dislike')).has('selected');

  classes(target).add('selected');
  classes(comment.querySelector('a.dislike')).remove('selected');

  var counter = comment.querySelector('.comment-counter');
  var count = parseInt(counter.innerText, 10) || 0;
  count += disliked ? 2 : (liked ? 0 : 1);
  counter.innerText = count;

  request
  .post('/api/comment/:id/upvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull upvote %s', id);
  });
}

/**
 * Action dislike comment
 */

CommentsView.prototype.ondislike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(comment.querySelector('a.like')).has('selected');
  var disliked = classes(comment.querySelector('a.dislike')).has('selected');

  classes(target).add('selected');
  classes(comment.querySelector('a.like')).remove('selected');

  var counter = comment.querySelector('.comment-counter');
  var count = parseInt(counter.innerText, 10) || 0;
  count -= liked ? 2 : (disliked ? 0 : 1);
  counter.innerText = count;

  request
  .post('/api/comment/:id/downvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull downvote %s', id);
  });
}

/**
 * Action flag comment
 */

CommentsView.prototype.onflag = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');

  classes(target).toggle('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');
  flagged ? classes(comment).remove('spam') : classes(comment).add('spam');

  request
  .post('/api/comment/:id/:action'.replace(':id', id).replace(':action', flagged ? 'unflag' : 'flag'))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull %s as spam %s', flagged ? 'unflag' : 'flag', id);
  });
}

/**
 * Show spam comment
 */

CommentsView.prototype.onshowspam = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');

  classes(comment).remove('spam');
}

/**
 * Fetch comments
 *
 * @api public
 */

CommentsView.prototype.fetch = function() {
  var view = this;
  request
  .get(this.url() + '/comments')
  .end(function(err, res) {
    if (err) {
      log('Fetch error: %s', err);
      return;
    };
    if (!res.ok) {
      log('Fetch error: %s', res.error);
      return;
    };
    if (res.body.error) {
      log('Fetch response error: %s', res.body.error);
      return;
    };
    view.emit('fetch', res.body);
  });
}

/**
 * Post a comment
 *
 * @param {Object} data
 * @api public
 */

CommentsView.prototype.post = function(data) {
  var view = this;
  data.context = this.context;
  data.reference = this.reference;

  request
  .post(this.url() + "/comment")
  .send({ comment: data })
  .end(function(err, res) {

    if (res.body && res.body.error) {
      return log('Fetch response error: %s', res.body.error), view.errors([res.body.error]);
    };

    if (err || !res.ok) return log('Fetch error: %s', err || res.error);

    view.emit('post', res.body);
    view.clear();
  });
}

/**
 * Load comments in view's `el`
 *
 * @param {Array} comments
 * @api public
 */

CommentsView.prototype.load = function(comments) {
  if (!comments.length) return;

  log('load %o', comments);

  this.comments.push.apply(this.comments, comments);

  this.comments
  .reverse()
  .sort(function(a, b) {
    var first = a.upvotes.length - a.downvotes.length;
    var second = b.upvotes.length - b.downvotes.length;
    var sort = (first - second);
    return 0 !== sort ? sort / Math.abs(sort) : sort;
  })
  .forEach(function(comment) {
    this.add(comment);
  }, this);

  this.emit('load');
}

/**
 * Add comment to block list
 */

CommentsView.prototype.add = function(comment) {
  var blockEl = this.el.querySelector('ul.media-list.comment-list.main-list');
  var deletedBlockEl = this.el.querySelector('ul.media-list.comment-list.deleted-list');
  var commentEl = domify(item({ comment: comment, citizen: citizen, t: t, truncate: truncate }));

  if (!comment.deleted) {
    if (blockEl.children.length) {
      blockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      blockEl.appendChild(commentEl);
    }
  } else {
    classes(this.el.querySelector('.toggle-deleted-comments')).remove('hide');
    if (deletedBlockEl.children.length) {
      deletedBlockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      deletedBlockEl.appendChild(commentEl);
    }
  }
}

/**
 * Clear form's inputs and textareas.
 *
 * @api public
 */

CommentsView.prototype.clear = function() {
  var els = this.el.querySelectorAll('input[type="text"], textarea');

  toArray(els).forEach(function(el) {
    switch ((el.tagName || el.nodeName).toLowerCase()) {
      case 'input':
      case 'textarea':
        el.value = '';
        break;
      case 'select':
        toArray(el.children)
          .forEach(function(option, i) {
            if (!i) return option.setAttribute('selected', 'selected');
            option.removeAttribute('selected');
          });
        break;
    }
  });
}

/**
 * Toggle deleted comments box
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.toggleDeletedComments = function(ev) {
  ev.preventDefault();

  classes(this.el.querySelector('ul.comment-list.deleted-list')).toggle('hide');
};

/**
 * Get api route
 */

CommentsView.prototype.url = function() {
  return "/api/{context}/{reference}"
    .replace('{context}', this.context)
    .replace('{reference}', this.reference);
}

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<commit_msg>[comments-view] - Fix bug for comments not showing more<commit_after>/**
 * Module dependencies.
 */

var block = require('./comments-block');
var item = require('./comment-item');
var serialize = require('serialize');
var request = require('request');
var toArray = require('to-array');
var Emitter = require('emitter');
var domify = require('domify');
var events = require('events');
var truncate = require('truncate');
var o = require('query');
var classes = require('classes');
var closest = require('closest');
var citizen = require('citizen');
var t = require('t');
var log = require('debug')('democracyos:comments-view');

/**
 * Expose comments view
 */

module.exports = CommentsView;

/**
 * View constructor
 *
 * @param {String} context
 * @param {String} reference
 * @constructor
 */

function CommentsView(context, reference) {
  if (!(this instanceof CommentsView)) {
    return new CommentsView(context, reference);
  };

  this.context = context;
  this.reference = reference;

  this.el = domify(block({
    citizen: citizen,
    context: context,
    reference: reference,
    t: t,
    truncate: truncate
  }));

  this.comments = [];

  this.events = events(this.el, this);
  this.switchOn();
}

/**
 * Mixin Emitter
 */

Emitter(CommentsView.prototype);

/**
 * Description
 */

CommentsView.prototype.render = function(el) {
  if (1 === arguments.length) {

    // if string, then query element
    if ('string' === typeof el) {
      el = document.querySelector(el);
    };

    // if it's not currently inserted
    // at `el`, then append to `el`
    if (el !== this.el.parentNode) {
      el.appendChild(this.el);
    };

    // !!!: Should we return different things
    // on different conditions?
    // Or should we be consistent with
    // render returning always `this.el`
    return this;
  };

  return this.el;
}

/**
 * Switch on events
 *
 * @api public
 */

CommentsView.prototype.switchOn = function() {
  this.events.bind('submit form.comment-form');
  this.events.bind('click .toggle-deleted-comments', 'toggleDeletedComments');
  this.events.bind('click a.like', 'onlike');
  this.events.bind('click a.dislike', 'ondislike');
  this.events.bind('click a.flag', 'onflag');
  this.events.bind('click span.show-spam a', 'onshowspam');
  this.events.bind('click .comment-text a.read-more', 'readmore');
  this.on('fetch', this.load.bind(this));
  this.on('post', this.add.bind(this));
}

/**
 * Read full comment
 *
 * @api public
 */

CommentsView.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-id]');
  var id = commentContainer.getAttribute('data-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('.comment-text', commentContainer);

  commentText.innerHTML = comment.text;
}

/**
 * Switch off events
 *
 * @api public
 */

CommentsView.prototype.switchOff = function() {
  this.off('fetch');
  this.off('post');
  this.events.unbind();
}

/**
 * Submit event handler
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.onsubmit = function(ev) {
  ev.preventDefault();

  var data = serialize.object(ev.target);
  var errors = this.validate(data);
  this.errors(errors)
  if (errors.length) return log('Found errors: %o', errors);
  this.emit('submit', data);
  this.post(data);
}

/**
 * Validate form's fields
 *
 * @param {Object} data
 * @return {Array} of Errors
 * @api public
 */
CommentsView.prototype.validate = function(data) {
  var errors = [];
  if (!data.text) {
    errors.push('Comment cannot be empty');
  };
  if (data.text.length > 4096) {
    errors.push('Comment is limited to 4096 characters');
  };
  return errors;
}

/**
 * Fill errors list
 *
 * @param {Array} errors
 * @api public
 */

CommentsView.prototype.errors = function(errors) {
  var span = o('span.help-text.form-errors', this.el);
  errors = errors || [];
  classes(o('form', this.el)).remove('has-error');

  span.innerText = '';

  span.innerText = '';
  errors.forEach(function(err) {
    span.innerText += t(err);
  });

  classes(o('form', this.el)).add('has-error');
}

/**
 * Action like comment
 */

CommentsView.prototype.onlike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(comment.querySelector('a.like')).has('selected');
  var disliked = classes(comment.querySelector('a.dislike')).has('selected');

  classes(target).add('selected');
  classes(comment.querySelector('a.dislike')).remove('selected');

  var counter = comment.querySelector('.comment-counter');
  var count = parseInt(counter.innerText, 10) || 0;
  count += disliked ? 2 : (liked ? 0 : 1);
  counter.innerText = count;

  request
  .post('/api/comment/:id/upvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull upvote %s', id);
  });
}

/**
 * Action dislike comment
 */

CommentsView.prototype.ondislike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(comment.querySelector('a.like')).has('selected');
  var disliked = classes(comment.querySelector('a.dislike')).has('selected');

  classes(target).add('selected');
  classes(comment.querySelector('a.like')).remove('selected');

  var counter = comment.querySelector('.comment-counter');
  var count = parseInt(counter.innerText, 10) || 0;
  count -= liked ? 2 : (disliked ? 0 : 1);
  counter.innerText = count;

  request
  .post('/api/comment/:id/downvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull downvote %s', id);
  });
}

/**
 * Action flag comment
 */

CommentsView.prototype.onflag = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');

  classes(target).toggle('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');
  flagged ? classes(comment).remove('spam') : classes(comment).add('spam');

  request
  .post('/api/comment/:id/:action'.replace(':id', id).replace(':action', flagged ? 'unflag' : 'flag'))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull %s as spam %s', flagged ? 'unflag' : 'flag', id);
  });
}

/**
 * Show spam comment
 */

CommentsView.prototype.onshowspam = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');

  classes(comment).remove('spam');
}

/**
 * Fetch comments
 *
 * @api public
 */

CommentsView.prototype.fetch = function() {
  var view = this;
  request
  .get(this.url() + '/comments')
  .end(function(err, res) {
    if (err) {
      log('Fetch error: %s', err);
      return;
    };
    if (!res.ok) {
      log('Fetch error: %s', res.error);
      return;
    };
    if (res.body.error) {
      log('Fetch response error: %s', res.body.error);
      return;
    };
    view.emit('fetch', res.body);
  });
}

/**
 * Post a comment
 *
 * @param {Object} data
 * @api public
 */

CommentsView.prototype.post = function(data) {
  var view = this;
  data.context = this.context;
  data.reference = this.reference;

  request
  .post(this.url() + "/comment")
  .send({ comment: data })
  .end(function(err, res) {

    if (res.body && res.body.error) {
      return log('Fetch response error: %s', res.body.error), view.errors([res.body.error]);
    };

    if (err || !res.ok) return log('Fetch error: %s', err || res.error);

    view.emit('post', res.body);
    view.comments.push(res.body);
    view.clear();
  });
}

/**
 * Load comments in view's `el`
 *
 * @param {Array} comments
 * @api public
 */

CommentsView.prototype.load = function(comments) {
  if (!comments.length) return;

  log('load %o', comments);

  this.comments.push.apply(this.comments, comments);

  this.comments
  .reverse()
  .sort(function(a, b) {
    var first = a.upvotes.length - a.downvotes.length;
    var second = b.upvotes.length - b.downvotes.length;
    var sort = (first - second);
    return 0 !== sort ? sort / Math.abs(sort) : sort;
  })
  .forEach(function(comment) {
    this.add(comment);
  }, this);

  this.emit('load');
}

/**
 * Add comment to block list
 */

CommentsView.prototype.add = function(comment) {
  var blockEl = this.el.querySelector('ul.media-list.comment-list.main-list');
  var deletedBlockEl = this.el.querySelector('ul.media-list.comment-list.deleted-list');
  var commentEl = domify(item({ comment: comment, citizen: citizen, t: t, truncate: truncate }));

  if (!comment.deleted) {
    if (blockEl.children.length) {
      blockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      blockEl.appendChild(commentEl);
    }
  } else {
    classes(this.el.querySelector('.toggle-deleted-comments')).remove('hide');
    if (deletedBlockEl.children.length) {
      deletedBlockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      deletedBlockEl.appendChild(commentEl);
    }
  }
}

/**
 * Clear form's inputs and textareas.
 *
 * @api public
 */

CommentsView.prototype.clear = function() {
  var els = this.el.querySelectorAll('input[type="text"], textarea');

  toArray(els).forEach(function(el) {
    switch ((el.tagName || el.nodeName).toLowerCase()) {
      case 'input':
      case 'textarea':
        el.value = '';
        break;
      case 'select':
        toArray(el.children)
          .forEach(function(option, i) {
            if (!i) return option.setAttribute('selected', 'selected');
            option.removeAttribute('selected');
          });
        break;
    }
  });
}

/**
 * Toggle deleted comments box
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.toggleDeletedComments = function(ev) {
  ev.preventDefault();

  classes(this.el.querySelector('ul.comment-list.deleted-list')).toggle('hide');
};

/**
 * Get api route
 */

CommentsView.prototype.url = function() {
  return "/api/{context}/{reference}"
    .replace('{context}', this.context)
    .replace('{reference}', this.reference);
}

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');

function template(name, subdir = 'default/') {
  const shortName = `${name.replace(/([A-Z])/g, '-$1').toLowerCase()}.hbs`;
  const hbsContents = fs.readFileSync(
    // Long path is so that it works whether code is run from lib or dist
    path.resolve(__dirname, '../../lib/config/templates/', subdir, shortName),
    'utf8'
  );
  // Strip off any trailing line break
  return hbsContents.replace(/\n$/, '');
}

module.exports = {
  getOptions,
};

const options = [
  {
    name: 'enabled',
    description: 'Enable or disable renovate',
    type: 'boolean',
  },
  {
    name: 'logFile',
    description: 'Log file path',
    level: 'global',
    type: 'string',
  },
  {
    name: 'logFileLevel',
    description: 'Log file log level',
    level: 'global',
    type: 'string',
    default: 'debug',
  },
  {
    name: 'timezone',
    description:
      '[IANA Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)',
    type: 'string',
  },
  {
    name: 'schedule',
    description: 'Times of day/week to renovate',
    type: 'list',
  },
  {
    name: 'onboarding',
    description: 'Require a Configuration PR first',
    level: 'repository',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'platform',
    description: 'Platform type of repository',
    level: 'repository',
    type: 'string',
    default: 'github',
    onboarding: false,
  },
  {
    name: 'endpoint',
    description: 'Custom endpoint to use',
    level: 'repository',
    type: 'string',
    onboarding: false,
  },
  {
    name: 'token',
    description: 'Repository Auth Token',
    level: 'repository',
    type: 'string',
    onboarding: false,
  },
  {
    name: 'autodiscover',
    description: 'Autodiscover all repositories',
    level: 'repository',
    type: 'boolean',
    default: false,
    onboarding: false,
  },
  {
    name: 'githubAppId',
    description: 'GitHub App ID (enables GitHub App functionality if set)',
    level: 'global',
    type: 'integer',
  },
  {
    name: 'githubAppKey',
    description: 'GitHub App Private Key (.pem file contents)',
    level: 'global',
    type: 'string',
  },
  {
    name: 'repositories',
    description: 'List of Repositories',
    level: 'global',
    type: 'list',
    cli: false,
  },
  {
    name: 'packageFiles',
    description: 'Package file paths',
    type: 'list',
    level: 'repository',
  },
  {
    name: 'depTypes',
    description: 'Dependency types',
    level: 'packageFile',
    type: 'list',
    default: [
      { depType: 'dependencies', semanticPrefix: 'fix: ' },
      'devDependencies',
      'optionalDependencies',
    ],
  },
  // depType
  {
    name: 'ignoreDeps',
    description: 'Dependencies to ignore',
    type: 'list',
    level: 'depType',
  },
  {
    name: 'packages',
    description: 'Package Rules',
    type: 'list',
    level: 'depType',
    cli: false,
    env: false,
    onboarding: false,
  },
  // Version behaviour
  {
    name: 'pinVersions',
    description: 'Convert ranged versions in package.json to pinned versions',
    type: 'boolean',
  },
  {
    name: 'separateMajorReleases',
    description:
      'If set to false, it will upgrade dependencies to latest release only, and not separate major/minor branches',
    type: 'boolean',
  },
  {
    name: 'ignoreFuture',
    description: 'Ignore versions tagged as "future"',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'ignoreUnstable',
    description: 'Ignore versions with unstable semver',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'respectLatest',
    description: 'Ignore versions newer than npm "latest" version',
    type: 'boolean',
    onboarding: false,
  },
  // Semantic commit / Semantic release
  {
    name: 'semanticCommits',
    description: 'Enable semantic commit prefixes for commits and PR titles',
    type: 'boolean',
    default: false,
  },
  {
    name: 'semanticPrefix',
    description: 'Prefix to use if semantic commits are enabled',
    type: 'string',
    default: 'chore: ',
  },
  // PR Behaviour
  {
    name: 'recreateClosed',
    description: 'Recreate PRs even if same ones were closed previously',
    type: 'boolean',
    default: false,
    onboarding: false,
  },
  {
    name: 'rebaseStalePrs',
    description: 'Rebase stale PRs (GitHub only)',
    type: 'boolean',
    default: false,
  },
  {
    name: 'prCreation',
    description:
      'When to create the PR for a branch. Values: immediate, not-pending, status-success.',
    type: 'string',
    default: 'immediate',
  },
  // Automatic merging
  {
    name: 'automerge',
    description:
      'What types of upgrades to merge to base branch automatically. Values: none, minor or any',
    type: 'string',
    default: 'none',
  },
  {
    name: 'automergeType',
    description:
      'How to automerge - "branch-merge-commit", "branch-push" or "pr". Branch support is GitHub-only',
    type: 'string',
    default: 'pr',
    onboarding: false,
  },
  // Default templates
  {
    name: 'branchName',
    description: 'Branch name template',
    type: 'string',
    default: template('branchName'),
    cli: false,
  },
  {
    name: 'commitMessage',
    description: 'Commit message template',
    type: 'string',
    default: template('commitMessage'),
    cli: false,
  },
  {
    name: 'prTitle',
    description: 'Pull Request title template',
    type: 'string',
    default: template('prTitle'),
    cli: false,
    onboarding: false,
  },
  {
    name: 'prBody',
    description: 'Pull Request body template',
    type: 'string',
    default: template('prBody'),
    cli: false,
    onboarding: false,
  },
  // Yarn Lock Maintenance
  {
    name: 'yarnCacheFolder',
    description:
      'Location of yarn cache folder to use. Set to empty string to disable',
    level: 'global',
    type: 'string',
    default: '/tmp/yarn-cache',
  },
  {
    name: 'lockFileMaintenance',
    description: 'Configuration for lock file maintenance',
    level: 'packageFile',
    type: 'json',
    default: {
      enabled: true,
      branchName: 'renovate/lock-files',
      commitMessage: '{{semanticPrefix}}Update lock file',
      prTitle: '{{semanticPrefix}}Lock file maintenance',
      prBody: 'This PR regenerates lock files to keep them up-to-date.',
      schedule: 'before 5am on monday',
    },
    cli: false,
    env: false,
    onboarding: false,
    mergeable: true,
  },
  // Dependency Groups
  {
    name: 'lazyGrouping',
    description: 'Use group names only when multiple dependencies upgraded',
    type: 'boolean',
    default: true,
    onboarding: false,
  },
  {
    name: 'groupName',
    description: 'Human understandable name for the dependency group',
    type: 'string',
    default: null,
    onboarding: false,
  },
  {
    name: 'groupSlug',
    description:
      'Slug to use for group (e.g. in branch name). Will be calculated from groupName if null',
    type: 'string',
    default: null,
    onboarding: false,
  },
  {
    name: 'group',
    description: 'Config if groupName is enabled',
    type: 'json',
    default: {
      branchName: template('branchName', 'group'),
      commitMessage: template('commitMessage', 'group'),
      prTitle: template('prTitle', 'group'),
      prBody: template('prBody', 'group'),
    },
    cli: false,
    env: false,
    onboarding: false,
    mergeable: true,
  },
  // Pull Request options
  {
    name: 'labels',
    description: 'Labels to add to Pull Request',
    type: 'list',
  },
  {
    name: 'assignees',
    description: 'Assignees for Pull Request',
    type: 'list',
  },
  {
    name: 'reviewers',
    description: 'Requested reviewers for Pull Requests (GitHub only)',
    type: 'list',
  },
  // Debug options
  {
    name: 'logLevel',
    description: 'Logging level',
    level: 'global',
    type: 'string',
    default: 'info',
    env: 'LOG_LEVEL',
  },
];

function getOptions() {
  return options;
}
<commit_msg>fix: relative lib require in config definitions<commit_after>const fs = require('fs');
const path = require('path');

function template(name, subdir = 'default/') {
  const shortName = `${name.replace(/([A-Z])/g, '-$1').toLowerCase()}.hbs`;
  const hbsContents = fs.readFileSync(
    // Long path is so that it works whether code is run from lib or dist
    path.resolve(__dirname, '../config/templates/', subdir, shortName),
    'utf8'
  );
  // Strip off any trailing line break
  return hbsContents.replace(/\n$/, '');
}

module.exports = {
  getOptions,
};

const options = [
  {
    name: 'enabled',
    description: 'Enable or disable renovate',
    type: 'boolean',
  },
  {
    name: 'logFile',
    description: 'Log file path',
    level: 'global',
    type: 'string',
  },
  {
    name: 'logFileLevel',
    description: 'Log file log level',
    level: 'global',
    type: 'string',
    default: 'debug',
  },
  {
    name: 'timezone',
    description:
      '[IANA Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)',
    type: 'string',
  },
  {
    name: 'schedule',
    description: 'Times of day/week to renovate',
    type: 'list',
  },
  {
    name: 'onboarding',
    description: 'Require a Configuration PR first',
    level: 'repository',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'platform',
    description: 'Platform type of repository',
    level: 'repository',
    type: 'string',
    default: 'github',
    onboarding: false,
  },
  {
    name: 'endpoint',
    description: 'Custom endpoint to use',
    level: 'repository',
    type: 'string',
    onboarding: false,
  },
  {
    name: 'token',
    description: 'Repository Auth Token',
    level: 'repository',
    type: 'string',
    onboarding: false,
  },
  {
    name: 'autodiscover',
    description: 'Autodiscover all repositories',
    level: 'repository',
    type: 'boolean',
    default: false,
    onboarding: false,
  },
  {
    name: 'githubAppId',
    description: 'GitHub App ID (enables GitHub App functionality if set)',
    level: 'global',
    type: 'integer',
  },
  {
    name: 'githubAppKey',
    description: 'GitHub App Private Key (.pem file contents)',
    level: 'global',
    type: 'string',
  },
  {
    name: 'repositories',
    description: 'List of Repositories',
    level: 'global',
    type: 'list',
    cli: false,
  },
  {
    name: 'packageFiles',
    description: 'Package file paths',
    type: 'list',
    level: 'repository',
  },
  {
    name: 'depTypes',
    description: 'Dependency types',
    level: 'packageFile',
    type: 'list',
    default: [
      { depType: 'dependencies', semanticPrefix: 'fix: ' },
      'devDependencies',
      'optionalDependencies',
    ],
  },
  // depType
  {
    name: 'ignoreDeps',
    description: 'Dependencies to ignore',
    type: 'list',
    level: 'depType',
  },
  {
    name: 'packages',
    description: 'Package Rules',
    type: 'list',
    level: 'depType',
    cli: false,
    env: false,
    onboarding: false,
  },
  // Version behaviour
  {
    name: 'pinVersions',
    description: 'Convert ranged versions in package.json to pinned versions',
    type: 'boolean',
  },
  {
    name: 'separateMajorReleases',
    description:
      'If set to false, it will upgrade dependencies to latest release only, and not separate major/minor branches',
    type: 'boolean',
  },
  {
    name: 'ignoreFuture',
    description: 'Ignore versions tagged as "future"',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'ignoreUnstable',
    description: 'Ignore versions with unstable semver',
    type: 'boolean',
    onboarding: false,
  },
  {
    name: 'respectLatest',
    description: 'Ignore versions newer than npm "latest" version',
    type: 'boolean',
    onboarding: false,
  },
  // Semantic commit / Semantic release
  {
    name: 'semanticCommits',
    description: 'Enable semantic commit prefixes for commits and PR titles',
    type: 'boolean',
    default: false,
  },
  {
    name: 'semanticPrefix',
    description: 'Prefix to use if semantic commits are enabled',
    type: 'string',
    default: 'chore: ',
  },
  // PR Behaviour
  {
    name: 'recreateClosed',
    description: 'Recreate PRs even if same ones were closed previously',
    type: 'boolean',
    default: false,
    onboarding: false,
  },
  {
    name: 'rebaseStalePrs',
    description: 'Rebase stale PRs (GitHub only)',
    type: 'boolean',
    default: false,
  },
  {
    name: 'prCreation',
    description:
      'When to create the PR for a branch. Values: immediate, not-pending, status-success.',
    type: 'string',
    default: 'immediate',
  },
  // Automatic merging
  {
    name: 'automerge',
    description:
      'What types of upgrades to merge to base branch automatically. Values: none, minor or any',
    type: 'string',
    default: 'none',
  },
  {
    name: 'automergeType',
    description:
      'How to automerge - "branch-merge-commit", "branch-push" or "pr". Branch support is GitHub-only',
    type: 'string',
    default: 'pr',
    onboarding: false,
  },
  // Default templates
  {
    name: 'branchName',
    description: 'Branch name template',
    type: 'string',
    default: template('branchName'),
    cli: false,
  },
  {
    name: 'commitMessage',
    description: 'Commit message template',
    type: 'string',
    default: template('commitMessage'),
    cli: false,
  },
  {
    name: 'prTitle',
    description: 'Pull Request title template',
    type: 'string',
    default: template('prTitle'),
    cli: false,
    onboarding: false,
  },
  {
    name: 'prBody',
    description: 'Pull Request body template',
    type: 'string',
    default: template('prBody'),
    cli: false,
    onboarding: false,
  },
  // Yarn Lock Maintenance
  {
    name: 'yarnCacheFolder',
    description:
      'Location of yarn cache folder to use. Set to empty string to disable',
    level: 'global',
    type: 'string',
    default: '/tmp/yarn-cache',
  },
  {
    name: 'lockFileMaintenance',
    description: 'Configuration for lock file maintenance',
    level: 'packageFile',
    type: 'json',
    default: {
      enabled: true,
      branchName: 'renovate/lock-files',
      commitMessage: '{{semanticPrefix}}Update lock file',
      prTitle: '{{semanticPrefix}}Lock file maintenance',
      prBody: 'This PR regenerates lock files to keep them up-to-date.',
      schedule: 'before 5am on monday',
    },
    cli: false,
    env: false,
    onboarding: false,
    mergeable: true,
  },
  // Dependency Groups
  {
    name: 'lazyGrouping',
    description: 'Use group names only when multiple dependencies upgraded',
    type: 'boolean',
    default: true,
    onboarding: false,
  },
  {
    name: 'groupName',
    description: 'Human understandable name for the dependency group',
    type: 'string',
    default: null,
    onboarding: false,
  },
  {
    name: 'groupSlug',
    description:
      'Slug to use for group (e.g. in branch name). Will be calculated from groupName if null',
    type: 'string',
    default: null,
    onboarding: false,
  },
  {
    name: 'group',
    description: 'Config if groupName is enabled',
    type: 'json',
    default: {
      branchName: template('branchName', 'group'),
      commitMessage: template('commitMessage', 'group'),
      prTitle: template('prTitle', 'group'),
      prBody: template('prBody', 'group'),
    },
    cli: false,
    env: false,
    onboarding: false,
    mergeable: true,
  },
  // Pull Request options
  {
    name: 'labels',
    description: 'Labels to add to Pull Request',
    type: 'list',
  },
  {
    name: 'assignees',
    description: 'Assignees for Pull Request',
    type: 'list',
  },
  {
    name: 'reviewers',
    description: 'Requested reviewers for Pull Requests (GitHub only)',
    type: 'list',
  },
  // Debug options
  {
    name: 'logLevel',
    description: 'Logging level',
    level: 'global',
    type: 'string',
    default: 'info',
    env: 'LOG_LEVEL',
  },
];

function getOptions() {
  return options;
}
<|endoftext|>


<commit_before>/**
 * Created by vepe on 08/03/17.
 */
module.exports = {
  noChange: [
    'Nothing new',
    'It\'s pretty quiet around here',
    'Not much to see today :thinking_face:',
    'Come back tomorrow.',
  ],
  smallestPositiveChange: [
    'At least you\'re not losing money!',
    'I guess that\'s something...',
    'Welp',
    'Investing is weird man',
    '$ave that money!',
  ],
  smallPositiveChange: [
    'Not bad, not bad...',
    'You\'re doing ok',
  ],
  postiveChange: [
    'Today was a p good day!',
    ':money_mouth_face:',
  ],
  bigPostiveChange: [
    'Now you\'re making money! :ok_hand:',
    ':moneybag:',
    'Cash rules everything around me! :moneybag:',
  ],
  biggestPositiveChange: [
    'The sky is the limit!',
    'Dolla dolla bills y\'all!',
    'Shake that money maker!',
  ],
  smallestNegativeChange: [
    'Could have been worse.',
    'As far as losses go, this is p ok',
  ],
  smallNegativeChange: [
    'Man, I dunno',
  ],
  negativeChange: [
    'Money out the window! :money_with_wings:',
  ],
  bigNegativeChange: [
    'Shiiiiiiiit',
    'I dunno what to say man',
    'I should probably say something comforting',
    'At least you have your health',
    'I am holding you tight during this time of loss',
    'I will always be here for you',
  ],
  biggestNegativeChange: [
    'Just know that my arms are wrapped around you during this time of loss.',
    'If Britney made it through 2008, you can make it through this.',
    'We never lose the ~people~ money we love. They live with us in our hearts for the rest of our lives.'],
};
<commit_msg>A few more emojis :+1:<commit_after>/**
 * Created by vepe on 08/03/17.
 */
module.exports = {
  noChange: [
    'Nothing new',
    'It\'s pretty quiet around here',
    'Not much to see today :thinking_face:',
    'Come back tomorrow.',
  ],
  smallestPositiveChange: [
    'At least you\'re not losing money!',
    'I guess that\'s something...',
    'Welp',
    'Investing is weird man',
    '$ave that money!',
  ],
  smallPositiveChange: [
    'Not bad, not bad...',
    'You\'re doing ok :ok_hand:',
  ],
  postiveChange: [
    'Today was a p good day! :+1:',
    ':money_mouth_face:',
  ],
  bigPostiveChange: [
    'Now you\'re making money! :ok_hand:',
    ':moneybag:',
    'Cash rules everything around me! :moneybag:',
  ],
  biggestPositiveChange: [
    'The sky is the limit!',
    'Dolla dolla bills y\'all!',
    'Shake that money maker!',
  ],
  smallestNegativeChange: [
    'Could have been worse.',
    'As far as losses go, this is p ok',
  ],
  smallNegativeChange: [
    'Man, I dunno',
  ],
  negativeChange: [
    'Money out the window! :money_with_wings:',
  ],
  bigNegativeChange: [
    'Shiiiiiiiit',
    'I dunno what to say man',
    'I should probably say something comforting',
    'At least you have your health',
    'I am holding you tight during this time of loss',
    'I will always be here for you',
  ],
  biggestNegativeChange: [
    'Just know that my arms are wrapped around you during this time of loss.',
    'If Britney made it through 2008, you can make it through this.',
    'We never lose the ~people~ money we love. They live with us in our hearts for the rest of our lives.'],
};
<|endoftext|>


<commit_before>let async = require('../utils/async_extend.js');

class Compiler {
  constructor(options) {
    this.plugins = options.plugins;
    this.logger = options.logger;
  }

  compile_contracts(contractFiles, cb) {
    const self = this;
    let available_compilers = {};

    if (contractFiles.length === 0) {
      return cb(null, {});
    }

    let pluginCompilers = self.plugins.getPluginsProperty('compilers', 'compilers');
    pluginCompilers.forEach(function (compilerObject) {
      available_compilers[compilerObject.extension] = compilerObject.cb;
    });

    let compiledObject = {};

    async.eachObject(available_compilers,
      function (extension, compiler, callback) {
        let matchingFiles = contractFiles.filter(function (file) {
          let fileMatch = file.filename.match(/\.[0-9a-z]+$/);
          if (fileMatch && (fileMatch[0] === extension)) {
            file.compiled = true;
            return true;
          }
          return false;
        });

        if (!matchingFiles || !matchingFiles.length) {
          return callback();
        }
        compiler.call(compiler, matchingFiles, function (err, compileResult) {
          Object.assign(compiledObject, compileResult);
          callback(err, compileResult);
        });
      },
      function (err) {
        contractFiles.forEach(file => {
          if (!file.compiled) {
            self.logger.warn(__("%s doesn't have a compatible contract compiler. Maybe a plugin exists for it.", file.filename));
          }
        });

        cb(err, compiledObject);
      }
    );
  }
}

module.exports = Compiler;
<commit_msg>Verify that compiler for extension hasn't been loaded<commit_after>let async = require('../utils/async_extend.js');

class Compiler {
  constructor(options) {
    this.plugins = options.plugins;
    this.logger = options.logger;
  }

  compile_contracts(contractFiles, cb) {
    const self = this;
    let available_compilers = {};

    if (contractFiles.length === 0) {
      return cb(null, {});
    }

    let pluginCompilers = self.plugins.getPluginsProperty('compilers', 'compilers');
    pluginCompilers.forEach(function (compilerObject) {
      if(available_compilers[compilerObject.extension] === undefined)
        available_compilers[compilerObject.extension] = compilerObject.cb;
    });

    let compiledObject = {};

    async.eachObject(available_compilers,
      function (extension, compiler, callback) {
        let matchingFiles = contractFiles.filter(function (file) {
          let fileMatch = file.filename.match(/\.[0-9a-z]+$/);
          if (fileMatch && (fileMatch[0] === extension)) {
            file.compiled = true;
            return true;
          }
          return false;
        });

        if (!matchingFiles || !matchingFiles.length) {
          return callback();
        }
        compiler.call(compiler, matchingFiles, function (err, compileResult) {
          Object.assign(compiledObject, compileResult);
          callback(err, compileResult);
        });
      },
      function (err) {
        contractFiles.forEach(file => {
          if (!file.compiled) {
            self.logger.warn(__("%s doesn't have a compatible contract compiler. Maybe a plugin exists for it.", file.filename));
          }
        });

        cb(err, compiledObject);
      }
    );
  }
}

module.exports = Compiler;
<|endoftext|>


<commit_before>'use strict';

const _         = require('lodash');
const events    = require('events');
const Immutable = require('immutable');

const combineHandlers = require('./combineHandlers');
const ParseContext    = require('./ParseContext');

/**
 * Maps incoming top-level object events to
 * context instances and fire deeper events
 */
class EventParser extends events.EventEmitter {

    constructor() {
        super();
        this.contexts = {};
        this.handlers = [];

        /*  Number of handlers that are to remain at the end of the stack regardless of whether new handlers
         Are inserted "last" */
        this.finalHandlers = 0;
    }

    /**
     * Read incoming top-level object
     * @param obj
     */
    receive(obj) {
        if (obj.begin) this.begin(obj);
        this.fire(obj);
    }

    /**
     * Invoked for incoming "begin" objects
     * @param obj
     */
    begin(obj) {
        let id        = obj.context;
        let timestamp = obj.timestamp;
        let context   = new ParseContext(id, timestamp);
        _.extend(context, obj.begin);
        // e.g. "begin":{"parent":"507d8410-b172-11e6-a36f-7f298ebb314a","scope":{"action":"land"}}
        // Translate context to actual object
        if (context.parent && context.parent !== null)
            context.parent = this.getContext(context.parent);
        this.contexts[id] = context;
        this.emit('begin', context);
    }

    /**
     * Invoke event handlers for the given event object
     * @param obj
     */
    fire(obj) {

        let context   = this.getContext(obj.context);
        let timestamp = obj.timestamp;
        let data      = Immutable.fromJS(_.omit(obj, 'context', 'timestamp'));

        let info = {context, timestamp};

        _(this.handlers)
            .concat((data, info) => this.emit('uncaught', data, info))
            .forEach(handler => {
                data = handler(data, info, obj);
                if (data === null || data === undefined) return false;
            });
    }

    /**
     * Add the given function to the end of the handler stack, and make sure handlers that are subsequently added
     * will be inserted before the given one (unless added via `useFinal`)
     *
     * @param {Function} fn
     */
    useFinal(fn) {
        this.finalHandlers++;
        this.handlers.push(fn);
    }

    /**
     * Push the given function onto the event handler stack.
     * The function must return either true (consumed) or false (not consumed) in order to propagate the event
     * to the next handler if appropriate.
     *
     * Event handlers will be invoked with two arguments:
     * 1. An immutable data structure containing the event object with "context" and "timestamp" omitted
     * 2. An object containing {timestamp, context} where context is an instance of ParserContext
     *
     * @param {Function} fn
     * @param {Boolean} [first = false] Whether to push the handler to the front of the stack
     */
    use(fn, first = false) {
        if (first) this.handlers.unshift(fn);
        else this.handlers.splice(this.handlers.length - this.finalHandlers, 0, fn);
    }

    /**
     * Same as `use` but expects an object of handlers that will be combined using `combineHandlers`
     *
     * @param {Object} fns
     * @param {Boolean} [first = false] Whether to push the handlers to the front of the stack
     */
    useCombine(fns, first = false) {
        this.use(combineHandlers(fns), first);
    }

    /**
     * By enabling this, an error will be thrown for any event
     * that is not handled by the handler stack.
     *
     * @param {boolean} [on = true]
     */
    setStrict(on = true) {
        if (!on && this.uncaughtEventListener) {
            this.removeListener('uncaught', this.uncaughtEventListener);
            this.uncaughtEventListener = null;
        }
        if (on) {
            this.uncaughtEventListener = (data, info) => {
                throw new Error(`Unhandled event in EventParser: ${JSON.stringify(data)}`);
            };
            this.addListener('uncaught', this.uncaughtEventListener);
        }
    }

    /**
     * Get the context associated with the given id
     * @param {String} id
     * @returns {Context}
     */
    getContext(id) {
        return this.contexts[id];
    }
}

module.exports = EventParser;<commit_msg>Update documentation for EventParser.use<commit_after>'use strict';

const _         = require('lodash');
const events    = require('events');
const Immutable = require('immutable');

const combineHandlers = require('./combineHandlers');
const ParseContext    = require('./ParseContext');

/**
 * Maps incoming top-level object events to
 * context instances and fire deeper events
 */
class EventParser extends events.EventEmitter {

    constructor() {
        super();
        this.contexts = {};
        this.handlers = [];

        /*  Number of handlers that are to remain at the end of the stack regardless of whether new handlers
         Are inserted "last" */
        this.finalHandlers = 0;
    }

    /**
     * Read incoming top-level object
     * @param obj
     */
    receive(obj) {
        if (obj.begin) this.begin(obj);
        this.fire(obj);
    }

    /**
     * Invoked for incoming "begin" objects
     * @param obj
     */
    begin(obj) {
        let id        = obj.context;
        let timestamp = obj.timestamp;
        let context   = new ParseContext(id, timestamp);
        _.extend(context, obj.begin);
        // e.g. "begin":{"parent":"507d8410-b172-11e6-a36f-7f298ebb314a","scope":{"action":"land"}}
        // Translate context to actual object
        if (context.parent && context.parent !== null)
            context.parent = this.getContext(context.parent);
        this.contexts[id] = context;
        this.emit('begin', context);
    }

    /**
     * Invoke event handlers for the given event object
     * @param obj
     */
    fire(obj) {

        let context   = this.getContext(obj.context);
        let timestamp = obj.timestamp;
        let data      = Immutable.fromJS(_.omit(obj, 'context', 'timestamp'));

        let info = {context, timestamp};

        _(this.handlers)
            .concat((data, info) => this.emit('uncaught', data, info))
            .forEach(handler => {
                data = handler(data, info, obj);
                if (data === null || data === undefined) return false;
            });
    }

    /**
     * Add the given function to the end of the handler stack, and make sure handlers that are subsequently added
     * will be inserted before the given one (unless added via `useFinal`)
     *
     * @param {Function} fn
     */
    useFinal(fn) {
        this.finalHandlers++;
        this.handlers.push(fn);
    }

    /**
     * Push the given function onto the event handler stack.
     * The function must return either true (consumed) or false (not consumed) in order to propagate the event
     * to the next handler if appropriate.
     *
     * Event handlers will be invoked with two arguments:
     * 1. An immutable data structure containing the event object with "context" and "timestamp" omitted
     * 2. An object containing {timestamp, context} where context is an instance of ParserContext
     * 3. The raw incoming object
     *
     * @param {Function} fn
     * @param {Boolean} [first = false] Whether to push the handler to the front of the stack
     */
    use(fn, first = false) {
        if (first) this.handlers.unshift(fn);
        else this.handlers.splice(this.handlers.length - this.finalHandlers, 0, fn);
    }

    /**
     * Same as `use` but expects an object of handlers that will be combined using `combineHandlers`
     *
     * @param {Object} fns
     * @param {Boolean} [first = false] Whether to push the handlers to the front of the stack
     */
    useCombine(fns, first = false) {
        this.use(combineHandlers(fns), first);
    }

    /**
     * By enabling this, an error will be thrown for any event
     * that is not handled by the handler stack.
     *
     * @param {boolean} [on = true]
     */
    setStrict(on = true) {
        if (!on && this.uncaughtEventListener) {
            this.removeListener('uncaught', this.uncaughtEventListener);
            this.uncaughtEventListener = null;
        }
        if (on) {
            this.uncaughtEventListener = (data, info) => {
                throw new Error(`Unhandled event in EventParser: ${JSON.stringify(data)}`);
            };
            this.addListener('uncaught', this.uncaughtEventListener);
        }
    }

    /**
     * Get the context associated with the given id
     * @param {String} id
     * @returns {Context}
     */
    getContext(id) {
        return this.contexts[id];
    }
}

module.exports = EventParser;<|endoftext|>