<commit_before>#!/usr/bin/env node

/**
 * # config
 */
var format = require('util').format;
var path = require('path');
var pkg = require('./package');
var _ = require('lodash');
var async = require('async');
var createDMG = require('electron-installer-dmg');
var codesign = require('electron-installer-codesign');
var electronWinstaller = require('electron-winstaller');
var electronPrebuiltVersion = require('electron-prebuilt/package.json').version;

exports.options = {
  verbose: {
    describe: 'Confused or trying to track down a bug and want lots of debug output?',
    type: 'boolean',
    default: false
  },
  platform: {
    describe: 'What platform are we building for?',
    choices: ['win32', 'linux', 'darwin'],
    default: process.platform
  },
  arch: {
    describe: 'What platform architecture are we building for?',
    choices: ['x64', 'x86'],
    default: process.arch
  },
  electron_version: {
    describe: 'What version of electron are we using?',
    default: electronPrebuiltVersion
  },
  version: {
    describe: 'What version of the application are we building?',
    default: process.env.npm_package_version || pkg.version
  },
  name: {
    describe: 'What is the kebab cased name of the application?',
    default: process.env.npm_package_name || pkg.name
  },
  product_name: {
    describe: 'What is the name of the application we should display to humans?',
    default: pkg.productName
  },
  description: {
    describe: 'What is the description of the application we should display to humans?',
    default: process.env.npm_package_description || pkg.description
  },
  sign: {
    describe: 'Should this build be signed?',
    type: 'boolean',
    default: true
  },
  signtool_params: {
    describe: 'What extra cli arguments should be passed to signtool.exe?',
    default: process.env.SIGNTOOL_PARAMS || null
  },
  favicon_url: {
    description: 'A URL to an ICO file to use as the application icon (e.g. Windows: displayed in Control Panel > Programs and Features)',
    default: _.get(pkg, 'config.hadron.build.win32.favicon_url')
  },
  evergreen_revision: {
    description: 'What revision, aka commit sha1 is evergreen building?',
    type: 'string',
    default: process.env.EVERGREEN_REVISION
  },
  evergreen_build_variant: {
    description: 'build_variant on evergreen',
    type: 'string',
    default: process.env.EVERGREEN_BUILD_VARIANT
  },
  evergreen_branch_name: {
    description: 'branch_name on evergreen',
    type: 'string',
    default: process.env.EVERGREEN_BRANCH_NAME
  },
  github_token: {
    description: 'GitHub API token.',
    default: process.env.GITHUB_TOKEN
  },
  github_owner: {
    default: pkg.github_owner
  },
  github_repo: {
    default: pkg.github_repo
  },
  author: {
    default: pkg.author || pkg.authors
  }
};

var assert = require('assert');
exports.get = function(cli, callback) {
  /**
   * a.k.a What directory is package.json in?
   */
  var PROJECT_ROOT = process.cwd();

  /**
   * Build the options object to pass to `electron-packager`
   * and various `electron-installer-*` modules.
   */
  var channel = 'stable';
  if (cli.argv.version.indexOf('-beta') > -1) {
    channel = 'beta';
  } else if (cli.argv.version.indexOf('-dev') > -1) {
    channel = 'dev';
  }

  var PRODUCT_NAME = cli.argv.product_name;
  assert(cli.argv.product_name);

  if (channel === 'beta') {
    PRODUCT_NAME += ' Beta';
  } else if (channel === 'dev') {
    PRODUCT_NAME += ' Dev';
  }

  var ID = cli.argv.name;

  /**
   * TODO (imlucas) beta and dev channels should have different
   * icons.
   */

  /**
   * TODO (imlucas) Make `CONFIG` a proper interface class
   * with implementors based on `platform`.
   */
  var CONFIG = _.omit(cli.argv, [
    '_', 'help', 'verbose', 'sign', 'format', '$0',
    'signtool_params', 'favicon_url'
  ]);

  var IGNORE_DIRECTORIES = 'node_modules/|.cache/|dist/|test/|.user-data';

  CONFIG.packagerOptions = {
    dir: PROJECT_ROOT,
    out: path.join(PROJECT_ROOT, 'dist'),
    overwrite: true,
    'app-copyright': format('%s %s', new Date().getFullYear(), CONFIG.author),
    'build-version': CONFIG.version,
    'app-version': CONFIG.version,
    ignore: IGNORE_DIRECTORIES,
    platform: CONFIG.platform,
    arch: CONFIG.arch,
    version: CONFIG.electron_version,
    sign: null
  };
  /**
   * First add to `CONFIG` the common keys which are
   * not platform specific.
   */
  CONFIG.out = path.join(PROJECT_ROOT, 'dist');
  CONFIG.platform = cli.argv.platform;
  CONFIG.arch = cli.argv.arch;
  CONFIG.channel = channel;

  if (cli.argv.platform === 'win32') {
    /**
     * ## Windows Configuration
     */
    var WINDOWS_APPNAME = PRODUCT_NAME.replace(/ /g, '');
    var WINDOWS_OUT_X64 = path.join(CONFIG.out,
      format('%s-win32-x64', WINDOWS_APPNAME));

    var WINDOWS_RESOURCES = path.join(WINDOWS_OUT_X64, 'resources');

    var WINDOWS_ICON = path.join(process.cwd(),
      _.get(pkg, 'config.hadron.build.win32.icon'));

    var WINDOWS_LOADING_GIF = path.join(process.cwd(),
      _.get(pkg, 'config.hadron.build.win32.loading_gif'));

    var WINDOWS_OUT_SETUP_EXE = path.join(CONFIG.out,
      format('%sSetup.exe', WINDOWS_APPNAME));

    var WINDOWS_OUT_MSI = path.join(CONFIG.out,
      format('%sSetup.msi', WINDOWS_APPNAME));

    CONFIG.appPath = WINDOWS_OUT_X64;
    CONFIG.resources = WINDOWS_RESOURCES;
    CONFIG.assets = [
      {
        name: path.basename(WINDOWS_OUT_SETUP_EXE),
        label: 'Windows Installer',
        path: WINDOWS_OUT_SETUP_EXE
      },
      {
        name: path.basename(WINDOWS_OUT_MSI),
        label: 'Windows Installer Package',
        path: WINDOWS_OUT_MSI
      },
      {
        name: 'RELEASES',
        path: path.join(CONFIG.out, 'RELEASES')
      },
      {
        name: format('%s-%s-full.nupkg',
          WINDOWS_APPNAME, CONFIG['app-version']),
        path: path.join(CONFIG.out, format('%s-%s-full.nupkg',
          WINDOWS_APPNAME, CONFIG['app-version']))
      },
      {
        name: format('%s-windows.zip', ID),
        path: path.join(CONFIG.out, format('%s.zip', WINDOWS_APPNAME))
      }
      /**
       * TODO (imlucas) Uncomment when hadron-endpoint-server deployed.
       path.join(CONFIG.out, format('%s-%s-delta.nupkg', WINDOWS_APPNAME, CONFIG['app-version']));
       */
    ];

    _.assign(CONFIG.packagerOptions, {
      name: WINDOWS_APPNAME,
      icon: WINDOWS_ICON,
      'version-string': {
        CompanyName: CONFIG.author,
        FileDescription: CONFIG.description,
        ProductName: PRODUCT_NAME,
        InternalName: CONFIG.name
      }
    });

    CONFIG.installerOptions = {
      loadingGif: WINDOWS_LOADING_GIF,
      signWithParams: cli.argv.signtool_params,
      iconUrl: cli.argv.favicon_url,
      appDirectory: WINDOWS_OUT_X64,
      outputDirectory: CONFIG.out,
      authors: CONFIG.author,
      version: CONFIG.version,
      exe: format('%s.exe', CONFIG.packagerOptions.name),
      title: PRODUCT_NAME,
      productName: PRODUCT_NAME,
      description: CONFIG.description,
      name: CONFIG.name.replace('-', '_'),
      id: CONFIG.name.replace('-', '_')
      /**
       * TODO (imlucas) Uncomment when hadron-endpoint-server deployed.
       * remoteReleases: _.get(pkg, 'config.hadron.endpoint'),
       * remoteToken: process.env.GITHUB_TOKEN,
       */
      /**
       * TODO (imlucas) The ICO file to use as the icon for the
       * generated Setup.exe. Defaults to the weird
       * "present" icon @thomasr mentioned:
       *  https://raw.githubusercontent.com/Squirrel/Squirrel.Windows/master/src/Setup/Setup.ico
       * setupIcon: WINDOWS_ICON
       */
    };

    CONFIG.createInstaller = function(done) {
      electronWinstaller.createWindowsInstaller(CONFIG.installerOptions)
        .then(function(res) {
          cli.debug('Successfully created installers', res);
          done();
        }, done);
    };
  } else if (cli.argv.platform === 'darwin') {
    /**
     * ## OS X Configuration
     */
    var OSX_APPNAME = PRODUCT_NAME;
    var OSX_OUT_X64 = path.join(CONFIG.out,
      format('%s-darwin-x64', OSX_APPNAME));
    var OSX_DOT_APP = path.join(OSX_OUT_X64, format('%s.app', OSX_APPNAME));
    var OSX_IDENTITY = _.get(pkg, 'config.hadron.build.darwin.codesign_identity');
    var OSX_IDENTITY_SHA1 = _.get(pkg, 'config.hadron.build.darwin.codesign_sha1');
    var OSX_RESOURCES = path.join(OSX_DOT_APP, 'Contents', 'Resources');

    var OSX_ICON = path.resolve(process.cwd(),
      _.get(pkg, 'config.hadron.build.darwin.icon'));

    var OSX_OUT_DMG = path.join(CONFIG.out,
      format('%s.dmg', OSX_APPNAME));

    var OSX_OUT_ZIP = path.join(CONFIG.out,
      format('%s.zip', OSX_APPNAME));

    _.assign(CONFIG.packagerOptions, {
      name: OSX_APPNAME,
      icon: OSX_ICON,
      'app-bundle-id': _.get(pkg, 'config.hadron.build.darwin.app_bundle_id'),
      /**
       * @see http://bit.ly/LSApplicationCategoryType
       */
      'app-category-type': _.get(pkg, 'config.hadron.build.darwin.app_category_type'),
      protocols: _.get(pkg, 'config.hadron.protocols')
    });

    CONFIG.appPath = OSX_DOT_APP;
    CONFIG.resources = OSX_RESOURCES;
    CONFIG.assets = [
      {
        name: format('%s.dmg', ID),
        path: OSX_OUT_DMG
      },
      {
        name: format('%s-mac.zip', ID),
        path: OSX_OUT_ZIP
      }
    ];

    CONFIG.installerOptions = {
      dmgPath: OSX_OUT_DMG,
      title: PRODUCT_NAME,
      overwrite: true,
      out: CONFIG.out,
      icon: OSX_ICON,
      identity_display: OSX_IDENTITY,
      identity: OSX_IDENTITY_SHA1,
      appPath: OSX_DOT_APP,
      /**
       * Background image for `.dmg`.
       * @see http://npm.im/electron-installer-dmg
       */
      background: path.resolve(process.cwd(),
        _.get(pkg, 'config.hadron.build.darwin.dmg_background')),
      /**
       * Layout for `.dmg`.
       * The following only modifies "x","y" values from defaults.
       * @see http://npm.im/electron-installer-dmg
       */
      contents: [
        /**
         * Show a shortcut on the right to `Applications` folder.
         */
        {
          x: 450,
          y: 344,
          type: 'link',
          path: '/Applications'
        },
        /**
         * Show a shortcut on the left for the application icon.
         */
        {
          x: 192,
          y: 344,
          type: 'file',
          path: OSX_DOT_APP
        }
      ]
    };

    CONFIG.createInstaller = function(done) {
      var tasks = [];
      var opts = CONFIG.installerOptions;
      codesign.isIdentityAvailable(opts.identity_display, function(err, available) {
        if (err) {
          return done(err);
        }
        if (available) {
          tasks.push(_.partial(codesign, {
            identity: opts.identity,
            appPath: opts.appPath
          }));
        } else {
          codesign.printWarning();
        }

        tasks.push(_.partial(createDMG, opts));
        async.series(tasks, done);
      });
    };
  } else {
    /**
     * ## Linux Configuration
     */
    var LINUX_APPNAME = cli.argv.name;
    var LINUX_OUT_X64 = path.join(CONFIG.out,
      format('%s-linux-x64', LINUX_APPNAME));
    var LINUX_RESOURCES = path.join(LINUX_OUT_X64, 'resources');

    _.assign(CONFIG.packagerOptions, {
      name: LINUX_APPNAME
    });

    CONFIG.resources = LINUX_RESOURCES;
    CONFIG.appPath = LINUX_OUT_X64;
    CONFIG.assets = [];

    CONFIG.createInstaller = function(done) {
      cli.warn('Linux installers coming soon!');
      done();
    };
  }

  // Normalize asset names for GitHub or else spaces
  // will automatically be replaced with `.`s.
  CONFIG.assets = CONFIG.assets.map(function(asset) {
    if (asset.name !== 'RELEASES') {
      asset.name = asset.name.replace(/ /g, '-').toLowerCase();
    }
    return asset;
  });
  if (callback) {
    return callback(null, CONFIG);
  }
  return CONFIG;
};

module.exports = exports;
<commit_msg>Fix windows nupkg filename<commit_after>#!/usr/bin/env node

/**
 * # config
 */
var format = require('util').format;
var path = require('path');
var pkg = require('./package');
var _ = require('lodash');
var async = require('async');
var createDMG = require('electron-installer-dmg');
var codesign = require('electron-installer-codesign');
var electronWinstaller = require('electron-winstaller');
var electronPrebuiltVersion = require('electron-prebuilt/package.json').version;

exports.options = {
  verbose: {
    describe: 'Confused or trying to track down a bug and want lots of debug output?',
    type: 'boolean',
    default: false
  },
  platform: {
    describe: 'What platform are we building for?',
    choices: ['win32', 'linux', 'darwin'],
    default: process.platform
  },
  arch: {
    describe: 'What platform architecture are we building for?',
    choices: ['x64', 'x86'],
    default: process.arch
  },
  electron_version: {
    describe: 'What version of electron are we using?',
    default: electronPrebuiltVersion
  },
  version: {
    describe: 'What version of the application are we building?',
    default: process.env.npm_package_version || pkg.version
  },
  name: {
    describe: 'What is the kebab cased name of the application?',
    default: process.env.npm_package_name || pkg.name
  },
  product_name: {
    describe: 'What is the name of the application we should display to humans?',
    default: pkg.productName
  },
  description: {
    describe: 'What is the description of the application we should display to humans?',
    default: process.env.npm_package_description || pkg.description
  },
  sign: {
    describe: 'Should this build be signed?',
    type: 'boolean',
    default: true
  },
  signtool_params: {
    describe: 'What extra cli arguments should be passed to signtool.exe?',
    default: process.env.SIGNTOOL_PARAMS || null
  },
  favicon_url: {
    description: 'A URL to an ICO file to use as the application icon (e.g. Windows: displayed in Control Panel > Programs and Features)',
    default: _.get(pkg, 'config.hadron.build.win32.favicon_url')
  },
  evergreen_revision: {
    description: 'What revision, aka commit sha1 is evergreen building?',
    type: 'string',
    default: process.env.EVERGREEN_REVISION
  },
  evergreen_build_variant: {
    description: 'build_variant on evergreen',
    type: 'string',
    default: process.env.EVERGREEN_BUILD_VARIANT
  },
  evergreen_branch_name: {
    description: 'branch_name on evergreen',
    type: 'string',
    default: process.env.EVERGREEN_BRANCH_NAME
  },
  github_token: {
    description: 'GitHub API token.',
    default: process.env.GITHUB_TOKEN
  },
  github_owner: {
    default: pkg.github_owner
  },
  github_repo: {
    default: pkg.github_repo
  },
  author: {
    default: pkg.author || pkg.authors
  }
};

var assert = require('assert');
exports.get = function(cli, callback) {
  /**
   * a.k.a What directory is package.json in?
   */
  var PROJECT_ROOT = process.cwd();

  /**
   * Build the options object to pass to `electron-packager`
   * and various `electron-installer-*` modules.
   */
  var channel = 'stable';
  if (cli.argv.version.indexOf('-beta') > -1) {
    channel = 'beta';
  } else if (cli.argv.version.indexOf('-dev') > -1) {
    channel = 'dev';
  }

  var PRODUCT_NAME = cli.argv.product_name;
  assert(cli.argv.product_name);

  if (channel === 'beta') {
    PRODUCT_NAME += ' Beta';
  } else if (channel === 'dev') {
    PRODUCT_NAME += ' Dev';
  }

  var ID = cli.argv.name;

  /**
   * TODO (imlucas) beta and dev channels should have different
   * icons.
   */

  /**
   * TODO (imlucas) Make `CONFIG` a proper interface class
   * with implementors based on `platform`.
   */
  var CONFIG = _.omit(cli.argv, [
    '_', 'help', 'verbose', 'sign', 'format', '$0',
    'signtool_params', 'favicon_url'
  ]);

  var IGNORE_DIRECTORIES = 'node_modules/|.cache/|dist/|test/|.user-data';

  CONFIG.packagerOptions = {
    dir: PROJECT_ROOT,
    out: path.join(PROJECT_ROOT, 'dist'),
    overwrite: true,
    'app-copyright': format('%s %s', new Date().getFullYear(), CONFIG.author),
    'build-version': CONFIG.version,
    'app-version': CONFIG.version,
    ignore: IGNORE_DIRECTORIES,
    platform: CONFIG.platform,
    arch: CONFIG.arch,
    version: CONFIG.electron_version,
    sign: null
  };
  /**
   * First add to `CONFIG` the common keys which are
   * not platform specific.
   */
  CONFIG.out = path.join(PROJECT_ROOT, 'dist');
  CONFIG.platform = cli.argv.platform;
  CONFIG.arch = cli.argv.arch;
  CONFIG.channel = channel;

  if (cli.argv.platform === 'win32') {
    /**
     * ## Windows Configuration
     */
    var WINDOWS_APPNAME = PRODUCT_NAME.replace(/ /g, '');
    var WINDOWS_OUT_X64 = path.join(CONFIG.out,
      format('%s-win32-x64', WINDOWS_APPNAME));

    var WINDOWS_RESOURCES = path.join(WINDOWS_OUT_X64, 'resources');

    var WINDOWS_ICON = path.join(process.cwd(),
      _.get(pkg, 'config.hadron.build.win32.icon'));

    var WINDOWS_LOADING_GIF = path.join(process.cwd(),
      _.get(pkg, 'config.hadron.build.win32.loading_gif'));

    var WINDOWS_OUT_SETUP_EXE = path.join(CONFIG.out,
      format('%sSetup.exe', WINDOWS_APPNAME));

    var WINDOWS_OUT_MSI = path.join(CONFIG.out,
      format('%sSetup.msi', WINDOWS_APPNAME));

    CONFIG.appPath = WINDOWS_OUT_X64;
    CONFIG.resources = WINDOWS_RESOURCES;
    CONFIG.assets = [
      {
        name: path.basename(WINDOWS_OUT_SETUP_EXE),
        label: 'Windows Installer',
        path: WINDOWS_OUT_SETUP_EXE
      },
      {
        name: path.basename(WINDOWS_OUT_MSI),
        label: 'Windows Installer Package',
        path: WINDOWS_OUT_MSI
      },
      {
        name: 'RELEASES',
        path: path.join(CONFIG.out, 'RELEASES')
      },
      {
        name: format('%s-%s-full.nupkg',
          WINDOWS_APPNAME, CONFIG.version),
        path: path.join(CONFIG.out, format('%s-%s-full.nupkg',
          WINDOWS_APPNAME, CONFIG.version))
      },
      {
        name: format('%s-windows.zip', ID),
        path: path.join(CONFIG.out, format('%s.zip', WINDOWS_APPNAME))
      }
      /**
       * TODO (imlucas) Uncomment when hadron-endpoint-server deployed.
       path.join(CONFIG.out, format('%s-%s-delta.nupkg', WINDOWS_APPNAME, CONFIG['app-version']));
       */
    ];

    _.assign(CONFIG.packagerOptions, {
      name: WINDOWS_APPNAME,
      icon: WINDOWS_ICON,
      'version-string': {
        CompanyName: CONFIG.author,
        FileDescription: CONFIG.description,
        ProductName: PRODUCT_NAME,
        InternalName: CONFIG.name
      }
    });

    CONFIG.installerOptions = {
      loadingGif: WINDOWS_LOADING_GIF,
      signWithParams: cli.argv.signtool_params,
      iconUrl: cli.argv.favicon_url,
      appDirectory: WINDOWS_OUT_X64,
      outputDirectory: CONFIG.out,
      authors: CONFIG.author,
      version: CONFIG.version,
      exe: format('%s.exe', CONFIG.packagerOptions.name),
      title: PRODUCT_NAME,
      productName: PRODUCT_NAME,
      description: CONFIG.description,
      name: CONFIG.name.replace('-', '_'),
      id: CONFIG.name.replace('-', '_')
      /**
       * TODO (imlucas) Uncomment when hadron-endpoint-server deployed.
       * remoteReleases: _.get(pkg, 'config.hadron.endpoint'),
       * remoteToken: process.env.GITHUB_TOKEN,
       */
      /**
       * TODO (imlucas) The ICO file to use as the icon for the
       * generated Setup.exe. Defaults to the weird
       * "present" icon @thomasr mentioned:
       *  https://raw.githubusercontent.com/Squirrel/Squirrel.Windows/master/src/Setup/Setup.ico
       * setupIcon: WINDOWS_ICON
       */
    };

    CONFIG.createInstaller = function(done) {
      electronWinstaller.createWindowsInstaller(CONFIG.installerOptions)
        .then(function(res) {
          cli.debug('Successfully created installers', res);
          done();
        }, done);
    };
  } else if (cli.argv.platform === 'darwin') {
    /**
     * ## OS X Configuration
     */
    var OSX_APPNAME = PRODUCT_NAME;
    var OSX_OUT_X64 = path.join(CONFIG.out,
      format('%s-darwin-x64', OSX_APPNAME));
    var OSX_DOT_APP = path.join(OSX_OUT_X64, format('%s.app', OSX_APPNAME));
    var OSX_IDENTITY = _.get(pkg, 'config.hadron.build.darwin.codesign_identity');
    var OSX_IDENTITY_SHA1 = _.get(pkg, 'config.hadron.build.darwin.codesign_sha1');
    var OSX_RESOURCES = path.join(OSX_DOT_APP, 'Contents', 'Resources');

    var OSX_ICON = path.resolve(process.cwd(),
      _.get(pkg, 'config.hadron.build.darwin.icon'));

    var OSX_OUT_DMG = path.join(CONFIG.out,
      format('%s.dmg', OSX_APPNAME));

    var OSX_OUT_ZIP = path.join(CONFIG.out,
      format('%s.zip', OSX_APPNAME));

    _.assign(CONFIG.packagerOptions, {
      name: OSX_APPNAME,
      icon: OSX_ICON,
      'app-bundle-id': _.get(pkg, 'config.hadron.build.darwin.app_bundle_id'),
      /**
       * @see http://bit.ly/LSApplicationCategoryType
       */
      'app-category-type': _.get(pkg, 'config.hadron.build.darwin.app_category_type'),
      protocols: _.get(pkg, 'config.hadron.protocols')
    });

    CONFIG.appPath = OSX_DOT_APP;
    CONFIG.resources = OSX_RESOURCES;
    CONFIG.assets = [
      {
        name: format('%s.dmg', ID),
        path: OSX_OUT_DMG
      },
      {
        name: format('%s-mac.zip', ID),
        path: OSX_OUT_ZIP
      }
    ];

    CONFIG.installerOptions = {
      dmgPath: OSX_OUT_DMG,
      title: PRODUCT_NAME,
      overwrite: true,
      out: CONFIG.out,
      icon: OSX_ICON,
      identity_display: OSX_IDENTITY,
      identity: OSX_IDENTITY_SHA1,
      appPath: OSX_DOT_APP,
      /**
       * Background image for `.dmg`.
       * @see http://npm.im/electron-installer-dmg
       */
      background: path.resolve(process.cwd(),
        _.get(pkg, 'config.hadron.build.darwin.dmg_background')),
      /**
       * Layout for `.dmg`.
       * The following only modifies "x","y" values from defaults.
       * @see http://npm.im/electron-installer-dmg
       */
      contents: [
        /**
         * Show a shortcut on the right to `Applications` folder.
         */
        {
          x: 450,
          y: 344,
          type: 'link',
          path: '/Applications'
        },
        /**
         * Show a shortcut on the left for the application icon.
         */
        {
          x: 192,
          y: 344,
          type: 'file',
          path: OSX_DOT_APP
        }
      ]
    };

    CONFIG.createInstaller = function(done) {
      var tasks = [];
      var opts = CONFIG.installerOptions;
      codesign.isIdentityAvailable(opts.identity_display, function(err, available) {
        if (err) {
          return done(err);
        }
        if (available) {
          tasks.push(_.partial(codesign, {
            identity: opts.identity,
            appPath: opts.appPath
          }));
        } else {
          codesign.printWarning();
        }

        tasks.push(_.partial(createDMG, opts));
        async.series(tasks, done);
      });
    };
  } else {
    /**
     * ## Linux Configuration
     */
    var LINUX_APPNAME = cli.argv.name;
    var LINUX_OUT_X64 = path.join(CONFIG.out,
      format('%s-linux-x64', LINUX_APPNAME));
    var LINUX_RESOURCES = path.join(LINUX_OUT_X64, 'resources');

    _.assign(CONFIG.packagerOptions, {
      name: LINUX_APPNAME
    });

    CONFIG.resources = LINUX_RESOURCES;
    CONFIG.appPath = LINUX_OUT_X64;
    CONFIG.assets = [];

    CONFIG.createInstaller = function(done) {
      cli.warn('Linux installers coming soon!');
      done();
    };
  }

  // Normalize asset names for GitHub or else spaces
  // will automatically be replaced with `.`s.
  CONFIG.assets = CONFIG.assets.map(function(asset) {
    if (asset.name !== 'RELEASES') {
      asset.name = asset.name.replace(/ /g, '-').toLowerCase();
    }
    return asset;
  });
  if (callback) {
    return callback(null, CONFIG);
  }
  return CONFIG;
};

module.exports = exports;
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

// load the master sakai object to access all Sakai OAE API methods
require(["jquery", "sakai/sakai.api.core"], function($, sakai) {

    /**
     * @name sakai_global.joingroup
     *
     * @class joingroup
     *
     * @description
     * Overlay for joining groups
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.joingroup = function (tuid, showSettings) {

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        // DOM jQuery Objects
        var $rootel = $("#" + tuid);  // unique container for each widget instance
        var $joingroup_hover_template = $("#joingroup_hover_template", $rootel);

        ///////////////////////
        // Utility Functions //
        ///////////////////////

        /**
         * Compare the names of 2 participants
         *
         * @param {Object} participant a
         * @param {Object} participant b
         * @return 1, 0 or -1
         */
        var participantSort = function (a, b) {
            if (a.displayname.toLowerCase() > b.displayname.toLowerCase()) {
                return 1;
            } else {
                if (a.displayname.toLowerCase() === b.displayname.toLowerCase()) {
                    return 0;
                } else {
                    return -1;
                }
            }
        };

        /**
         * Adjust the number of participants listed in the search result
         *
         * @param {String} groupid The group ID
         * @param {Integer} value Value to adjust the number of participants by
         */
        var adjustParticipantCount = function (groupid, value) {
            var participantCount = parseInt($("#searchgroups_result_participant_count_" + groupid).text());
            participantCount = participantCount + value;
            $("#searchgroups_result_participant_count_" + groupid).text(participantCount);
            if (participantCount === 1) {
                $("#searchgroups_text_participant_" + groupid).text(sakai.api.i18n.General.getValueForKey("PARTICIPANT"));
            } else {
                $("#searchgroups_text_participant_" + groupid).text(sakai.api.i18n.General.getValueForKey("PARTICIPANTS"));
            }
            $("#searchgroups_result_participant_link_" + groupid).attr("title", $.trim($("#searchgroups_result_participant_link_" + groupid).text()));
        };

        /**
         * Push the given member to the given list of members to be rendered.
         * If the member is a manager, set is_manager to 'true'
         *
         * @param {Object} member      object returned in batch data results
         * @param {Object} list        list of members to add to
         * @param {Object} is_manager  optional flag to set whether this user
         *     is a manager or not
         */
        var push_member_to_list = function (member, list, role) {
            var picsrc = "/dev/images/default_profile_picture_32.png";
            if (member.basic.elements.picture &&
                member.basic.elements.picture.name &&
                member.basic.elements.picture.name.value) {
                picsrc = member.basic.elements.picture.name.value;
            }
            list.push({
                link: member.homePath,
                picsrc: picsrc,
                displayname: sakai.api.User.getDisplayName(member),
                role: role
            });
        };

        var getGroup = function(groupid, callback) {
            var group = {};
            // get batch group data for this group
            var batchRequests = [
                {
                    url: "/var/joinrequests/list.json?groupId=" + groupid,
                    method: "GET"
                },
                {
                    url: "/~" + groupid + "/public.1.json",
                    method: "GET"
                }
            ];
            sakai.api.Server.batch(batchRequests, function (success, data) {
                if (success && data && data.results && data.results.length) {
                    var participants = [];
                    // join requests
                    if (data.results[0].body) {
                        var joinrequests = $.parseJSON(data.results[0].body);
                        group.joinrequests = joinrequests;
                    }
                    // joinability info
                    if (data.results[1].body) {
                        var groupdata = $.parseJSON(data.results[1].body);
                        group.groupProfile =
                            groupdata.authprofile;
                        group.joinability =
                            groupdata.authprofile["sakai:group-joinable"];
                        group.title =
                            groupdata.authprofile["sakai:group-title"];
                        group.id =
                            groupid;
                    }
                    sakai.api.Groups.getMembers(groupid, false, function(success, members) {
                        group.groupMembers = members;

                        $.each(members, function(role, users) {
                            $.each(users.results, function(index, user) {
                                push_member_to_list(user, participants, role);
                            });
                        });

                        group.totalParticipants = participants.length;
                        if (participants.length > 1) {
                            participants = participants.sort(participantSort);
                        }
                        if (participants.length > 5) {
                            participants = participants.slice(0, 5);
                            group.seeAll = true;
                        } else {
                            group.seeAll = false;
                        }
                        group.participants = participants;
                        if ($.isFunction(callback)){
                            callback(group);
                        }
                    });
                } else {
                    debug.error("Batch request to fetch group (id: " + id + ") data failed.");
                }
            });
            return group;
        };

        var openTooltip = function (groupid, $item, leaveAllowed) {
            getGroup(groupid, function(group) {
                $(window).trigger("init.tooltip.sakai", {
                    tooltipHTML: sakai.api.Util.TemplateRenderer(
                        $joingroup_hover_template, group),
                    tooltipAutoClose: true,
                    tooltipArrow: "top",
                    tooltipTop: $item.offset().top + $item.height(),
                    tooltipLeft: $item.offset().left + $item.width(),
                    onShow: function () {
                        $(window).trigger("init.joinrequestbuttons.sakai", [
                            {
                                "groupProfile": group.groupProfile,
                                "groupMembers": group.groupMembers,
                                "leaveAllowed": leaveAllowed
                            },
                            groupid,
                            group.joinability,
                            group.managerCount,
                            false,
                            function (renderedButtons) {
                                // onShow
                                $("#joingroup_joinrequestbuttons").html(
                                    renderedButtons.html());
                            },
                            function (success, id) {
                                // requestCallback
                                if (success) {
                                    // reset joinrequest data
                                    group.joinrequests = false;
                                }
                            },
                            function (success, id) {
                                // joinCallback
                                if (success) {
                                    // re-render tooltip
                                    resetTooltip(groupid, $item);
                                    $("#searchgroups_memberimage_" + groupid).show();
                                    $("#searchgroups_memberimage_" + groupid).parent().removeClass("s3d-actions-addtolibrary");
                                    adjustParticipantCount(groupid, 1);
                                }
                            },
                            function (success, id) {
                                // leaveCallback
                                if (success) {
                                    // re-render tooltip
                                    resetTooltip(groupid, $item);
                                    $("#searchgroups_memberimage_" + groupid).hide();
                                    $("#searchgroups_memberimage_" + groupid).parent().addClass("s3d-actions-addtolibrary");
                                    adjustParticipantCount(groupid, -1);
                                }
                            },
                            group.joinrequests
                        ]);
                    }
                });
            });
        };

        var resetTooltip = function (groupid, $item) {
            $(window).trigger("done.tooltip.sakai");
            openTooltip(groupid, $item);
        };

        /////////////////////////////
        // Initialization function //
        /////////////////////////////

        var doInit = function () {
            $(window).bind("initialize.joingroup.sakai", function(evObj, groupid, target){
                sakai.api.Groups.getMembers(groupid,"",function(membersSuccess, memberData){
                    sakai.api.Groups.getGroupAuthorizableData(groupid, function(membershipSuccess, membershipData){
                        // Members are always allowed to leave the group, managers should always be present and cannot leave when they are the last one in the group
                        if (!sakai.api.Groups.isCurrentUserAManager(groupid, sakai.data.me, membershipData.properties)) {
                            openTooltip(groupid, $(target), true);
                        } else {
                            var roles = $.parseJSON(membershipData.properties["sakai:roles"]);
                            var numManagers = 0;
                            $.each(roles, function(index, role){
                                if (role.allowManage) {
                                    numManagers = numManagers + memberData[role.title].results.length;
                                }
                            });
                            var leaveAllowed = false;
                            if (numManagers > 1) {
                                leaveAllowed = true;
                            }
                            openTooltip(groupid, $(target), leaveAllowed);
                        }
                    })
                }, "everyone");
                return false;
            });
        };

        // run the initialization function when the widget object loads
        doInit();

    };

    // inform Sakai OAE that this widget has loaded and is ready to run
    sakai.api.Widgets.widgetLoader.informOnLoad("joingroup");
});
<commit_msg>SAKIII-3651 List group manager in participants, different aproach to groups in join popover<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

// load the master sakai object to access all Sakai OAE API methods
require(["jquery", "sakai/sakai.api.core"], function($, sakai) {

    /**
     * @name sakai_global.joingroup
     *
     * @class joingroup
     *
     * @description
     * Overlay for joining groups
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.joingroup = function (tuid, showSettings) {

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        // DOM jQuery Objects
        var $rootel = $("#" + tuid);  // unique container for each widget instance
        var $joingroup_hover_template = $("#joingroup_hover_template", $rootel);

        ///////////////////////
        // Utility Functions //
        ///////////////////////

        /**
         * Compare the names of 2 participants
         *
         * @param {Object} participant a
         * @param {Object} participant b
         * @return 1, 0 or -1
         */
        var participantSort = function (a, b) {
            if (a.displayname.toLowerCase() > b.displayname.toLowerCase()) {
                return 1;
            } else {
                if (a.displayname.toLowerCase() === b.displayname.toLowerCase()) {
                    return 0;
                } else {
                    return -1;
                }
            }
        };

        /**
         * Adjust the number of participants listed in the search result
         *
         * @param {String} groupid The group ID
         * @param {Integer} value Value to adjust the number of participants by
         */
        var adjustParticipantCount = function (groupid, value) {
            var participantCount = parseInt($("#searchgroups_result_participant_count_" + groupid).text());
            participantCount = participantCount + value;
            $("#searchgroups_result_participant_count_" + groupid).text(participantCount);
            if (participantCount === 1) {
                $("#searchgroups_text_participant_" + groupid).text(sakai.api.i18n.General.getValueForKey("PARTICIPANT"));
            } else {
                $("#searchgroups_text_participant_" + groupid).text(sakai.api.i18n.General.getValueForKey("PARTICIPANTS"));
            }
            $("#searchgroups_result_participant_link_" + groupid).attr("title", $.trim($("#searchgroups_result_participant_link_" + groupid).text()));
        };

        /**
         * Push the given member to the given list of members to be rendered.
         * If the member is a manager, set is_manager to 'true'
         *
         * @param {Object} member      object returned in batch data results
         * @param {Object} list        list of members to add to
         * @param {Object} is_manager  optional flag to set whether this user
         *     is a manager or not
         */
        var push_member_to_list = function (member, list, role) {
            var link, picsrc, displayname = "";
            if (member["sakai:category"] == "group") {
                picsrc = "/dev/images/group_avatar_icon_35x35_nob.png";
                if (member.basic.elements.picture && member.basic.elements.picture.name && member.basic.elements.picture.name.value) {
                    picsrc = member.basic.elements.picture.name.value;
                }
                displayname = member["sakai:group-title"];
            }
            else {
                picsrc = "/dev/images/default_profile_picture_32.png";
                if (member.basic.elements.picture && member.basic.elements.picture.name && member.basic.elements.picture.name.value) {
                    picsrc = member.basic.elements.picture.name.value;
                }
                displayname = sakai.api.User.getDisplayName(member);
            }
            list.push({
                link: link,
                picsrc: picsrc,
                displayname: displayname,
                role: role
            });
        };

        var getGroup = function(groupid, callback) {
            var group = {};
            // get batch group data for this group
            var batchRequests = [
                {
                    url: "/var/joinrequests/list.json?groupId=" + groupid,
                    method: "GET"
                },
                {
                    url: "/~" + groupid + "/public.1.json",
                    method: "GET"
                }
            ];
            sakai.api.Server.batch(batchRequests, function (success, data) {
                if (success && data && data.results && data.results.length) {
                    var participants = [];
                    // join requests
                    if (data.results[0].body) {
                        var joinrequests = $.parseJSON(data.results[0].body);
                        group.joinrequests = joinrequests;
                    }
                    // joinability info
                    if (data.results[1].body) {
                        var groupdata = $.parseJSON(data.results[1].body);
                        group.groupProfile =
                            groupdata.authprofile;
                        group.joinability =
                            groupdata.authprofile["sakai:group-joinable"];
                        group.title =
                            groupdata.authprofile["sakai:group-title"];
                        group.id =
                            groupid;
                    }
                    sakai.api.Groups.getMembers(groupid, false, function(success, members) {
                        group.groupMembers = members;

                        $.each(members, function(role, users) {
                            $.each(users.results, function(index, user) {
                                push_member_to_list(user, participants, role);
                            });
                        });

                        group.totalParticipants = participants.length;
                        if (participants.length > 1) {
                            participants = participants.sort(participantSort);
                        }
                        if (participants.length > 5) {
                            participants = participants.slice(0, 5);
                            group.seeAll = true;
                        } else {
                            group.seeAll = false;
                        }
                        group.participants = participants;
                        if ($.isFunction(callback)){
                            callback(group);
                        }
                    }, true);
                } else {
                    debug.error("Batch request to fetch group (id: " + id + ") data failed.");
                }
            });
            return group;
        };

        var openTooltip = function (groupid, $item, leaveAllowed) {
            getGroup(groupid, function(group) {
                $(window).trigger("init.tooltip.sakai", {
                    tooltipHTML: sakai.api.Util.TemplateRenderer(
                        $joingroup_hover_template, group),
                    tooltipAutoClose: true,
                    tooltipArrow: "top",
                    tooltipTop: $item.offset().top + $item.height(),
                    tooltipLeft: $item.offset().left + $item.width(),
                    onShow: function () {
                        $(window).trigger("init.joinrequestbuttons.sakai", [
                            {
                                "groupProfile": group.groupProfile,
                                "groupMembers": group.groupMembers,
                                "leaveAllowed": leaveAllowed
                            },
                            groupid,
                            group.joinability,
                            group.managerCount,
                            false,
                            function (renderedButtons) {
                                // onShow
                                $("#joingroup_joinrequestbuttons").html(
                                    renderedButtons.html());
                            },
                            function (success, id) {
                                // requestCallback
                                if (success) {
                                    // reset joinrequest data
                                    group.joinrequests = false;
                                }
                            },
                            function (success, id) {
                                // joinCallback
                                if (success) {
                                    // re-render tooltip
                                    resetTooltip(groupid, $item);
                                    $("#searchgroups_memberimage_" + groupid).show();
                                    $("#searchgroups_memberimage_" + groupid).parent().removeClass("s3d-actions-addtolibrary");
                                    adjustParticipantCount(groupid, 1);
                                }
                            },
                            function (success, id) {
                                // leaveCallback
                                if (success) {
                                    // re-render tooltip
                                    resetTooltip(groupid, $item);
                                    $("#searchgroups_memberimage_" + groupid).hide();
                                    $("#searchgroups_memberimage_" + groupid).parent().addClass("s3d-actions-addtolibrary");
                                    adjustParticipantCount(groupid, -1);
                                }
                            },
                            group.joinrequests
                        ]);
                    }
                });
            });
        };

        var resetTooltip = function (groupid, $item) {
            $(window).trigger("done.tooltip.sakai");
            openTooltip(groupid, $item);
        };

        /////////////////////////////
        // Initialization function //
        /////////////////////////////

        var doInit = function () {
            $(window).bind("initialize.joingroup.sakai", function(evObj, groupid, target){
                sakai.api.Groups.getMembers(groupid,"",function(membersSuccess, memberData){
                    sakai.api.Groups.getGroupAuthorizableData(groupid, function(membershipSuccess, membershipData){
                        // Members are always allowed to leave the group, managers should always be present and cannot leave when they are the last one in the group
                        if (!sakai.api.Groups.isCurrentUserAManager(groupid, sakai.data.me, membershipData.properties)) {
                            openTooltip(groupid, $(target), true);
                        } else {
                            var roles = $.parseJSON(membershipData.properties["sakai:roles"]);
                            var numManagers = 0;
                            $.each(roles, function(index, role){
                                if (role.allowManage) {
                                    numManagers = numManagers + memberData[role.title].results.length;
                                }
                            });
                            var leaveAllowed = false;
                            if (numManagers > 1) {
                                leaveAllowed = true;
                            }
                            openTooltip(groupid, $(target), leaveAllowed);
                        }
                    })
                }, "everyone");
                return false;
            });
        };

        // run the initialization function when the widget object loads
        doInit();

    };

    // inform Sakai OAE that this widget has loaded and is ready to run
    sakai.api.Widgets.widgetLoader.informOnLoad("joingroup");
});
<|endoftext|>


<commit_before>import React from 'react';
import {Link} from 'react-router';

class AdminMenu extends React.Component {
    render() {
        return (

                {!this.props.children &&

<Link to={'/admin/users/create'}>Create user</Link>

                }
                {this.props.children}

        );
    }
}

AdminMenu.propTypes = {
    children: React.PropTypes.node
};

export default AdminMenu;
<commit_msg>UI on admin menu<commit_after>import React from 'react';
import {Link} from 'react-router';

class AdminMenu extends React.Component {
    render() {
        return (

                {!this.props.children && <Link to={'/admin/users/create'}>Créer un itinérant</Link>}
                {this.props.children}

        );
    }
}

AdminMenu.propTypes = {
    children: React.PropTypes.node
};

export default AdminMenu;
<|endoftext|>


<commit_before>import Base from './base';
import arrayFrom from '../helpers/array/from';
import {MODEL_TYPE} from './types';

class Model extends Base {

    static get type() {
        return MODEL_TYPE;
    }

    get type() {
        return MODEL_TYPE;
    }

    constructor(options={}) {

        super(options);

        this.data = {};
        this._storage = {};

        if (options.data) {
            this.add(options.data);
        }

        Object.defineProperty(this.data, 'api', {
            writeable: false,
            enumerable: false,
            configurable: false,
            value: this
        });

        this.beforeInitialize(options);
        this.initialize(options);
        this.afterInitialize(options);
        this.bindCustomEvents();
        this.delegateCustomEvents();
    }

    add(data) {

        if (data && typeof data !== 'object') {
            let tmpData = {};

            Array.from(arguments).forEach(key => {
                tmpData[key] = (function() {}()); // undefined
            });

            data = tmpData;
        }

        for (let key in data) {
            if (data.hasOwnProperty(key) && key
                && typeof this.data[key] === 'undefined' && key !== 'data') {

                Object.defineProperty(this.data, key, {
                    configurable: true,
                    enumerable: true,
                    writeable: false,
                    get: () => {
                        return this._storage[`__${key}`];
                    },
                    set: (val) => {
                        let isInvalid;
                        let methodKey = key.charAt(0).toUpperCase() + key.slice(1);

                        if (typeof this[`validate${methodKey}`] === 'function') {
                            isInvalid = !(this[`validate${methodKey}`](val));
                        }

                        if (!isInvalid) {
                            let oldVal = this._storage[`__${key}`];
                            this._storage[`__${key}`] = val;
                            // console.log(`set change trigger ${key}`, this, 'change', key);
                            this.trigger(`change`, {model: this, key, val, oldVal});
                            this.trigger(`change:${key}`, {model: this, val, oldVal});
                        } else {
                            // console.log('set invalid trigger', this, 'change', key);
                            this.trigger(`invalid`, {model: this, key, val, isInvalid});
                            this.trigger(`invalid:${key}`, {model: this, val, isInvalid});
                        }
                    }
                });

                if (typeof data[key] !== 'undefined') {
                    this.data[`${key}`] = data[key];
                }
            } else if (key && data.hasOwnProperty(key) && typeof this.data[key] !== 'undefined') {
                let oldVal = this.data[`${key}`];
                this.data[`${key}`] = data[key];

                this.trigger(`change`, {model: this, key, val: data[key], oldVal});
                this.trigger(`change:${key}`, {model: this, val: data[key], oldVal});
            }
        }

        return this;
    }

    update(data) {
        this.add(data);
        return this;
    }

    remove(parameter) {

        if (typeof parameter === 'string') {
            delete this.data[parameter];
            delete this._storage[`__${parameter}`];
        }

        if (parameter instanceof Array) {
            parameter.forEach(param => this.remove(param));
            return;
        }

        return this;
    }
}

export default Model;<commit_msg>Implements methods for overriding model default getters and setters<commit_after>import Base from './base';
import arrayFrom from '../helpers/array/from';
import {MODEL_TYPE} from './types';

class Model extends Base {

    static get type() {
        return MODEL_TYPE;
    }

    get type() {
        return MODEL_TYPE;
    }

    constructor(options={}) {

        super(options);

        this.data = {};
        this._storage = {};
        this.getters = {};
        this.setters = {};

        if (options.data) {
            this.add(options.data);
        }

        Object.defineProperty(this.data, 'api', {
            writeable: false,
            enumerable: false,
            configurable: false,
            value: this
        });

        this.beforeInitialize(options);
        this.initialize(options);
        this.afterInitialize(options);
        this.bindCustomEvents();
        this.delegateCustomEvents();
    }

    add(data) {

        if (data && typeof data !== 'object') {
            let tmpData = {};

            Array.from(arguments).forEach(key => {
                tmpData[key] = (function() {}()); // undefined
            });

            data = tmpData;
        }

        for (let key in data) {
            if (data.hasOwnProperty(key) && key
                && typeof this.data[key] === 'undefined' && key !== 'data') {

                this.getters[`${key}`] = this.defaultGet(key);
                this.setters[`${key}`] = this.defaultSet(key);

                Object.defineProperty(this.data, key, {
                    configurable: true,
                    enumerable: true,
                    writeable: true,
                    get: this.getters[`${key}`],
                    set: this.setters[`${key}`]
                });

                if (typeof data[key] !== 'undefined') {
                    this.data[`${key}`] = data[key];
                }
            } else if (key && data.hasOwnProperty(key) && typeof this.data[key] !== 'undefined') {
                let oldVal = this.data[`${key}`];
                this.data[`${key}`] = data[key];

                this.trigger(`change`, {model: this, key, val: data[key], oldVal});
                this.trigger(`change:${key}`, {model: this, val: data[key], oldVal});
            }
        }

        return this;
    }

    defaultGet(key) {
        return () => {
            return this._storage[`__${key}`];
        };
    }

    defaultSet(key) {
        return (val) => {
            let isInvalid;
            let methodKey = key.charAt(0).toUpperCase() + key.slice(1);

            if (typeof this[`validate${methodKey}`] === 'function') {
                isInvalid = !(this[`validate${methodKey}`](val));
            }

            if (!isInvalid) {
                let oldVal = this._storage[`__${key}`];
                this._storage[`__${key}`] = val;
                // console.log(`set change trigger ${key}`, this, 'change', key);
                this.trigger(`change`, {model: this, key, val, oldVal});
                this.trigger(`change:${key}`, {model: this, val, oldVal});
            } else {
                // console.log('set invalid trigger', this, 'change', key);
                this.trigger(`invalid`, {model: this, key, val, isInvalid});
                this.trigger(`invalid:${key}`, {model: this, val, isInvalid});
            }
        }
    }

    overrideGet(override) {
        this.getters[`${override.key}`] = override.method;
    }

    overrideSet(override) {
        this.setters[`${override.key}`] = override.method;
    }

    update(data) {
        this.add(data);
        return this;
    }

    remove(parameter) {

        if (typeof parameter === 'string') {
            delete this.data[parameter];
            delete this._storage[`__${parameter}`];
        }

        if (parameter instanceof Array) {
            parameter.forEach(param => this.remove(param));
            return;
        }

        return this;
    }
}

export default Model;<|endoftext|>


<commit_before>'use strict';

/**
 * Utility for reading the angularity configuration files.
 *
 * By default the config is a `angularity.json` that is located at the root
 * of an angularity project.
 *
 * This utility also makes sure that the package.json of the project is kept in sync with
 * angularity so that it can be distributed with npm.
 *
 * @type {exports} Object
 */
var fs = require('fs'),
path   = require('path');

var installMenu = require('./cli/installMenu');

function Config() {
  var self = this;

  self.ES5 = 'ES5';
  self.ES6 = 'ES6';
  self.isMinify = (process.argv[process.argv.length - 1] !== 'nominify');

  // Load angularity's global config file from the user's Home directory.
  var configPath = path.join(require('ide-template').HOME(), '.angularity');
  self.config = JSON.parse(fs.readFileSync(configPath, 'utf8'));

  if (!validateAngularityConfig(this.config))
    console.error('There was an issue validating your .angularity file.');

  // Load the project's config and validate it
  initialiseProjectConfig();

  // Load the npm package.json and validate it
  initialiseNPMConfig();

  // Keep the version numbers in sync between the npm and angularity configs.
  synchroniseConfigs();

//todo use a schema library like... https://github.com/apiaryio/Amanda
  function validateConfig(configData) {
    return ( typeof configData.name !== 'undefined' &&
    typeof configData.version !== 'undefined');
  }

  function validateAngularityConfig(configData) {
    return ( typeof configData.serverHttpPort !== 'undefined' &&
    typeof configData.javascriptVersion !== 'undefined' &&
    typeof  configData.consoleWidth !== 'undefined');
  }

  function initialiseProjectConfig() {
    self.projectConfigPath = path.join(process.cwd(), 'angularity.json');
    self.projectConfigPresent = fs.existsSync(self.projectConfigPath);

    if (self.projectConfigPresent) {
      self.projectConfig = require(self.projectConfigPath);

      if (!validateConfig(self.projectConfig))
        console.error('There was an issue validating your angularity.json file.');
    } else {
      self.projectConfig = {
        consoleWidth          : 80,
        serverPort            : 999,
        JAVASCRIPT_TARGET     : self.ES5,
        webstormExecutablePath: ''
      };
    }
  }

  function initialiseNPMConfig() {
    self.npmConfigPath = path.join(process.cwd(), 'package.json');
    self.npmConfigPresent = fs.existsSync(self.npmConfigPath);

    if (self.npmConfigPresent) {
      self.npmConfig = require(self.npmConfigPath);

      if (!validateConfig(self.npmConfig))
        console.error('There was an issue validating your package.json file.');
    }
    return self.npmConfigPresent;
  }

  function synchroniseConfigs() {
    if (self.projectConfigPresent && self.npmConfigPresent) {
      // todo prompt to fs.write the highest version...
      if (self.projectConfig.version !== self.npmConfig.version) {
        console.log('Your project\'s npm version is out of sync with your angularity.json');
        process.exit(1);
      }
    }
  }
}

/**
 * Resolve a config value by order of priority,
 * Use the project specific angularity.json first,
 * then the global .angularity in the local HOME directory.
 * @param name
 * @returns {*}
 */
Config.prototype.resolveConfigValue = function (name) {
  if (this.projectConfig[name])
    return this.projectConfig[name];

  else if (this.config[name])
    return this.config[name];
};

/* Configurable methods that use the resolve override */

Config.prototype.consoleWidth = function () {
  return this.resolveConfigValue('consoleWidth');
};

Config.prototype.serverHttpPort = function () {
  //Math.floor(Math.random() * 9000) + 1000
  return this.resolveConfigValue('serverHttpPort');
};

Config.prototype.javascriptVersion = function () {
  //validate string values case sensitivity etc
  return this.resolveConfigValue('javascriptVersion');
};

Config.prototype.webstormExecutablePath = function () {
  return this.resolveConfigValue('webstormExecutablePath');
};

Config.prototype.minify = function () {
  return this.resolveConfigValue('minify');
};

module.exports.init = function () {

  var configPath = path.join(require('ide-template').HOME(), '.angularity');
  var configPresent = fs.existsSync(configPath);

  if (configPresent) {
    createInstance();
  } else {
    installMenu.promptAngularityInstall();
    createInstance();
  }

  function createInstance() {
    // Redundant var but lets WebStorm autocomplete
    var configInstance = new Config();
    module.exports = configInstance;
  }
};<commit_msg>feature(config) synchronise the project, npm and bower config sem versions. Minor style changes.<commit_after>/**
 * Utility for reading the angularity configuration files.
 *
 * By default the config is a `angularity.json` that is located at the root
 * of an angularity project.
 *
 * This utility also makes sure that the package.json of the project is kept in sync with
 * angularity so that it can be distributed with npm.
 *
 * @type {exports} Object
 */
'use strict';

var fs   = require('fs'),
    _    = require('lodash'),
    path = require('path');

var installMenu = require('./cli/installMenu');

function Config() {
  var self = this;

  self.ES5 = 'ES5';
  self.ES6 = 'ES6';
  self.isMinify = (process.argv[process.argv.length - 1] !== 'nominify');

  // Load angularity's global config file from the user's Home directory.
  var configPath = path.join(require('ide-template').HOME(), '.angularity');
  self.config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  self.npmConfigPath = path.join(process.cwd(), 'package.json');
  self.projectConfigPath = path.join(process.cwd(), 'angularity.json');
  self.bowerConfigPath = path.join(process.cwd(), 'bower.json');

  if (!validateAngularityConfig(this.config)) {
    console.error('There was an issue validating your .angularity file.');
  }

  // Load the project's config and validate it
  initialiseProjectConfig();

  // Load the npm package.json and validate it
  initialiseNPMConfig();

  // Load the npm package.json and validate it
  initialiseBowerConfig();

  // Keep the config version numbers in sync between the npm, bower and angularity.
  synchroniseConfigVersion();

//todo use a schema library like... https://github.com/apiaryio/Amanda
  function validateConfig(configData) {
    return ( typeof configData.name !== 'undefined' &&
    typeof configData.version !== 'undefined');
  }

  function validateAngularityConfig(configData) {
    return ( typeof configData.serverHttpPort !== 'undefined' &&
    typeof configData.javascriptVersion !== 'undefined' &&
    typeof  configData.consoleWidth !== 'undefined');
  }

  function initialiseProjectConfig() {
    self.projectConfigPresent = fs.existsSync(self.projectConfigPath);

    if (self.projectConfigPresent) {
      self.projectConfig = require(self.projectConfigPath);

      if (!validateConfig(self.projectConfig)) {
        console.error('There was an issue validating your angularity.json file.');
        //todo handle error with prompt
      }
    }
  }

  function initialiseNPMConfig() {
    self.npmConfigPresent = fs.existsSync(self.npmConfigPath);

    if (self.npmConfigPresent) {
      self.npmConfig = require(self.npmConfigPath);

      if (!validateConfig(self.npmConfig)) {
        console.error('There was an issue validating your package.json file.');
        //todo handle error with prompt
      }
    }
    return self.npmConfigPresent;
  }

  function initialiseBowerConfig() {
    self.bowerConfigPresent = fs.existsSync(self.bowerConfigPath);

    if (self.bowerConfigPresent) {
      self.bowerConfig = require(self.bowerConfigPath);

      if (!validateConfig(self.bowerConfig)) {
        console.error('There was an issue validating your bower.json file.');
        //todo handle error with prompt
      }
    }
    return self.bowerConfigPresent;
  }

  /**
   * Determine the highest SemVersion between a project's bower, npm and angularity configs.
   * If the versions are out of sync, update the config files with the highest version.
   */
  function synchroniseConfigVersion() {
    if (self.projectConfigPresent && self.npmConfigPresent) {

      if (self.projectConfig.version !== self.npmConfig.version !== self.bowerConfig.version) {
        var values = [
          {name: 'angularity.json', version: self.projectConfig.version},
          {name: 'package.json', version: self.npmConfig.version},
          {name: 'bower.json', version: self.bowerConfig.version}
        ];

        var highest = highestSemVersion(values);

        console.warn('Your configuration versions are out of sync, the highest version',
          highest.version, 'from your', highest.name, 'will now be used for all.');

        self.projectConfig.version = highest.version;
        self.npmConfig.version = highest.version;
        self.bowerConfig.version = highest.version;

        saveConfigs();
      }
    }
  }

  /**
   * Update all the project json configs on the disk based on the objects in this config class.
   */
  function saveConfigs() {
    var projectConfig = JSON.stringify(self.projectConfig, null, 4);
    fs.writeFileSync(self.projectConfigPath, projectConfig);

    var npmConfig = JSON.stringify(self.npmConfig, null, 4);
    fs.writeFileSync(self.npmConfigPath, npmConfig);

    var bowerConfig = JSON.stringify(self.bowerConfig, null, 4);
    fs.writeFileSync(self.bowerConfigPath, bowerConfig);
  }

  /**
   * Based on an Array of objects containing name and a version in sem version syntax
   * return the object with the highest version.
   *
   * @param versions
   * @returns {*}
   */
  function highestSemVersion(versions) {
    var results = [];

    _.forEach(versions, function (value) {
      results.push({value: value.version, float: parseVersionFloat(value.version)});
    });

    results = results.sort(function (a, b) {
      return b.float - a.float;
    });

    var highestVersion = results[0].value;

    return _.filter(versions, function (num) {
      return num.version === highestVersion;
    })[0];
  }

  /**
   * Given an semVersion String, calculate a Float value that respects it's
   * SemVersion highest value to be used when comparing versions together.
   * @param semVersion
   * @returns {number}
   */
  function parseVersionFloat(semVersion) {
    var versionFloat = 0;
    var versionsSplit = ('' + semVersion)
      .replace(/[^0-9.]/g, '')
      .split('.');

    for (var i = 0; i < versionsSplit.length; ++i) {
      versionFloat += Number(versionsSplit[i]) / Math.pow(10, i * 3);
    }
    return versionFloat;
  }
}

/**
 * Resolve a config value by order of priority,
 * Use the project specific angularity.json first,
 * then the global .angularity in the local HOME directory.
 * @param name
 * @returns {*}
 */
Config.prototype.resolveConfigValue = function (name) {
  if (this.projectConfig[name])
    return this.projectConfig[name];

  else if (this.config[name])
    return this.config[name];
};

/* Configurable methods that use the resolve override */

Config.prototype.consoleWidth = function () {
  return this.resolveConfigValue('consoleWidth');
};

Config.prototype.serverHttpPort = function () {
  //Math.floor(Math.random() * 9000) + 1000
  return this.resolveConfigValue('serverHttpPort');
};

Config.prototype.javascriptVersion = function () {
  //validate string values case sensitivity etc
  return this.resolveConfigValue('javascriptVersion');
};

Config.prototype.webstormExecutablePath = function () {
  return this.resolveConfigValue('webstormExecutablePath');
};

Config.prototype.minify = function () {
  return this.resolveConfigValue('minify');
};

module.exports.init = function () {

  var configPath = path.join(require('ide-template').HOME(), '.angularity');
  var configPresent = fs.existsSync(configPath);

  if (configPresent) {
    createInstance();
  } else {
    installMenu.promptAngularityInstall();
    createInstance();
  }

  function createInstance() {
    // Redundant var but lets WebStorm autocomplete
    var configInstance = new Config();
    module.exports = configInstance;
  }
};<|endoftext|>


<commit_before>/*
 * Provides methods for input validation.
 */
var InputValidator =
{
    EMPTY: " is null or empty.",
    NULL: " is null.",
    UNDEFINED: " is undefined.",

    isArray: function(object)
    {
        return Array.isArray(object);
    },

    isNumber: function(n)
    {
        return !isNaN(parseFloat(n)) && isFinite(n);
    },

    validateInRange: function(objectName, object, low, high)
    {
        this.validateIsNumber(objectName, object);

        if (object < low || high < object)
        {
            console.log(new Error().stack);
            throw objectName + " is out of range [" + low + ", " + high + "]: "
                    + object;
        }
    },

    validateIsNumber: function(objectName, object)
    {
        if (typeof object !== "number")
        {
            console.log(new Error().stack);
            throw objectName + " is not a number: " + object;
        }
    },

    validateNotEmpty: function(objectName, object)
    {
        if (InputValidator.isArray(object))
        {
            if (object.length == 0)
            {
                // Empty array.
                console.log(new Error().stack);
                throw objectName + InputValidator.EMPTY;
            }
        }
        else if (InputValidator.isNumber(object))
        {
            // Valid.
        }
        else if (object === true || object === false)
        {
            // Valid.
        }
        else
        {
            if (!object)
            {
                console.log(new Error().stack);
                throw objectName + InputValidator.EMPTY;
            }
        }
    },

    validateNotNull: function(objectName, object)
    {
        if (object === undefined)
        {
            console.log(new Error().stack);
            throw objectName + InputValidator.UNDEFINED;
        }

        if (object === null)
        {
            console.log(new Error().stack);
            throw objectName + InputValidator.NULL;
        }
    }
}
<commit_msg>Changed to use the logger instead of console.log.<commit_after>/*
 * Provides methods for input validation.
 */
var InputValidator =
{
    EMPTY: " is null or empty.",
    NULL: " is null.",
    UNDEFINED: " is undefined.",

    isArray: function(object)
    {
        return Array.isArray(object);
    },

    isNumber: function(n)
    {
        return !isNaN(parseFloat(n)) && isFinite(n);
    },

    validateInRange: function(objectName, object, low, high)
    {
        this.validateIsNumber(objectName, object);

        if (object < low || high < object)
        {
            LOGGER.error(new Error().stack);
            throw objectName + " is out of range [" + low + ", " + high + "]: "
                    + object;
        }
    },

    validateIsNumber: function(objectName, object)
    {
        if (typeof object !== "number")
        {
            LOGGER.error(new Error().stack);
            throw objectName + " is not a number: " + object;
        }
    },

    validateNotEmpty: function(objectName, object)
    {
        if (InputValidator.isArray(object))
        {
            if (object.length == 0)
            {
                // Empty array.
                LOGGER.error(new Error().stack);
                throw objectName + InputValidator.EMPTY;
            }
        }
        else if (InputValidator.isNumber(object))
        {
            // Valid.
        }
        else if (object === true || object === false)
        {
            // Valid.
        }
        else
        {
            if (!object)
            {
                LOGGER.error(new Error().stack);
                throw objectName + InputValidator.EMPTY;
            }
        }
    },

    validateNotNull: function(objectName, object)
    {
        if (object === undefined)
        {
            LOGGER.error(new Error().stack);
            throw objectName + InputValidator.UNDEFINED;
        }

        if (object === null)
        {
            LOGGER.error(new Error().stack);
            throw objectName + InputValidator.NULL;
        }
    }
}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var fs = require('fs');
var log = require('./log');

module.exports = Config;

function Config(configuration) {
  var config = configuration || parseConfigFromEnvironmentVariablePath();

  for (var fieldName in config) {
    if (config.hasOwnProperty(fieldName)) {
      this[fieldName] = config[fieldName];
    }
  }
  this.validate();
}

var schema = {
  requiredTopLevelFields: {
    githubUser: 'GitHub username',
    githubTimeout: 'GitHub API timeout limit in milliseconds',
    slackTimeout: 'Slack API timeout limit in milliseconds',
    successReaction: 'emoji used to indicate an issue was successfully filed',
    rules: 'Slack-reaction-to-GitHub-issue rules'
  },
  requiredRulesFields: {
    reactionName: 'name of the reaction emoji triggering the rule',
    githubRepository: 'GitHub repository to which to post issues'
  },
  optionalRulesFields: {
    channelNames: 'names of the Slack channels triggering the rules; ' +
      'leave undefined to match messages in any Slack channel'
  }
};

Config.prototype.validate = function() {
  var errors = [],
      errMsg;

  this.checkRequiredTopLevelFields(errors);
  this.checkForUnknownFieldNames(errors);
  this.checkRequiredRulesFields(errors);
  this.checkForUnknownRuleFieldNames(errors);

  if (errors.length === 0) {
    this.checkForMisconfiguredRules(errors);
  }

  if (errors.length !== 0) {
    errMsg = 'Invalid configuration:\n  ' + errors.join('\n  ');
    log(errMsg);
    throw new Error(errMsg);
  }
};

function parseConfigFromEnvironmentVariablePath() {
  var configPath = (process.env.HUBOT_SLACK_GITHUB_ISSUES_CONFIG_PATH ||
    'config/slack-github-issues.json');
  log('loading config from ' + configPath);
  return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}

Config.prototype.checkRequiredTopLevelFields = function(errors) {
  var fieldName;

  for (fieldName in schema.requiredTopLevelFields) {
    if (schema.requiredTopLevelFields.hasOwnProperty(fieldName) &&
        !this.hasOwnProperty(fieldName)) {
      errors.push('missing ' + fieldName);
    }
  }
};

Config.prototype.checkForUnknownFieldNames = function(errors) {
  var fieldName;

  for (fieldName in this) {
    if (this.hasOwnProperty(fieldName) &&
      !schema.requiredTopLevelFields.hasOwnProperty(fieldName)) {
      errors.push('unknown property ' + fieldName);
    }
  }
};

Config.prototype.checkRequiredRulesFields = function(errors) {
  if (!this.rules) {
    return;
  }

  this.rules.forEach(function(rule, index) {
    var fieldName;

    for (fieldName in schema.requiredRulesFields) {
      if (schema.requiredRulesFields.hasOwnProperty(fieldName) &&
          !rule.hasOwnProperty(fieldName)) {
          errors.push('rule ' + index + ' missing ' + fieldName);
      }
    }
  });
};

Config.prototype.checkForUnknownRuleFieldNames = function(errors) {
  if (!this.rules) {
    return;
  }

  this.rules.forEach(function(rule, index) {
    var fieldName;

    for (fieldName in rule) {
      if (rule.hasOwnProperty(fieldName) &&
        !schema.requiredRulesFields.hasOwnProperty(fieldName) &&
        !schema.optionalRulesFields.hasOwnProperty(fieldName)) {
        errors.push('rule ' + index +
          ' contains unknown property ' + fieldName);
      }
    }
  });
};

Config.prototype.checkForMisconfiguredRules = function(errors) {
  var sorted, isEqualToSorted, NUM_SPACES = 2,
      rulesByReaction;

  if (!this.rules) {
    return;
  }

  sorted = this.rules.slice().sort(compareRules);
  isEqualToSorted = function(value, index) {
    return value === sorted[index];
  };

  if (!this.rules.every(isEqualToSorted)) {
    errors.push('rules are not sorted; expected: ' +
      JSON.stringify(sorted, null, NUM_SPACES).replace(/\n/g, '\n  '));
  }

  rulesByReaction = groupRulesBy(this.rules, 'reactionName');
  checkRulesByReaction(rulesByReaction, errors);
};

function compareRules(lhs, rhs) {
  return lhs.reactionName.localeCompare(rhs.reactionName) ||
    compareChannelNames(lhs.channelNames, rhs.channelNames) ||
    lhs.githubRepository.localeCompare(rhs.githubRepository);
}

function compareChannelNames(lhs, rhs) {
  if (lhs !== undefined && rhs === undefined) {
    return -1;
  }
  if (lhs === undefined && rhs !== undefined) {
    return 1;
  }
  return 0;
}

function groupRulesBy(rules, property) {
  var result = {};

  rules.forEach(function(rule) {
    (result[rule[property]] = result[rule[property]] || []).push(rule);
  });
  return result;
}

function checkRulesByReaction(rulesByReaction, errors) {
  for (var reactionName in rulesByReaction) {
    if (rulesByReaction.hasOwnProperty(reactionName)) {
      checkRulesForReaction(rulesByReaction, reactionName, errors);
    }
  }
}

function checkRulesForReaction(rulesByReaction, reactionName, errors) {
  var rules = rulesByReaction[reactionName];
  checkForDuplicateRepositories(rules, reactionName, errors);
  checkForDuplicateChannels(rules, reactionName, errors);
  checkForDuplicateAllChannelRules(rules, reactionName, errors);
}

function checkForDuplicateRepositories(rules, reactionName, errors) {
  var duplicateRepos = rules.map(function(r) { return r.githubRepository; })
    .sort(function(lhs, rhs) { return lhs.localeCompare(rhs); })
    .filter(detectDuplicates);

  if (duplicateRepos.length !== 0) {
    errors.push('duplicate repositories for ' + reactionName + ' rules:');
    duplicateRepos.forEach(function(repo) { errors.push('  ' + repo); });
  }
}

function checkForDuplicateChannels(rules, reactionName, errors) {
  var duplicateChannels = rules.reduce(reduceChannels, [])
    .sort(sortStrings)
    .filter(detectDuplicates);

  if (duplicateChannels.length !== 0) {
    errors.push('duplicate channels for ' + reactionName + ' rules:');
    duplicateChannels.forEach(function(channel) {
      errors.push('  ' + channel);
    });
  }
}

function reduceChannels(channels, rule) {
  Array.prototype.push.apply(channels, rule.channelNames || []);
  return channels;
}

function detectDuplicates(property, index, properties) {
  return index !== 0 && properties[index - 1] === property;
}

function checkForDuplicateAllChannelRules(rules, reactionName, errors) {
  var allChannelRules = rules.filter(function(rule) {
    return rule.channelNames === undefined;
  });

  if (allChannelRules.length > 1) {
    errors.push('multiple all-channel rules defined for ' + reactionName);
  }
}
<commit_msg>Refactor Config.checkForMisconfiguredRules<commit_after>/* jshint node: true */
'use strict';

var fs = require('fs');
var log = require('./log');

module.exports = Config;

function Config(configuration) {
  var config = configuration || parseConfigFromEnvironmentVariablePath();

  for (var fieldName in config) {
    if (config.hasOwnProperty(fieldName)) {
      this[fieldName] = config[fieldName];
    }
  }
  this.validate();
}

var schema = {
  requiredTopLevelFields: {
    githubUser: 'GitHub username',
    githubTimeout: 'GitHub API timeout limit in milliseconds',
    slackTimeout: 'Slack API timeout limit in milliseconds',
    successReaction: 'emoji used to indicate an issue was successfully filed',
    rules: 'Slack-reaction-to-GitHub-issue rules'
  },
  requiredRulesFields: {
    reactionName: 'name of the reaction emoji triggering the rule',
    githubRepository: 'GitHub repository to which to post issues'
  },
  optionalRulesFields: {
    channelNames: 'names of the Slack channels triggering the rules; ' +
      'leave undefined to match messages in any Slack channel'
  }
};

Config.prototype.validate = function() {
  var errors = [],
      errMsg;

  this.checkRequiredTopLevelFields(errors);
  this.checkForUnknownFieldNames(errors);
  this.checkRequiredRulesFields(errors);
  this.checkForUnknownRuleFieldNames(errors);

  if (errors.length === 0) {
    this.checkForMisconfiguredRules(errors);
  }

  if (errors.length !== 0) {
    errMsg = 'Invalid configuration:\n  ' + errors.join('\n  ');
    log(errMsg);
    throw new Error(errMsg);
  }
};

function parseConfigFromEnvironmentVariablePath() {
  var configPath = (process.env.HUBOT_SLACK_GITHUB_ISSUES_CONFIG_PATH ||
    'config/slack-github-issues.json');
  log('loading config from ' + configPath);
  return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}

Config.prototype.checkRequiredTopLevelFields = function(errors) {
  var fieldName;

  for (fieldName in schema.requiredTopLevelFields) {
    if (schema.requiredTopLevelFields.hasOwnProperty(fieldName) &&
        !this.hasOwnProperty(fieldName)) {
      errors.push('missing ' + fieldName);
    }
  }
};

Config.prototype.checkForUnknownFieldNames = function(errors) {
  var fieldName;

  for (fieldName in this) {
    if (this.hasOwnProperty(fieldName) &&
      !schema.requiredTopLevelFields.hasOwnProperty(fieldName)) {
      errors.push('unknown property ' + fieldName);
    }
  }
};

Config.prototype.checkRequiredRulesFields = function(errors) {
  if (!this.rules) {
    return;
  }

  this.rules.forEach(function(rule, index) {
    var fieldName;

    for (fieldName in schema.requiredRulesFields) {
      if (schema.requiredRulesFields.hasOwnProperty(fieldName) &&
          !rule.hasOwnProperty(fieldName)) {
          errors.push('rule ' + index + ' missing ' + fieldName);
      }
    }
  });
};

Config.prototype.checkForUnknownRuleFieldNames = function(errors) {
  if (!this.rules) {
    return;
  }

  this.rules.forEach(function(rule, index) {
    var fieldName;

    for (fieldName in rule) {
      if (rule.hasOwnProperty(fieldName) &&
        !schema.requiredRulesFields.hasOwnProperty(fieldName) &&
        !schema.optionalRulesFields.hasOwnProperty(fieldName)) {
        errors.push('rule ' + index +
          ' contains unknown property ' + fieldName);
      }
    }
  });
};

Config.prototype.checkForMisconfiguredRules = function(errors) {
  if (!this.rules) {
    return;
  }
  checkRulesAreSorted(this.rules, errors);
  checkRulesByReaction(this.rules, errors);
};

function checkRulesAreSorted(rules, errors) {
  var sorted = rules.slice().sort(compareRules),
      isSorted = function(rule, i) { return rule === sorted[i]; },
      NUM_SPACES = 2;

  if (!rules.every(isSorted)) {
    errors.push('rules are not sorted; expected: ' +
      JSON.stringify(sorted, null, NUM_SPACES).replace(/\n/g, '\n  '));
  }
}

function compareRules(lhs, rhs) {
  return lhs.reactionName.localeCompare(rhs.reactionName) ||
    compareChannelNames(lhs.channelNames, rhs.channelNames) ||
    lhs.githubRepository.localeCompare(rhs.githubRepository);
}

function compareChannelNames(lhs, rhs) {
  if (lhs !== undefined && rhs === undefined) {
    return -1;
  }
  if (lhs === undefined && rhs !== undefined) {
    return 1;
  }
  return 0;
}

function checkRulesByReaction(rules, errors) {
  var rulesByReaction = groupRulesBy(rules, 'reactionName');

  for (var reactionName in rulesByReaction) {
    if (rulesByReaction.hasOwnProperty(reactionName)) {
      checkRulesForReaction(rulesByReaction, reactionName, errors);
    }
  }
}

function groupRulesBy(rules, property) {
  var result = {};

  rules.forEach(function(rule) {
    (result[rule[property]] = result[rule[property]] || []).push(rule);
  });
  return result;
}

function checkRulesForReaction(rulesByReaction, reactionName, errors) {
  var rules = rulesByReaction[reactionName];
  checkForDuplicateRepositories(rules, reactionName, errors);
  checkForDuplicateChannels(rules, reactionName, errors);
  checkForDuplicateAllChannelRules(rules, reactionName, errors);
}

function checkForDuplicateRepositories(rules, reactionName, errors) {
  var duplicateRepos = rules.map(function(r) { return r.githubRepository; })
    .sort(function(lhs, rhs) { return lhs.localeCompare(rhs); })
    .filter(detectDuplicates);

  if (duplicateRepos.length !== 0) {
    errors.push('duplicate repositories for ' + reactionName + ' rules:');
    duplicateRepos.forEach(function(repo) { errors.push('  ' + repo); });
  }
}

function checkForDuplicateChannels(rules, reactionName, errors) {
  var duplicateChannels = rules.reduce(reduceChannels, [])
    .sort(sortStrings)
    .filter(detectDuplicates);

  if (duplicateChannels.length !== 0) {
    errors.push('duplicate channels for ' + reactionName + ' rules:');
    duplicateChannels.forEach(function(channel) {
      errors.push('  ' + channel);
    });
  }
}

function reduceChannels(channels, rule) {
  Array.prototype.push.apply(channels, rule.channelNames || []);
  return channels;
}

function detectDuplicates(property, index, properties) {
  return index !== 0 && properties[index - 1] === property;
}

function checkForDuplicateAllChannelRules(rules, reactionName, errors) {
  var allChannelRules = rules.filter(function(rule) {
    return rule.channelNames === undefined;
  });

  if (allChannelRules.length > 1) {
    errors.push('multiple all-channel rules defined for ' + reactionName);
  }
}
<|endoftext|>


<commit_before>let _ = require('lodash');
let path = require('path');

module.exports = (appDirectory, config) => {
  config = _.merge({
    debug: true,
    paths: {
      controllers: 'controllers',
      views: 'views',
      static: 'static',
      docs: 'docs',
      crons: 'crons'
    },
    db: {
      host: 'localhost',
      port: 5984,
      db: 'express-yourself',
      secure: false
    },
    search: {
      host: 'http://localhost:9200',
      index: 'express-yourself'
    },
    logging: {
      console: { level: 'debug' },
      file: path.join('logs', 'log.txt'),
      couchdb: true
    },
    mailer: false,
    hostname: 'http://localhost:8080'
  }, config);

  config.paths = _.mapValues(config.paths, (val) => path.join(appDirectory, val));
  if (config.logging.file)
    config.logging.file = path.join(appDirectory, config.logging.file);

  config.db.name =
    `http${config.db.secure ? 's' : ''}://${config.db.host}:${config.db.port}/${config.db.db}`;

  return config;
}<commit_msg>Add upload directory to config<commit_after>let _ = require('lodash');
let path = require('path');

module.exports = (appDirectory, config) => {
  config = _.merge({
    debug: true,
    paths: {
      controllers: 'controllers',
      views: 'views',
      static: 'static',
      docs: 'docs',
      crons: 'crons',
      uploads: '/tmp'
    },
    db: {
      host: 'localhost',
      port: 5984,
      db: 'express-yourself',
      secure: false
    },
    search: {
      host: 'http://localhost:9200',
      index: 'express-yourself'
    },
    logging: {
      console: { level: 'debug' },
      file: path.join('logs', 'log.txt'),
      couchdb: true
    },
    mailer: false,
    hostname: 'http://localhost:8080'
  }, config);

  config.paths = _.mapValues(config.paths, (val) => {
    if (path.isAbsolute(val))
      return val;
    return path.join(appDirectory, val)
  });
  if (config.logging.file)
    config.logging.file = path.join(appDirectory, config.logging.file);

  config.db.name =
    `http${config.db.secure ? 's' : ''}://${config.db.host}:${config.db.port}/${config.db.db}`;

  return config;
}<|endoftext|>


<commit_before>module.exports = {
    testFramework: {
        defaults: {
            javascript: 'cw-2',
            coffeescript: 'cw-2',
            ruby: 'cw-2',
            python: 'unittest'
        }
    },
    timeouts: {
        javascript: 5000,
        coffeescript: 5000,
        ruby: 5000,
        python: 5000
    },
    snippets: {
        javascript: {
            requireCw2: "require('./frameworks/javascript/cw-2')\n",
            inlineTestFixture: {
                start: "(function() { var Test = this.Test, describe = this.describe, it = this.it, before = this.before, after = this.after;try{\n",
                end: '}catch(ex){Test.handleError(ex)}})();'
            }
        }
    },
    cmds: {
        dockerRun: "docker run --rm -m 125mb codewars/cli-runner --format json"
    }
};<commit_msg>removed memory limit since Digital Ocean kernel doesn't allow swap limits<commit_after>module.exports = {
    testFramework: {
        defaults: {
            javascript: 'cw-2',
            coffeescript: 'cw-2',
            ruby: 'cw-2',
            python: 'unittest'
        }
    },
    timeouts: {
        javascript: 5000,
        coffeescript: 5000,
        ruby: 5000,
        python: 5000
    },
    snippets: {
        javascript: {
            requireCw2: "require('./frameworks/javascript/cw-2')\n",
            inlineTestFixture: {
                start: "(function() { var Test = this.Test, describe = this.describe, it = this.it, before = this.before, after = this.after;try{\n",
                end: '}catch(ex){Test.handleError(ex)}})();'
            }
        }
    },
    cmds: {
        dockerRun: "docker run --rm codewars/cli-runner --format json"
    }
};<|endoftext|>