<commit_before>const Plugged = require('plugged')
const path = require('path')
const { inherits } = require('util')
const fs = require('fs')
const debug = require('debug')('sekshi:sekshi')
const logChat = require('debug')('sekshi:chat')
const mongoose = require('mongoose')
const Promise = require('bluebird')
const find = require('array-find')
const includes = require('array-includes')
const mkdirp = require('mkdirp')
const { User } = require('./models')
const commandsSymbol = require('./command').symbol
const ModuleManager = require('./ModuleManager')
const { splitMessageSemiProperlyMaybe } = require('./utils')

mongoose.Promise = Promise

export default class Sekshi extends Plugged {
  constructor(args) {
    super({ messageProc: splitMessageSemiProperlyMaybe })
    this.invokeLogger(this._debug)

    this.options = args
    this.db = mongoose.connect(args.mongo)

    this.modules = new ModuleManager(this, path.join(__dirname, 'modules'))
    this.delimiter = args.delimiter || '!'

    this._room = args.room
    this._configDir = path.join(__dirname, '../.config')

    this.onMessage = this.onMessage.bind(this)
    this.onUserUpdate = this.onUserUpdate.bind(this)
  }

  _debug(msg, verbosity, color) {
    debug(msg)
  }

  start(creds, cb) {
    this.login(creds)

    this.once(this.LOGIN_SUCCESS, () => {
      this.cacheChat(true)
      this.connect(this.options.room)
      this.loadModules()
    })

    this.on(this.CHAT, this.onMessage)
    this.on(this.USER_UPDATE, this.onUserUpdate)

    this.once(this.JOINED_ROOM, (room) => {
      mkdirp(this._configDir, (e) => {
        if (cb) cb(e || null, room || null)
      })
    })

    // the event is fired on nextTick so modules can simply listen for "moduleloaded"
    // and get events for *all* the modules when loadModules() is called, even for those
    // that register earlier
    this.modules.on('load', (mod, name) => {
      setImmediate(() => { this.emit('moduleloaded', mod, name) })
    })
    this.modules.on('unload', (mod, name) => {
      setImmediate(() => { this.emit('moduleunloaded', mod, name) })
    })
  }

  stop(cb) {
    this.unloadModules()
    this.logout()
    // should *probably* also wait for this before callback-ing
    mongoose.disconnect()

    this.once(this.LOGOUT_SUCCESS, () => {
      this.removeAllListeners()
      if (cb) {
        cb()
      }
    })
    this.once(this.LOGOUT_ERROR, e => {
      // TODO figure out something useful to do here
      this.removeAllListeners()
      if (cb) {
        cb(e)
      }
    })
  }

  setRoom(room) {
    this.options.room = room
    this.connect(room)
  }

  // updates user name, avatar and level
  onUserUpdate(update) {
    const user = this.getUserByID(update.id)
    if (!user) return
    User.findById(user.id).exec().then(model => {
      if (!model) return
      if (update.level) {
        user.level = update.level
        model.set('level', update.level)
      }
      if (update.avatarID) {
        user.avatarID = update.avatarID
        model.set('avatar', update.avatarID)
      }
      if (update.username) {
        user.username = update.username
        model.set('username', update.username)
      }
      model.save()
    })
  }

  onMessage(msg) {
    if (!this.getCurrentRoomStats()) {
      return
    }
    if (this.getSelf().id === msg.uid) {
      return
    }

    msg.message = msg.message.replace(/\\"/g, '"')

    logChat(msg.username, msg.message)
    if (msg.message.charAt(0) === this.delimiter) {
      let user = msg.id === 'sekshi' ? { role: this.USERROLE.HOST }
                                     : this.getUserByID(msg.id, true)

      // nonexistent user
      if (!user) return
      // Somehow plug decided to not handle chat levels on the server side, so if people
      // work around the client side chat level restriction, we can still get messages
      // from people whose level is below the minimum chat level for the room -.-
      if ('level' in user && user.level < this.getRoomMeta().minChatLevel) {
        return
      }

      user.role = user.role || this.USERROLE.NONE

      let args = this.parseArguments(msg.message)

      let commandName = args.shift().replace(this.delimiter, '').toLowerCase()

      this.modules.loaded().forEach(name => {
        let mod = this.modules.get(name)
        if (!mod) return
        if (!mod.enabled() || !Array.isArray(mod.commands)) return

        let command = find(mod.commands, com => includes(com.names, commandName))
        if (!command) return

        if (command.ninjaVanish) {
          this.deleteMessage(msg.cid)
        }
        if (user.role >= command.role) {
          if (command.method) {
            mod[command.method](user, ...args)
          }
          else {
            command.callback.call(mod, user, ...args)
          }
        }
        else {
          this.sendChat(`@${msg.username}: You don't have sufficient permissions to use this command.`, 5 * 1000)
        }
      })
    }
  }

  // Parses space-separated chat command arguments.
  // single words become single arguments.
  //    word → [ 'word ']
  // strings surrounded by double quotes become single arguments.
  //    "quoted words" word → [ 'quoted words', 'word' ]
  // strings prefixed with "@" are matched to the online user list.
  // if any online user's name matches the string, it will be passed instead.
  // this is so that you don't need quotes around usernames with spaces.
  //    @Online User parameter @Offline User → [ 'Online User', 'parameter', '@Offline', 'User' ]
  //
  // feature-bugs:
  // if you forget to close a quoted string it will go until the end of the line (might be unexpected)
  // if you forget to add a space after a quoted string, the rest will be read as a separate parameter
  parseArguments(str = '') {
    let args = []
    let i = 0
    let chunk

    let usernames = str.indexOf('@') !== -1 // might contain a username
      ? [ this.getSelf(), ...this.getUsers() ]
          .map(u => u.username)
          // longest usernames first
          .sort((a, b) => a.length > b.length ? -1 : 1)
      : []

    while (chunk = str.slice(i)) {
      // separator
      if (chunk.charAt(0) === ' ') {
        i++
        continue
      }
      // quoted string
      else if (chunk.charAt(0) === '"') {
        let end = chunk.indexOf('"', 1)
        // end of param string
        if (end === -1) {
          args.push(chunk.slice(1))
          break
        }
        args.push(chunk.slice(1, end))
        i += end + 1
        continue
      }
      // possible username
      else if (chunk.charAt(0) === '@') {
        let username = find(usernames,
                            name => chunk.slice(1, name.length + 1).toLowerCase() === name.toLowerCase())
        if (username) {
          args.push(username)
          i += `@${username}`.length
          continue
        }
      }
      // single parameter word
      let end = chunk.indexOf(' ')
      // end of param string
      if (end === -1) {
        args.push(chunk)
        break
      }
      args.push(chunk.slice(0, end))
      i += end + 1
      continue
    }

    return args
  }

  lockskipDJ(id, position, cb) {
    const skipDJ = Promise.promisify(this.skipDJ, this)
    const addToWaitlist = Promise.promisify(this.addToWaitlist, this)
    const moveDJ = Promise.promisify(this.moveDJ, this)
    const setLock = Promise.promisify(this.setLock, this)

    if (this.doesWaitlistCycle()) {
      skipDJ(id)
        .then(() => moveDJ(id, position))
        .nodeify(cb)
    }
    else {
      let locked = this.isWaitlistLocked()
      // DJ cycle is off
      // first, lock the wait list so no-one can steal the 50th spot before the DJ is put back
      setLock(true, false)
        // then skip & add the DJ again
        .then(() => skipDJ(id))
        .then(() => addToWaitlist(id))
        .then(() => moveDJ(id, position))
        // and finally revert to the old wait list lock status
        .then(() => setLock(locked, false),
              () => setLock(locked, false))
        .nodeify(cb)
    }
  }

  getModule(name) {
    return this.modules.get(name)
  }

  loadModules() {
    debug('load all')
    this.modules.update()
      .each(name => this.modules.load(name))
  }

  unloadModules() {
    debug('unload all')
    this.modules.loaded()
      .forEach(name => this.modules.unload(name))
  }

  getConfigDir() {
    return this._configDir
  }
}
<commit_msg>core: add findUser method for retrieving user models by name<commit_after>const Plugged = require('plugged')
const path = require('path')
const fs = require('fs')
const debug = require('debug')('sekshi:sekshi')
const logChat = require('debug')('sekshi:chat')
const mongoose = require('mongoose')
const Promise = require('bluebird')
const find = require('array-find')
const includes = require('array-includes')
const mkdirp = require('mkdirp')
const { User } = require('./models')
const commandsSymbol = require('./command').symbol
const ModuleManager = require('./ModuleManager')
const { splitMessageSemiProperlyMaybe } = require('./utils')
const quote = require('regexp-quote')

mongoose.Promise = Promise

export default class Sekshi extends Plugged {
  constructor(args) {
    super({ messageProc: splitMessageSemiProperlyMaybe })
    this.invokeLogger(this._debug)

    this.options = args
    this.db = mongoose.connect(args.mongo)

    this.modules = new ModuleManager(this, path.join(__dirname, 'modules'))
    this.delimiter = args.delimiter || '!'

    this._room = args.room
    this._configDir = path.join(__dirname, '../.config')

    this.onMessage = this.onMessage.bind(this)
    this.onUserUpdate = this.onUserUpdate.bind(this)
  }

  _debug(msg, verbosity, color) {
    debug(msg)
  }

  start(creds, cb) {
    this.login(creds)

    this.once(this.LOGIN_SUCCESS, () => {
      this.cacheChat(true)
      this.connect(this.options.room)
      this.loadModules()
    })

    this.on(this.CHAT, this.onMessage)
    this.on(this.USER_UPDATE, this.onUserUpdate)

    this.once(this.JOINED_ROOM, (room) => {
      mkdirp(this._configDir, (e) => {
        if (cb) cb(e || null, room || null)
      })
    })

    // the event is fired on nextTick so modules can simply listen for "moduleloaded"
    // and get events for *all* the modules when loadModules() is called, even for those
    // that register earlier
    this.modules.on('load', (mod, name) => {
      setImmediate(() => { this.emit('moduleloaded', mod, name) })
    })
    this.modules.on('unload', (mod, name) => {
      setImmediate(() => { this.emit('moduleunloaded', mod, name) })
    })
  }

  stop(cb) {
    this.unloadModules()
    this.logout()
    // should *probably* also wait for this before callback-ing
    mongoose.disconnect()

    this.once(this.LOGOUT_SUCCESS, () => {
      this.removeAllListeners()
      if (cb) {
        cb()
      }
    })
    this.once(this.LOGOUT_ERROR, e => {
      // TODO figure out something useful to do here
      this.removeAllListeners()
      if (cb) {
        cb(e)
      }
    })
  }

  setRoom(room) {
    this.options.room = room
    this.connect(room)
  }

  // Find a user model or default to something.
  // Useful for commands that can optionally take a target user.
  findUser(name, _default = null) {
    if (!name) {
      return _default
        ? Promise.resolve(_default)
        : Promise.reject(new Error('No user given'))
    }

    let promise
    let user = this.getUserByName(name)
    if (user) {
      promise = User.findById(user.id)
    }
    else {
      let rx = new RegExp(`^${quote(name)}$`, 'i')
      promise = User.findOne({ username: rx })
    }
    return promise.then(user => {
      return user || Promise.reject(new Error('User not found'))
    })
  }

  // updates user name, avatar and level
  onUserUpdate(update) {
    const user = this.getUserByID(update.id)
    if (!user) return
    User.findById(user.id).exec().then(model => {
      if (!model) return
      if (update.level) {
        user.level = update.level
        model.set('level', update.level)
      }
      if (update.avatarID) {
        user.avatarID = update.avatarID
        model.set('avatar', update.avatarID)
      }
      if (update.username) {
        user.username = update.username
        model.set('username', update.username)
      }
      model.save()
    })
  }

  onMessage(msg) {
    if (!this.getCurrentRoomStats()) {
      return
    }
    if (this.getSelf().id === msg.uid) {
      return
    }

    msg.message = msg.message.replace(/\\"/g, '"')

    logChat(msg.username, msg.message)
    if (msg.message.charAt(0) === this.delimiter) {
      let user = msg.id === 'sekshi' ? { role: this.USERROLE.HOST }
                                     : this.getUserByID(msg.id, true)

      // nonexistent user
      if (!user) return
      // Somehow plug decided to not handle chat levels on the server side, so if people
      // work around the client side chat level restriction, we can still get messages
      // from people whose level is below the minimum chat level for the room -.-
      if ('level' in user && user.level < this.getRoomMeta().minChatLevel) {
        return
      }

      user.role = user.role || this.USERROLE.NONE

      let args = this.parseArguments(msg.message)

      let commandName = args.shift().replace(this.delimiter, '').toLowerCase()

      this.modules.loaded().forEach(name => {
        let mod = this.modules.get(name)
        if (!mod) return
        if (!mod.enabled() || !Array.isArray(mod.commands)) return

        let command = find(mod.commands, com => includes(com.names, commandName))
        if (!command) return

        if (command.ninjaVanish) {
          this.deleteMessage(msg.cid)
        }
        if (user.role >= command.role) {
          if (command.method) {
            mod[command.method](user, ...args)
          }
          else {
            command.callback.call(mod, user, ...args)
          }
        }
        else {
          this.sendChat(`@${msg.username}: You don't have sufficient permissions to use this command.`, 5 * 1000)
        }
      })
    }
  }

  // Parses space-separated chat command arguments.
  // single words become single arguments.
  //    word → [ 'word ']
  // strings surrounded by double quotes become single arguments.
  //    "quoted words" word → [ 'quoted words', 'word' ]
  // strings prefixed with "@" are matched to the online user list.
  // if any online user's name matches the string, it will be passed instead.
  // this is so that you don't need quotes around usernames with spaces.
  //    @Online User parameter @Offline User → [ 'Online User', 'parameter', '@Offline', 'User' ]
  //
  // feature-bugs:
  // if you forget to close a quoted string it will go until the end of the line (might be unexpected)
  // if you forget to add a space after a quoted string, the rest will be read as a separate parameter
  parseArguments(str = '') {
    let args = []
    let i = 0
    let chunk

    let usernames = str.indexOf('@') !== -1 // might contain a username
      ? [ this.getSelf(), ...this.getUsers() ]
          .map(u => u.username)
          // longest usernames first
          .sort((a, b) => a.length > b.length ? -1 : 1)
      : []

    while (chunk = str.slice(i)) {
      // separator
      if (chunk.charAt(0) === ' ') {
        i++
        continue
      }
      // quoted string
      else if (chunk.charAt(0) === '"') {
        let end = chunk.indexOf('"', 1)
        // end of param string
        if (end === -1) {
          args.push(chunk.slice(1))
          break
        }
        args.push(chunk.slice(1, end))
        i += end + 1
        continue
      }
      // possible username
      else if (chunk.charAt(0) === '@') {
        let username = find(usernames,
                            name => chunk.slice(1, name.length + 1).toLowerCase() === name.toLowerCase())
        if (username) {
          args.push(username)
          i += `@${username}`.length
          continue
        }
      }
      // single parameter word
      let end = chunk.indexOf(' ')
      // end of param string
      if (end === -1) {
        args.push(chunk)
        break
      }
      args.push(chunk.slice(0, end))
      i += end + 1
      continue
    }

    return args
  }

  lockskipDJ(id, position, cb) {
    const skipDJ = Promise.promisify(this.skipDJ, this)
    const addToWaitlist = Promise.promisify(this.addToWaitlist, this)
    const moveDJ = Promise.promisify(this.moveDJ, this)
    const setLock = Promise.promisify(this.setLock, this)

    if (this.doesWaitlistCycle()) {
      skipDJ(id)
        .then(() => moveDJ(id, position))
        .nodeify(cb)
    }
    else {
      let locked = this.isWaitlistLocked()
      // DJ cycle is off
      // first, lock the wait list so no-one can steal the 50th spot before the DJ is put back
      setLock(true, false)
        // then skip & add the DJ again
        .then(() => skipDJ(id))
        .then(() => addToWaitlist(id))
        .then(() => moveDJ(id, position))
        // and finally revert to the old wait list lock status
        .then(() => setLock(locked, false),
              () => setLock(locked, false))
        .nodeify(cb)
    }
  }

  getModule(name) {
    return this.modules.get(name)
  }

  loadModules() {
    debug('load all')
    this.modules.update()
      .each(name => this.modules.load(name))
  }

  unloadModules() {
    debug('unload all')
    this.modules.loaded()
      .forEach(name => this.modules.unload(name))
  }

  getConfigDir() {
    return this._configDir
  }
}
<|endoftext|>


<commit_before>//require('./testModBrowse.js');
//require('./testModBrowser.js');
//require('./testTemp.js');

require('./testModFlow.js');
require('./testModHTMLRenderer.js');
require('./testModOfflinePage.js');
require('./testModPage.js');
require('./testEvalParser.js');<commit_msg>reorder test case<commit_after>//require('./testModBrowse.js');
//require('./testModBrowser.js');
//require('./testTemp.js');
require('./testEvalParser.js');
require('./testModFlow.js');
require('./testModHTMLRenderer.js');
require('./testModOfflinePage.js');
require('./testModPage.js');<|endoftext|>


<commit_before>/**
 * FCUI (Fengchao UI)
 * Copyright 2014 Baidu Inc. All rights reserved.
 *
 * @file
 * @author Cory(kuanghongrui@baidu.com)
 */
define(function (require) {
    var _ = require('underscore');
    var fc = require('fc-core');
    var fcui = require('./main');
    var helper = require('./controlHelper');
    var InputControl = require('./InputControl');
    var lib = require('./lib');
    var Select = require('esui/Select');
    var Layer = require('./Layer');

    /**
     * select的浮层类型。现已把select当成dropdown来使用了。
     * 这种方式很不好，但是esui的layer架构改变不了呀。需要从长计议。
     * @type {Object}
     */
    var LAYER_TYPE = {
        DEFAULT: 'default', // 默认的layer类型，则为正常的select交互。
        CUSTOM: 'custom' // 用户自定义layer类型，则可以自定义layer里面的内容。
    };

    /**
     * `Select`控件使用的自定义层类
     *
     * @extends Layer
     * @ignore
     * @constructor
     */
    function CustomLayer() {
        Layer.apply(this, arguments);
    }
    lib.inherits(CustomLayer, Layer);

    /**
     * 自定义的layer渲染方法。
     * @override
     */
    CustomLayer.prototype.render = function (element) {
        var customLayerHTML = this.control.customLayerHTML;
        if (customLayerHTML) {
            element.innerHTML = customLayerHTML;
            var controls = this.layerControls = fcui.init(element, {
                viewContext: this.control.viewContext
            });
            if (controls && controls.length > 0) {
                controls[0].on('submit', _.bind(function (e) {
                    this.control.fire('layersubmit', fc.util.customData(e.data));
                    this.hide();
                }, this));
            }
        }
    };

    /**
     * 同步控件状态到层。
     * @override
     */
    CustomLayer.prototype.syncState = function (element) {
        var layerControls = this.layerControls;
        if (layerControls && layerControls.length > 0) {
            _.each(layerControls, function (control) {
                if (control.setValue) {
                    control.setValue(this.control.get('rawValue'));
                }
            }, this);
        }
    };

    CustomLayer.prototype.dock = {
        strictWidth: true
    };

    /**
     * select的浮层类型.
     * 若该类型为空，则为默认的select交互浮层效果。
     * @type {string}
     */
    Select.prototype.layerType = LAYER_TYPE.DEFAULT;

    /**
     * 显示label及选中值的模板
     *
     * @type {string}
     */
    Select.prototype.labelTemplate = '${label}';

    /**
     * 原始初始化方法。
     * @inner
     * @type {function}
     */
    var initStructure = Select.prototype.initStructure;

    /**
     * 初始化DOM结构
     *
     * @protected
     * @override
     */
    Select.prototype.initStructure = function () {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            if (this.layer) {
                this.layer.dispose();
            }
            this.layer = new CustomLayer(this);
            var layerContent = this.main.querySelector('.custom-layer-content').innerHTML.replace(/(^\s+)|(\s+$)/g, '');
            if (layerContent) {
                this.customLayerHTML = layerContent;
            }
        }
        initStructure.apply(this, arguments);
    };

    /**
     * 原始初始化事件交互方法。
     * @inner
     * @type {function}
     */
    var initEvents = Select.prototype.initEvents;

    /**
     * 初始化事件交互
     *
     * @protected
     * @override
     */
    Select.prototype.initEvents = function () {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            this.on('layersubmit', _.bind(this.layerSubmit, this));
        }
        initEvents.apply(this, arguments);
    };

    /**
     * 弹出层提交。
     *
     * @param {Event} e
     */
    Select.prototype.layerSubmit = function (e) {
        this.set('value', e.data);
    };

    /**
     * 原始获取原始值方法。
     * @inner
     * @type {function}
     */
    var getRawValue = Select.prototype.getRawValue;

    /**
     * 获取原始值
     * @override
     * @return {Object}
     */
    Select.prototype.getRawValue = function () {
        if (this.layerType === LAYER_TYPE.DEFAULT) {
            return getRawValue.apply(this, arguments);
        }
        return this.rawValue;
    };

    /**
     * 设置显示文字。
     * @param {string} displayText 所要设置的显示文字，若该文字为null或空字符串，则将由getDisplayHTML来生成相应的显示文字，
     */
    Select.prototype.setDisplayText = function (displayText) {
        var textHolder = this.helper.getPart('text');
        var displayHTML = null;
        if (displayText) {
            displayHTML = this._getDisplayHTML({
                text: displayText
            });
        }
        else {
            var selectedItem = this.selectedIndex === -1
                ? null
                : this.datasource[this.selectedIndex];
            displayHTML = this.getDisplayHTML(selectedItem);
        }
        textHolder.innerHTML = displayHTML;
    };

    /**
     * 获取显示的label+option名字的html字符串。
     * @param {Object} data displayTemplate中的数据。
     * @protected
     */
    Select.prototype._getDisplayHTML = function (data) {
        var label = _.escape(this.label);
        var labelWrapData = {
            label: label,
            labelClass: helper.getPartClasses(this, 'label').join(' ')
        };
        var labelHtml = label ? lib.format(this.labelTemplate, labelWrapData) : '';
        if (!data) {
            return labelHtml + _.escape(this.emptyText || '');
        }
        return labelHtml + lib.format(this.displayTemplate, data);
    },

    /**
     * 根据所选择的选项，获取其显示的label+option名字的html字符串。
     * @override
     * @return {string}
     */
    Select.prototype.getDisplayHTML = function (item) {
        var data = null;
        if (item) {
            data = {
                text: _.escape(item.name || item.text),
                value: _.escape(item.value)
            };
        }
        return this._getDisplayHTML(data);
    };

    var setProperties = Select.prototype.setProperties;

    /**
     * 批量更新属性并重绘
     *
     * @param {Object} properties 需更新的属性
     * @override
     * @fires change
     */
    Select.prototype.setProperties = function (properties) {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            var changes = InputControl.prototype.setProperties.apply(this, arguments);
            if (changes.hasOwnProperty('value') || changes.hasOwnProperty('rawValue')) {
                this.fire('change');
            }
            return changes;
        }
        return setProperties.apply(this, arguments);
    };

    return Select;
});
<commit_msg>修复了layer渲染Control的问题<commit_after>/**
 * FCUI (Fengchao UI)
 * Copyright 2014 Baidu Inc. All rights reserved.
 *
 * @file
 * @author Cory(kuanghongrui@baidu.com)
 */
define(function (require) {
    var _ = require('underscore');
    var fc = require('fc-core');
    var fcui = require('./main');
    var helper = require('./controlHelper');
    var InputControl = require('./InputControl');
    var lib = require('./lib');
    var Select = require('esui/Select');
    var Layer = require('./Layer');

    /**
     * select的浮层类型。现已把select当成dropdown来使用了。
     * 这种方式很不好，但是esui的layer架构改变不了呀。需要从长计议。
     * @type {Object}
     */
    var LAYER_TYPE = {
        DEFAULT: 'default', // 默认的layer类型，则为正常的select交互。
        CUSTOM: 'custom' // 用户自定义layer类型，则可以自定义layer里面的内容。
    };

    /**
     * `Select`控件使用的自定义层类
     *
     * @extends Layer
     * @ignore
     * @constructor
     */
    function CustomLayer() {
        Layer.apply(this, arguments);
    }
    lib.inherits(CustomLayer, Layer);

    /**
     * 自定义的layer渲染方法。
     * @override
     */
    CustomLayer.prototype.render = function (element) {
        var customLayerHTML = this.control.customLayerHTML;
        if (customLayerHTML) {
            element.innerHTML = customLayerHTML;
            var controls = this.layerControls = fcui.init(element, {
                viewContext: this.control.viewContext
            });
            if (controls && controls.length > 0) {
                controls[0].on('submit', _.bind(function (e) {
                    this.control.fire('layersubmit', fc.util.customData(e.data));
                    this.hide();
                }, this));
            }
        }
    };

    /**
     * 同步控件状态到层。
     * @override
     */
    CustomLayer.prototype.syncState = function (element) {
        var layerControls = this.layerControls;
        if (layerControls && layerControls.length > 0) {
            _.each(layerControls, function (control) {
                if (control.setValue) {
                    control.setValue(this.control.get('rawValue'));
                }
            }, this);
        }
    };

    CustomLayer.prototype.dock = {
        strictWidth: true
    };

    /**
     * select的浮层类型.
     * 若该类型为空，则为默认的select交互浮层效果。
     * @type {string}
     */
    Select.prototype.layerType = LAYER_TYPE.DEFAULT;

    /**
     * 显示label及选中值的模板
     *
     * @type {string}
     */
    Select.prototype.labelTemplate = '${label}';

    /**
     * 原始初始化方法。
     * @inner
     * @type {function}
     */
    var initStructure = Select.prototype.initStructure;

    /**
     * 初始化DOM结构
     *
     * @protected
     * @override
     */
    Select.prototype.initStructure = function () {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            if (this.layer) {
                this.layer.dispose();
            }
            this.layer = new CustomLayer(this);
            this.layer.getElement(true);
            var layerContent = this.main.querySelector('.custom-layer-content').innerHTML.replace(/(^\s+)|(\s+$)/g, '');
            if (layerContent) {
                this.customLayerHTML = layerContent;
            }
        }
        initStructure.apply(this, arguments);
    };

    /**
     * 原始初始化事件交互方法。
     * @inner
     * @type {function}
     */
    var initEvents = Select.prototype.initEvents;

    /**
     * 初始化事件交互
     *
     * @protected
     * @override
     */
    Select.prototype.initEvents = function () {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            this.on('layersubmit', _.bind(this.layerSubmit, this));
        }
        initEvents.apply(this, arguments);
    };

    /**
     * 弹出层提交。
     *
     * @param {Event} e
     */
    Select.prototype.layerSubmit = function (e) {
        this.set('value', e.data);
    };

    /**
     * 原始获取原始值方法。
     * @inner
     * @type {function}
     */
    var getRawValue = Select.prototype.getRawValue;

    /**
     * 获取原始值
     * @override
     * @return {Object}
     */
    Select.prototype.getRawValue = function () {
        if (this.layerType === LAYER_TYPE.DEFAULT) {
            return getRawValue.apply(this, arguments);
        }
        return this.rawValue;
    };

    /**
     * 设置显示文字。
     * @param {string} displayText 所要设置的显示文字，若该文字为null或空字符串，则将由getDisplayHTML来生成相应的显示文字，
     */
    Select.prototype.setDisplayText = function (displayText) {
        var textHolder = this.helper.getPart('text');
        var displayHTML = null;
        if (displayText) {
            displayHTML = this._getDisplayHTML({
                text: displayText
            });
        }
        else {
            var selectedItem = this.selectedIndex === -1
                ? null
                : this.datasource[this.selectedIndex];
            displayHTML = this.getDisplayHTML(selectedItem);
        }
        textHolder.innerHTML = displayHTML;
    };

    /**
     * 获取显示的label+option名字的html字符串。
     * @param {Object} data displayTemplate中的数据。
     * @protected
     */
    Select.prototype._getDisplayHTML = function (data) {
        var label = _.escape(this.label);
        var labelWrapData = {
            label: label,
            labelClass: helper.getPartClasses(this, 'label').join(' ')
        };
        var labelHtml = label ? lib.format(this.labelTemplate, labelWrapData) : '';
        if (!data) {
            return labelHtml + _.escape(this.emptyText || '');
        }
        return labelHtml + lib.format(this.displayTemplate, data);
    },

    /**
     * 根据所选择的选项，获取其显示的label+option名字的html字符串。
     * @override
     * @return {string}
     */
    Select.prototype.getDisplayHTML = function (item) {
        var data = null;
        if (item) {
            data = {
                text: _.escape(item.name || item.text),
                value: _.escape(item.value)
            };
        }
        return this._getDisplayHTML(data);
    };

    var setProperties = Select.prototype.setProperties;

    /**
     * 批量更新属性并重绘
     *
     * @param {Object} properties 需更新的属性
     * @override
     * @fires change
     */
    Select.prototype.setProperties = function (properties) {
        if (this.layerType === LAYER_TYPE.CUSTOM) {
            var changes = InputControl.prototype.setProperties.apply(this, arguments);
            if (changes.hasOwnProperty('value') || changes.hasOwnProperty('rawValue')) {
                this.fire('change');
            }
            return changes;
        }
        return setProperties.apply(this, arguments);
    };

    return Select;
});
<|endoftext|>


<commit_before>var request = require('supertest'),
    express = require('express'),
    assert = require('assert'),
    chai = require('chai'),
    expect = chai.expect;

// Run the tests against the prod mongolab db
//process.env.NODE_ENV = "production";
//var config = require('config');

var app = require('../main');

describe('clues', function(){
  this.timeout(50000);
  it('gets clues', function(done){
    request(app)
        .get('/api/clues/')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(1000);
          done();
        });
  })
});

describe('random clues', function(){
  this.timeout(50000);
  it('gets random clues', function(done){
        request(app)
            .get('/api/clues/r/')
            .expect('Content-Type', /json/)
            .expect(200)
            .end(function(err, res){
                if (err) throw err;
                //console.log("Clues returned: " + res.body.length);
                done();
            });
    });

  it('gets 10 random clues', function(done){
    request(app)
        .get('/api/clues/r/10')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(10);
          done();
        });
  })

});

describe('categories', function(){
  this.timeout(50000);
  it('gets 10 random categories', function(done){
    request(app)
        .get('/api/cats/r/')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(10);
          //console.log("Categories returned: " + res.body.length);
          console.log(res.body);
          done();
        });
  })
});
<commit_msg>quiet<commit_after>var request = require('supertest'),
    express = require('express'),
    assert = require('assert'),
    chai = require('chai'),
    expect = chai.expect;

// Run the tests against the prod mongolab db
//process.env.NODE_ENV = "production";
//var config = require('config');

var app = require('../main');

describe('clues', function(){
  this.timeout(50000);
  it('gets clues', function(done){
    request(app)
        .get('/api/clues/')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(1000);
          done();
        });
  })
});

describe('random clues', function(){
  this.timeout(50000);
  it('gets random clues', function(done){
        request(app)
            .get('/api/clues/r/')
            .expect('Content-Type', /json/)
            .expect(200)
            .end(function(err, res){
                if (err) throw err;
                //console.log("Clues returned: " + res.body.length);
                done();
            });
    });

  it('gets 10 random clues', function(done){
    request(app)
        .get('/api/clues/r/10')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(10);
          done();
        });
  })

});

describe('categories', function(){
  this.timeout(50000);
  it('gets 10 random categories', function(done){
    request(app)
        .get('/api/cats/r/')
        .expect('Content-Type', /json/)
        .expect(200)
        .end(function(err, res){
          if (err) throw err;
          expect(res.body.length).to.equal(10);
          //console.log(res.body);
          done();
        });
  })
});
<|endoftext|>


<commit_before>ThreeAudio.Source = function (fftSize) {
  this.fftSize = fftSize || 512;

  this.filters = {};
  this.playing = false;

  this.init();
}

ThreeAudio.Source.prototype = {

  init: function () {
    var c = this.context = new webkitAudioContext();

    // Create source
    this.source = c.createBufferSource();

    // Create main analyser
    this.analyser = c.createAnalyser();
    this.analyser.fftSize = this.fftSize;

    // Create filter nodes for bass/mid/treble signals.
    var parameters = {
      bass: {
        type: 0, // LOWPASS
        frequency: 160,
        Q: 1.2,
        gain: 2.0//,
      },
      mid: {
        type: 2, // BANDPASS
        frequency: 400,
        Q: 1.2,
        gain: 4.0//,
      },
      treble: {
        type: 1, //HIGHPASS
        frequency: 2000,
        Q: 1.2,
        gain: 3.0//,
      }//,
    };
    var filters = this.filters;
    _.each(parameters, function (spec, key) {
      var filter = c.createBiquadFilter();
      filter.key = key;
      filter.type = spec.type;
      filter.frequency.value = spec.frequency;
      filter.Q.value = spec.Q;

      // Create analyser for filtered signal.
      filter.analyser = c.createAnalyser();
      filter.analyser.fftSize = 512;

      // Create delay node to compensate for fftSize difference/lag
      // Note: disabled, Texture.js signal smoothing ends up adding enough filter delay to compensate.
      filter.delayNode = c.createDelayNode();
      filter.delayNode.delayTime.value = 0;
      //*(this.fftSize - 512) / c.sampleRate;

      // Create gain node to offset filter loss.
      filter.gainNode = c.createGainNode();
      filter.gainNode.gain.value = spec.gain;

      filters[key] = filter;
    });

    // Create playback delay to compensate for FFT lag.
    this.delay = c.createDelayNode();
    this.delay.delayTime.value = this.fftSize * 2 / c.sampleRate;

    // Connect main audio processing pipe
    this.source.connect(this.analyser);
    this.analyser.connect(this.delay);
    this.delay.connect(c.destination);

    // Connect secondary filters + analysers + gain.
    var source = this.source;
    _.each(filters, function (filter) {
      source.connect(filter.delayNode);
      filter.delayNode.connect(filter);
      filter.connect(filter.gainNode);
      filter.gainNode.connect(filter.analyser);
    });

    // Create buffers for time/freq data.
    this.samples = this.analyser.frequencyBinCount;
    this.data = {
      // High resolution FFT for frequency / time data
      freq: new Uint8Array(this.samples),
      time: new Uint8Array(this.samples),
      // Low resolution filtered signals, time data only.
      filter: {
        bass: new Uint8Array(256),
        mid: new Uint8Array(256),
        treble: new Uint8Array(256)//,
      }//,
    };
  },

  update: function () {
    var a = this.analyser, d = this.data;
    a.smoothingTimeConstant = 0;
    a.getByteFrequencyData(d.freq);
    a.getByteTimeDomainData(d.time);

    _.each(this.filters, function (filter) {
      filter.analyser.getByteTimeDomainData(d.filter[filter.key]);
    });

    return this;
  },

  size: function () {
    return this.analyser.frequencyBinCount;
  },

  load: function (url, callback) {
    var context = this.context,
        source = this.source;

    // Load file via AJAX
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.responseType = "arraybuffer";

    request.onload = (function() {
      // Link databuffer to source
      var buffer = context.createBuffer(request.response, false);
      source.buffer = buffer;
      source.loop = true;

      // Begin playback if requested earlier.
      if (this.playing) {
        this._play();
      }

      callback && callback();
    }).bind(this);

    request.send();

    return this;
  },

  play: function () {
    this.playing = true;
    if (this.source.buffer) {
      this._play();
    }
    return this;
  },

  stop: function () {
    this.playing = false;
    if (this.source.buffer) {
      this._stop();
    }
    return this;
  },

  _play: function () {
    this.source.noteOn(0);
  },

  _stop: function () {
    this.source.noteOff(0);
  }//,

};<commit_msg>Remove dependency on es5-shim<commit_after>ThreeAudio.Source = function (fftSize) {
  this.fftSize = fftSize || 512;

  this.filters = {};
  this.playing = false;

  this.init();
}

ThreeAudio.Source.prototype = {

  init: function () {
    var c = this.context = new webkitAudioContext();

    // Create source
    this.source = c.createBufferSource();

    // Create main analyser
    this.analyser = c.createAnalyser();
    this.analyser.fftSize = this.fftSize;

    // Create filter nodes for bass/mid/treble signals.
    var parameters = {
      bass: {
        type: 0, // LOWPASS
        frequency: 160,
        Q: 1.2,
        gain: 2.0//,
      },
      mid: {
        type: 2, // BANDPASS
        frequency: 400,
        Q: 1.2,
        gain: 4.0//,
      },
      treble: {
        type: 1, //HIGHPASS
        frequency: 2000,
        Q: 1.2,
        gain: 3.0//,
      }//,
    };
    var filters = this.filters;
    _.each(parameters, function (spec, key) {
      var filter = c.createBiquadFilter();
      filter.key = key;
      filter.type = spec.type;
      filter.frequency.value = spec.frequency;
      filter.Q.value = spec.Q;

      // Create analyser for filtered signal.
      filter.analyser = c.createAnalyser();
      filter.analyser.fftSize = 512;

      // Create delay node to compensate for fftSize difference/lag
      // Note: disabled, Texture.js signal smoothing ends up adding enough filter delay to compensate.
      filter.delayNode = c.createDelayNode();
      filter.delayNode.delayTime.value = 0;
      //*(this.fftSize - 512) / c.sampleRate;

      // Create gain node to offset filter loss.
      filter.gainNode = c.createGainNode();
      filter.gainNode.gain.value = spec.gain;

      filters[key] = filter;
    });

    // Create playback delay to compensate for FFT lag.
    this.delay = c.createDelayNode();
    this.delay.delayTime.value = this.fftSize * 2 / c.sampleRate;

    // Connect main audio processing pipe
    this.source.connect(this.analyser);
    this.analyser.connect(this.delay);
    this.delay.connect(c.destination);

    // Connect secondary filters + analysers + gain.
    var source = this.source;
    _.each(filters, function (filter) {
      source.connect(filter.delayNode);
      filter.delayNode.connect(filter);
      filter.connect(filter.gainNode);
      filter.gainNode.connect(filter.analyser);
    });

    // Create buffers for time/freq data.
    this.samples = this.analyser.frequencyBinCount;
    this.data = {
      // High resolution FFT for frequency / time data
      freq: new Uint8Array(this.samples),
      time: new Uint8Array(this.samples),
      // Low resolution filtered signals, time data only.
      filter: {
        bass: new Uint8Array(256),
        mid: new Uint8Array(256),
        treble: new Uint8Array(256)//,
      }//,
    };
  },

  update: function () {
    var a = this.analyser, d = this.data;
    a.smoothingTimeConstant = 0;
    a.getByteFrequencyData(d.freq);
    a.getByteTimeDomainData(d.time);

    _.each(this.filters, function (filter) {
      filter.analyser.getByteTimeDomainData(d.filter[filter.key]);
    });

    return this;
  },

  size: function () {
    return this.analyser.frequencyBinCount;
  },

  load: function (url, callback) {
    var context = this.context,
        source = this.source,
        that = this;

    // Load file via AJAX
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.responseType = "arraybuffer";

    request.onload = (function() {
      // Link databuffer to source
      var buffer = context.createBuffer(request.response, false);
      source.buffer = buffer;
      source.loop = true;

      // Begin playback if requested earlier.
      if (that.playing) {
        that._play();
      }

      callback && callback();
    };

    request.send();

    return this;
  },

  play: function () {
    this.playing = true;
    if (this.source.buffer) {
      this._play();
    }
    return this;
  },

  stop: function () {
    this.playing = false;
    if (this.source.buffer) {
      this._stop();
    }
    return this;
  },

  _play: function () {
    this.source.noteOn(0);
  },

  _stop: function () {
    this.source.noteOff(0);
  }//,

};<|endoftext|>


<commit_before>var vumigo = require('vumigo_v02');
var app = require('../lib/app');
var GoDiApp = app.GoDiApp;
var AppTester = vumigo.AppTester;

describe("app", function() {
    describe("GoDiApp", function() {
        var app;
        var tester;

        beforeEach(function() {
            app = new GoDiApp();

            tester = new AppTester(app);

            tester.setup.config.app({
                name: 'test_app'
            });
        });

        describe("when the user starts a session", function() {
            it("should ask them to choose a language ", function() {
                return tester
                    .start()
                    .check.interaction({
                        state: 'states:start',
                        reply: ['your language:',
                                '1. English',
                                '2. Afrikaans',
                                '3. Zulu',
                                '4. Xhosa',
                                '5. Sotho'
                        ].join('\n')
                    })
                    .run();
            });
        });

        describe("when the user has submitted their language", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('1')
                    .check.user.properties({lang: 'en'})
                    .run();
            });

            it("should show them the address menu in their language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('1')
                    .check.interaction({
                        state:'states:address',
                        reply:'To be completed'
                    }).run();
            });
        });

        describe("when the user has submitted their language", function() {
            it("should set the user's language",function() {
               return tester
                   .setup.user.state('states:start')
                   .input('2')
                   .check.user.properties({lang: 'af'})
                   .run();
            });
        });

        describe("when the user has submitted their language", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('3')
                    .check.user.properties({lang: 'zu'})
                    .run();
            });
        });

        describe("when the user has submitted their language", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('4')
                    .check.user.properties({lang: 'xh'})
                    .run();
            });
        });

        describe("when the user has submitted their language", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('5')
                    .check.user.properties({lang: 'so'})
                    .run();
            });
        });

    });
});
<commit_msg>Change description for each test to include language tested<commit_after>var vumigo = require('vumigo_v02');
var app = require('../lib/app');
var GoDiApp = app.GoDiApp;
var AppTester = vumigo.AppTester;

describe("app", function() {
    describe("GoDiApp", function() {
        var app;
        var tester;

        beforeEach(function() {
            app = new GoDiApp();

            tester = new AppTester(app);

            tester.setup.config.app({
                name: 'test_app'
            });
        });

        describe("when the user starts a session", function() {
            it("should ask them to choose a language ", function() {
                return tester
                    .start()
                    .check.interaction({
                        state: 'states:start',
                        reply: ['your language:',
                                '1. English',
                                '2. Afrikaans',
                                '3. Zulu',
                                '4. Xhosa',
                                '5. Sotho'
                        ].join('\n')
                    })
                    .run();
            });
        });

        describe("when the user has submitted their language as english", function() {
            it("should set the user's language to english",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('1')
                    .check.user.properties({lang: 'en'})
                    .run();
            });

            it("should show them the address menu in english",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('1')
                    .check.interaction({
                        state:'states:address',
                        reply:'To be completed'
                    }).run();
            });
        });

        describe("when the user has submitted their language as afrikaans", function() {
            it("should set the user's language",function() {
               return tester
                   .setup.user.state('states:start')
                   .input('2')
                   .check.user.properties({lang: 'af'})
                   .run();
            });
        });

        describe("when the user has submitted their language as zulu", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('3')
                    .check.user.properties({lang: 'zu'})
                    .run();
            });
        });

        describe("when the user has submitted their language as Xhosa", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('4')
                    .check.user.properties({lang: 'xh'})
                    .run();
            });
        });

        describe("when the user has submitted their language as Sotho", function() {
            it("should set the user's language",function() {
                return tester
                    .setup.user.state('states:start')
                    .input('5')
                    .check.user.properties({lang: 'so'})
                    .run();
            });
        });

    });
});
<|endoftext|>


<commit_before>const resolvePreset = function (presetName) {
  return require.resolve(`babel-preset-${presetName}`);
};

const resolvePlugin = function (pluginName) {
  return require.resolve(`babel-plugin-${pluginName}`);
};

module.exports = function createOpts(env, react, { presets: otherPresets, plugins: otherPlugins } = {}) {
  const production = !env.endsWith('-dev');

  if (!production) {
    env = env.slice(0, -4);
  }

  if (env === 'doc') {
    return {
      presets: [
        'jsdoc',
        react && 'react',
        resolvePreset('stage-1'),
      ].filter(Boolean),
      plugins: [
        'add-jsdoc-annotations',
        resolvePlugin('transform-flow-strip-types'),
      ]
    };
  }

  let presets;
  let browser;
  let modernBrowsers = false;

  switch (env) {
    case 'es5':
      throw new Error('use olderNode instead.');

    case 'test':
    case 'node6':
      presets = [
        'es2015-node6',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = false;
      break;

    case 'older-node':
      presets = [
        'es2015',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = false;
      break;

    case 'webpack':
      presets = [
        ['es2015', { modules: false }],
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      break;

    case 'webpack-modern-browsers':
      presets = [
        ['modern-browsers', { modules: false }],
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      modernBrowsers = true;
      break;

    case 'browsers':
      presets = [
        'es2015',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      break;

    default:
      throw new Error(`Unsupported env ${env}`);

  }

  return {
    presets: presets.concat(otherPresets || []).filter(Boolean),
    plugins: [
      resolvePlugin('syntax-flow'),
      [require('babel-plugin-import-export-rename'), { '^([a-z\\-]+|[\./]+)/src(.*)$': '$1$2' }],
      [require('babel-plugin-transform-export-default-name-forked'), { compose: true }],
      !production && resolvePlugin('tcomb-forked'),
      resolvePlugin('transform-flow-strip-types'),
      react && resolvePlugin('react-require'),
      // browser && 'react-hot-loader/babel',
      !production && react && resolvePlugin('transform-react-jsx-self'),
      !production && react && resolvePlugin('transform-react-jsx-source'),

      [resolvePlugin('minify-replace'), {
          replacements: [
              {
                  identifierName: 'PRODUCTION',
                  replacement: { type: 'booleanLiteral', value: production },
              },
              {
                  identifierName: 'BROWSER',
                  replacement: { type: 'booleanLiteral', value: browser },
              },
              {
                  identifierName: 'SERVER',
                  replacement: { type: 'booleanLiteral', value: !browser },
              },
              {
                  identifierName: 'NODEJS',
                  replacement: { type: 'booleanLiteral', value: !browser },
              },
          ],
      }],
      resolvePlugin('minify-constant-folding'),
      [require('babel-plugin-minify-dead-code-elimination'), { keepFnName: true, keepFnames: true }],
      resolvePlugin('minify-guarded-expressions'),
      resolvePlugin('discard-module-references'),
      ...(otherPlugins || []),
    ].filter(Boolean)
  };
};
<commit_msg>feat: babel option reactHotLoader<commit_after>const resolvePreset = function (presetName) {
  return require.resolve(`babel-preset-${presetName}`);
};

const resolvePlugin = function (pluginName) {
  return require.resolve(`babel-plugin-${pluginName}`);
};

module.exports = function createOpts(
  env,
  react,
  { presets: otherPresets, plugins: otherPlugins, reactHotLoader = false } = {}
) {
  const production = !env.endsWith('-dev');

  if (!production) {
    env = env.slice(0, -4);
  }

  if (env === 'doc') {
    return {
      presets: [
        'jsdoc',
        react && 'react',
        resolvePreset('stage-1'),
      ].filter(Boolean),
      plugins: [
        'add-jsdoc-annotations',
        resolvePlugin('transform-flow-strip-types'),
      ]
    };
  }

  let presets;
  let browser;
  let modernBrowsers = false;

  switch (env) {
    case 'es5':
      throw new Error('use olderNode instead.');

    case 'test':
    case 'node6':
      presets = [
        'es2015-node6',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = false;
      break;

    case 'older-node':
      presets = [
        'es2015',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = false;
      break;

    case 'webpack':
      presets = [
        ['es2015', { modules: false }],
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      break;

    case 'webpack-modern-browsers':
      presets = [
        ['modern-browsers', { modules: false }],
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      modernBrowsers = true;
      break;

    case 'browsers':
      presets = [
        'es2015',
        react && 'react',
        resolvePreset('stage-1'),
      ];
      browser = true;
      break;

    default:
      throw new Error(`Unsupported env ${env}`);

  }

  return {
    presets: presets.concat(otherPresets || []).filter(Boolean),
    plugins: [
      resolvePlugin('syntax-flow'),
      [require('babel-plugin-import-export-rename'), { '^([a-z\\-]+|[\./]+)/src(.*)$': '$1$2' }],
      !production && [require('babel-plugin-transform-export-default-name-forked'), { compose: true }],
      !production && resolvePlugin('tcomb-forked'),
      resolvePlugin('transform-flow-strip-types'),
      react && resolvePlugin('react-require'),
      !production && react && reactHotLoader && 'react-hot-loader/babel',
      !production && react && resolvePlugin('transform-react-jsx-self'),
      !production && react && resolvePlugin('transform-react-jsx-source'),

      [resolvePlugin('minify-replace'), {
          replacements: [
              {
                  identifierName: 'PRODUCTION',
                  replacement: { type: 'booleanLiteral', value: production },
              },
              {
                  identifierName: 'BROWSER',
                  replacement: { type: 'booleanLiteral', value: browser },
              },
              {
                  identifierName: 'SERVER',
                  replacement: { type: 'booleanLiteral', value: !browser },
              },
              {
                  identifierName: 'NODEJS',
                  replacement: { type: 'booleanLiteral', value: !browser },
              },
          ],
      }],
      resolvePlugin('minify-constant-folding'),
      [require('babel-plugin-minify-dead-code-elimination'), { keepFnName: true, keepFnames: true }],
      resolvePlugin('minify-guarded-expressions'),
      resolvePlugin('discard-module-references'),
      ...(otherPlugins || []),
    ].filter(Boolean)
  };
};
<|endoftext|>


<commit_before>/*global before,describe,it */
var fs = require('fs');
var PassThrough = require('readable-stream').PassThrough;
var WriteStream = fs.createWriteStream;

var assert = require('chai').assert;
var mkdir = require('mkdirp');

var helpers = require('./helpers');
var HashStream = helpers.HashStream;
var UnBufferedStream = helpers.UnBufferedStream;
var WriteHashStream = helpers.WriteHashStream;
var binaryBuffer = helpers.binaryBuffer;

var archiver = require('../');

var testBuffer = binaryBuffer(1024 * 16);

var testDate = new Date('Jan 03 2013 14:26:38 GMT');
var testDate2 = new Date('Feb 10 2013 10:24:42 GMT');

var win32 = process.platform === 'win32';

describe('archiver', function() {
  before(function() {
    mkdir.sync('tmp');

    if (!win32) {
      fs.chmodSync('test/fixtures/executable.sh', 0777);
      fs.chmodSync('test/fixtures/directory/subdir/', 0755);
    }
  });

  describe('core', function() {
    describe('#abort', function() {
      var archive;

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/abort.json');

        testStream.on('close', function() {
          done();
        });

        archive.pipe(testStream);

        archive
          .append(testBuffer, { name: 'buffer.txt', date: testDate })
          .append(fs.createReadStream('test/fixtures/test.txt'), { name: 'stream.txt', date: testDate })
          .file('test/fixtures/test.txt')
          .abort();
      });

      it('should have a state of aborted', function() {
        assert.property(archive, '_state');
        assert.propertyVal(archive._state, 'aborted', true);
      });
    });

    describe('#append', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/append.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/append.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .append(testBuffer, { name: 'buffer.txt', date: testDate })
          .append(fs.createReadStream('test/fixtures/test.txt'), { name: 'stream.txt', date: testDate })
          .append(null, { name: 'directory/', date: testDate })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);
        assert.lengthOf(actual, 3);
      });

      it('should append buffer', function() {
        assert.property(entries, 'buffer.txt');
        assert.propertyVal(entries['buffer.txt'], 'name', 'buffer.txt');
        assert.propertyVal(entries['buffer.txt'], 'type', 'file');
        assert.propertyVal(entries['buffer.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['buffer.txt'], 'mode', 420);
        assert.propertyVal(entries['buffer.txt'], 'crc32', 3893830384);
        assert.propertyVal(entries['buffer.txt'], 'size', 16384);
      });

      it('should append stream', function() {
        assert.property(entries, 'stream.txt');
        assert.propertyVal(entries['stream.txt'], 'name', 'stream.txt');
        assert.propertyVal(entries['stream.txt'], 'type', 'file');
        assert.propertyVal(entries['stream.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['stream.txt'], 'mode', 420);
        assert.propertyVal(entries['stream.txt'], 'crc32', 585446183);
        assert.propertyVal(entries['stream.txt'], 'size', 19);
      });

      it('should append directory', function() {
        assert.property(entries, 'directory/');
        assert.propertyVal(entries['directory/'], 'name', 'directory/');
        assert.propertyVal(entries['directory/'], 'type', 'directory');
        assert.propertyVal(entries['directory/'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['directory/'], 'mode', 493);
        assert.propertyVal(entries['directory/'], 'crc32', 0);
        assert.propertyVal(entries['directory/'], 'size', 0);
      });
    });

    describe('#directory', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/directory.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/directory.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .directory('test/fixtures/directory', null, { date: testDate })
          .directory('test/fixtures/directory', 'directory', function(data) {
            data.funcProp = true;
            return data;
          })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);

        assert.property(entries, 'test/fixtures/directory/level0.txt');
        assert.property(entries, 'test/fixtures/directory/subdir/');
        assert.property(entries, 'test/fixtures/directory/subdir/level1.txt');
        assert.property(entries, 'test/fixtures/directory/subdir/subsub/');
        assert.property(entries, 'test/fixtures/directory/subdir/subsub/level2.txt');
        assert.propertyVal(entries['test/fixtures/directory/level0.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['test/fixtures/directory/subdir/'], 'date', '2013-01-03T14:26:38.000Z');

        assert.property(entries, 'directory/level0.txt');
        assert.property(entries, 'directory/subdir/');
        assert.property(entries, 'directory/subdir/level1.txt');
        assert.property(entries, 'directory/subdir/subsub/');
        assert.property(entries, 'directory/subdir/subsub/level2.txt');
      });

      it('should support setting data properties via function', function() {
        assert.property(entries, 'directory/level0.txt');
        assert.propertyVal(entries['directory/level0.txt'], 'funcProp', true);
      });
    });

    describe('#file', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/file.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/file.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .file('test/fixtures/test.txt', { name: 'test.txt', date: testDate })
          .file('test/fixtures/test.txt')
          .file('test/fixtures/executable.sh', { mode: win32 ? 0777 : null })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);
        assert.lengthOf(actual, 3);
      });

      it('should append filepath', function() {
        assert.property(entries, 'test.txt');
        assert.propertyVal(entries['test.txt'], 'name', 'test.txt');
        assert.propertyVal(entries['test.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['test.txt'], 'crc32', 585446183);
        assert.propertyVal(entries['test.txt'], 'size', 19);
      });

      it('should fallback to filepath when no name is set', function() {
        assert.property(entries, 'test/fixtures/test.txt');
      });

      it('should fallback to file stats when applicable', function() {
        assert.property(entries, 'test/fixtures/executable.sh');
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'name', 'test/fixtures/executable.sh');
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'mode', 511);
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'crc32', 3957348457);
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'size', 11);
      });
    });

    describe('#bulk', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/bulk.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/bulk.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .bulk([
            { expand: true, cwd: 'test/fixtures/directory/', src: '**', data: { prop: 'value' } },
            { expand: true, cwd: 'test/fixtures/directory/', src: '**', dest: 'directory/', data: function(data) {
              data.funcProp = true;
              return data;
            }},
          ])
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);

        assert.property(entries, 'level0.txt');
        assert.property(entries, 'subdir/');
        assert.property(entries, 'subdir/level1.txt');
        assert.property(entries, 'subdir/subsub/');
        assert.property(entries, 'subdir/subsub/level2.txt');

        assert.property(entries, 'directory/level0.txt');
        assert.property(entries, 'directory/subdir/');
        assert.property(entries, 'directory/subdir/level1.txt');
        assert.property(entries, 'directory/subdir/subsub/');
        assert.property(entries, 'directory/subdir/subsub/level2.txt');
      });

      it('should support setting data properties', function() {
        assert.property(entries, 'level0.txt');
        assert.propertyVal(entries['level0.txt'], 'prop', 'value');
      });

      it('should support setting data properties via function', function() {
        assert.property(entries, 'directory/level0.txt');
        assert.propertyVal(entries['directory/level0.txt'], 'funcProp', true);
      });

      it('should retain directory permissions', function() {
        assert.property(entries, 'subdir/');
        assert.propertyVal(entries['subdir/'], 'mode', 493);
      });
    });

    describe('#glob', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/glob.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/glob.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .glob('test/fixtures/test.txt', null, { stats: null })
          .glob('test/fixtures/empty.txt', null, { stats: null })
          .glob('test/fixtures/executable.sh', null, { stats: null })
          .glob('test/fixtures/directory/**/*', { ignore: 'test/fixtures/directory/subdir/**/*', nodir: true }, { stats: null })
          .glob('**/*', { cwd: 'test/fixtures/directory/subdir/' }, { stats: null })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);
        console.log(entries);

        assert.property(entries, 'test/fixtures/test.txt');
        assert.property(entries, 'test/fixtures/executable.sh');
        assert.property(entries, 'test/fixtures/empty.txt');

        assert.property(entries, 'test/fixtures/directory/level0.txt');

        assert.property(entries, 'level1.txt');
        assert.property(entries, 'subsub/level2.txt');
      });
    });

  });
});<commit_msg>tests(glob): quick refactor.<commit_after>/*global before,describe,it */
var fs = require('fs');
var PassThrough = require('readable-stream').PassThrough;
var WriteStream = fs.createWriteStream;

var assert = require('chai').assert;
var mkdir = require('mkdirp');

var helpers = require('./helpers');
var HashStream = helpers.HashStream;
var UnBufferedStream = helpers.UnBufferedStream;
var WriteHashStream = helpers.WriteHashStream;
var binaryBuffer = helpers.binaryBuffer;

var archiver = require('../');

var testBuffer = binaryBuffer(1024 * 16);

var testDate = new Date('Jan 03 2013 14:26:38 GMT');
var testDate2 = new Date('Feb 10 2013 10:24:42 GMT');

var win32 = process.platform === 'win32';

describe('archiver', function() {
  before(function() {
    mkdir.sync('tmp');

    if (!win32) {
      fs.chmodSync('test/fixtures/executable.sh', 0777);
      fs.chmodSync('test/fixtures/directory/subdir/', 0755);
    }
  });

  describe('core', function() {
    describe('#abort', function() {
      var archive;

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/abort.json');

        testStream.on('close', function() {
          done();
        });

        archive.pipe(testStream);

        archive
          .append(testBuffer, { name: 'buffer.txt', date: testDate })
          .append(fs.createReadStream('test/fixtures/test.txt'), { name: 'stream.txt', date: testDate })
          .file('test/fixtures/test.txt')
          .abort();
      });

      it('should have a state of aborted', function() {
        assert.property(archive, '_state');
        assert.propertyVal(archive._state, 'aborted', true);
      });
    });

    describe('#append', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/append.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/append.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .append(testBuffer, { name: 'buffer.txt', date: testDate })
          .append(fs.createReadStream('test/fixtures/test.txt'), { name: 'stream.txt', date: testDate })
          .append(null, { name: 'directory/', date: testDate })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);
        assert.lengthOf(actual, 3);
      });

      it('should append buffer', function() {
        assert.property(entries, 'buffer.txt');
        assert.propertyVal(entries['buffer.txt'], 'name', 'buffer.txt');
        assert.propertyVal(entries['buffer.txt'], 'type', 'file');
        assert.propertyVal(entries['buffer.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['buffer.txt'], 'mode', 420);
        assert.propertyVal(entries['buffer.txt'], 'crc32', 3893830384);
        assert.propertyVal(entries['buffer.txt'], 'size', 16384);
      });

      it('should append stream', function() {
        assert.property(entries, 'stream.txt');
        assert.propertyVal(entries['stream.txt'], 'name', 'stream.txt');
        assert.propertyVal(entries['stream.txt'], 'type', 'file');
        assert.propertyVal(entries['stream.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['stream.txt'], 'mode', 420);
        assert.propertyVal(entries['stream.txt'], 'crc32', 585446183);
        assert.propertyVal(entries['stream.txt'], 'size', 19);
      });

      it('should append directory', function() {
        assert.property(entries, 'directory/');
        assert.propertyVal(entries['directory/'], 'name', 'directory/');
        assert.propertyVal(entries['directory/'], 'type', 'directory');
        assert.propertyVal(entries['directory/'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['directory/'], 'mode', 493);
        assert.propertyVal(entries['directory/'], 'crc32', 0);
        assert.propertyVal(entries['directory/'], 'size', 0);
      });
    });

    describe('#directory', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/directory.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/directory.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .directory('test/fixtures/directory', null, { date: testDate })
          .directory('test/fixtures/directory', 'directory', function(data) {
            data.funcProp = true;
            return data;
          })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);

        assert.property(entries, 'test/fixtures/directory/level0.txt');
        assert.property(entries, 'test/fixtures/directory/subdir/');
        assert.property(entries, 'test/fixtures/directory/subdir/level1.txt');
        assert.property(entries, 'test/fixtures/directory/subdir/subsub/');
        assert.property(entries, 'test/fixtures/directory/subdir/subsub/level2.txt');
        assert.propertyVal(entries['test/fixtures/directory/level0.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['test/fixtures/directory/subdir/'], 'date', '2013-01-03T14:26:38.000Z');

        assert.property(entries, 'directory/level0.txt');
        assert.property(entries, 'directory/subdir/');
        assert.property(entries, 'directory/subdir/level1.txt');
        assert.property(entries, 'directory/subdir/subsub/');
        assert.property(entries, 'directory/subdir/subsub/level2.txt');
      });

      it('should support setting data properties via function', function() {
        assert.property(entries, 'directory/level0.txt');
        assert.propertyVal(entries['directory/level0.txt'], 'funcProp', true);
      });
    });

    describe('#file', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/file.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/file.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .file('test/fixtures/test.txt', { name: 'test.txt', date: testDate })
          .file('test/fixtures/test.txt')
          .file('test/fixtures/executable.sh', { mode: win32 ? 0777 : null })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);
        assert.lengthOf(actual, 3);
      });

      it('should append filepath', function() {
        assert.property(entries, 'test.txt');
        assert.propertyVal(entries['test.txt'], 'name', 'test.txt');
        assert.propertyVal(entries['test.txt'], 'date', '2013-01-03T14:26:38.000Z');
        assert.propertyVal(entries['test.txt'], 'crc32', 585446183);
        assert.propertyVal(entries['test.txt'], 'size', 19);
      });

      it('should fallback to filepath when no name is set', function() {
        assert.property(entries, 'test/fixtures/test.txt');
      });

      it('should fallback to file stats when applicable', function() {
        assert.property(entries, 'test/fixtures/executable.sh');
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'name', 'test/fixtures/executable.sh');
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'mode', 511);
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'crc32', 3957348457);
        assert.propertyVal(entries['test/fixtures/executable.sh'], 'size', 11);
      });
    });

    describe('#bulk', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/bulk.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/bulk.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .bulk([
            { expand: true, cwd: 'test/fixtures/directory/', src: '**', data: { prop: 'value' } },
            { expand: true, cwd: 'test/fixtures/directory/', src: '**', dest: 'directory/', data: function(data) {
              data.funcProp = true;
              return data;
            }},
          ])
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);

        assert.property(entries, 'level0.txt');
        assert.property(entries, 'subdir/');
        assert.property(entries, 'subdir/level1.txt');
        assert.property(entries, 'subdir/subsub/');
        assert.property(entries, 'subdir/subsub/level2.txt');

        assert.property(entries, 'directory/level0.txt');
        assert.property(entries, 'directory/subdir/');
        assert.property(entries, 'directory/subdir/level1.txt');
        assert.property(entries, 'directory/subdir/subsub/');
        assert.property(entries, 'directory/subdir/subsub/level2.txt');
      });

      it('should support setting data properties', function() {
        assert.property(entries, 'level0.txt');
        assert.propertyVal(entries['level0.txt'], 'prop', 'value');
      });

      it('should support setting data properties via function', function() {
        assert.property(entries, 'directory/level0.txt');
        assert.propertyVal(entries['directory/level0.txt'], 'funcProp', true);
      });

      it('should retain directory permissions', function() {
        assert.property(entries, 'subdir/');
        assert.propertyVal(entries['subdir/'], 'mode', 493);
      });
    });

    describe('#glob', function() {
      var actual;
      var archive;
      var entries = {};

      before(function(done) {
        archive = archiver('json');
        var testStream = new WriteStream('tmp/glob.json');

        testStream.on('close', function() {
          actual = helpers.readJSON('tmp/glob.json');

          actual.forEach(function(entry) {
            entries[entry.name] = entry;
          });

          done();
        });

        archive.pipe(testStream);

        archive
          .glob('test/fixtures/test.txt', null, { stats: null })
          .glob('test/fixtures/empty.txt', null, { stats: null })
          .glob('test/fixtures/executable.sh', null, { stats: null })
          .glob('test/fixtures/directory/**/*', { ignore: 'test/fixtures/directory/subdir/**/*', nodir: true }, { stats: null })
          .glob('**/*', { cwd: 'test/fixtures/directory/subdir/' }, { stats: null })
          .finalize();
      });

      it('should append multiple entries', function() {
        assert.isArray(actual);

        assert.property(entries, 'test/fixtures/test.txt');
        assert.property(entries, 'test/fixtures/executable.sh');
        assert.property(entries, 'test/fixtures/empty.txt');

        assert.property(entries, 'test/fixtures/directory/level0.txt');

        assert.property(entries, 'level1.txt');
        assert.property(entries, 'subsub/level2.txt');
      });
    });

  });
});<|endoftext|>


<commit_before>'use strict';

var utils = require('../common/utils'),
    url = require('url'),
    fs = require('fs'),
    path = require('path'),
    request = require('request'),
    archiver = require('archiver'),
    cloudappx = require('cloudappx-server'),
    log = require('loglevel'),
    os = require('os');
var version = require('../common/version');
var metadataItemTemplate=  '\r\n\t\t<build:Item Name ="{0}" Version ="{1}" />';

var serviceEndpoint = 'http://cloudappx.azurewebsites.net';

function replaceManifestValues(w3cManifestInfo, content) {
  var w3cManifest = w3cManifestInfo.content;
  var timestamp = w3cManifestInfo.timestamp || new Date().toISOString().replace(/T/, ' ').replace(/\.[0-9]+/, ' ');
  var replacedContent = content;
  var guid = utils.newGuid();

  var applicationId = utils.sanitizeName(w3cManifest.short_name);

  // Update general properties
  replacedContent = replacedContent.replace(/{IdentityName}/g, guid)
                                    .replace(/{PhoneProductId}/g, guid)
                                    .replace(/{DisplayName}/g, w3cManifest.short_name)
                                    .replace(/{ApplicationId}/g, applicationId)
                                    .replace(/{StartPage}/g, w3cManifest.start_url)
                                    .replace(/{DisplayName}/g, w3cManifest.short_name)
                                    .replace(/{Description}/g, w3cManifest.name || w3cManifest.short_name)
                                    .replace(/{RotationPreference}/g, w3cManifest.orientation || 'portrait')
                                    .replace(/{ManifoldJSVersion}/g, version.getCurrentPackageVersion())
                                    .replace(/{GeneratedFrom}/g, w3cManifestInfo.generatedFrom || 'API')
                                    .replace(/{GenerationDate}/g, timestamp);

  // Add additional metadata items
  var metadataItems = '';
  if (w3cManifestInfo.generatedUrl) {
    metadataItems += metadataItemTemplate.replace(/\{0}/g, 'GeneratedURL')
                                         .replace(/\{1}/g, w3cManifestInfo.generatedUrl);
  }

  replacedContent = replacedContent.replace(/{MetadataItems}/g, metadataItems);

  // Update access rules
  var indentationChars = '\r\n\t\t\t\t';

  // Set the base access rule using the start_url's base url
  var baseUrlPattern = url.resolve(w3cManifest.start_url, '/');
  var baseApiAccess = 'none';
  if (w3cManifest.scope && w3cManifest.scope.length) {
    // If the scope is defined, the base access rule is defined by the scope
    var parsedScopeUrl = url.parse(w3cManifest.scope);

    if (parsedScopeUrl.host && parsedScopeUrl.protocol) {
      baseUrlPattern = w3cManifest.scope;
    } else {
      baseUrlPattern = url.resolve(baseUrlPattern, w3cManifest.scope);
    }
  }

  // If the base access rule ends with '/*', remove the '*'.
  if (baseUrlPattern.indexOf('/*', baseUrlPattern.length - 2) !== -1) {
    baseUrlPattern = baseUrlPattern.substring(0, baseUrlPattern.length - 1);
  }

  var applicationContentUriRules = '';

  // Add additional access rules
  if (w3cManifest.mjs_access_whitelist && w3cManifest.mjs_access_whitelist instanceof Array) {
    for (var j = 0; j < w3cManifest.mjs_access_whitelist.length; j++) {
      var accessUrl = w3cManifest.mjs_access_whitelist[j].url;
      // Ignore the '*' rule
      if (accessUrl !== '*') {
        // If the access url ends with '/*', remove the '*'.
        if (accessUrl.indexOf('/*', accessUrl.length - 2) !== -1) {
          accessUrl = accessUrl.substring(0, accessUrl.length - 1);
        }

        var apiAccess = w3cManifest.mjs_access_whitelist[j].apiAccess || 'none';

        if (accessUrl === baseUrlPattern) {
          baseApiAccess = apiAccess;
        } else  {
          applicationContentUriRules += indentationChars + '<uap:Rule Type="include" WindowsRuntimeAccess="' + apiAccess + '" Match="' + accessUrl + '" />';
        }
      }
    }
  }

  // Added base rule
  applicationContentUriRules = '<uap:Rule Type="include" WindowsRuntimeAccess="' + baseApiAccess + '" Match="' + baseUrlPattern + '" />' + applicationContentUriRules;

  replacedContent = replacedContent.replace(/{ApplicationContentUriRules}/g, applicationContentUriRules);

  return replacedContent;
}

function invokeCloudAppX(appName, appFolder, outputPath, callback) {
  var archive = archiver('zip');
  var zipFile = path.join(os.tmpdir(), appName + '.zip');
  var output = fs.createWriteStream(zipFile);
  archive.on('error', function (err) {
    return callback && callback(err);
  });

  archive.pipe(output);

  archive.directory(appFolder, appName);
  archive.finalize();
  output.on('close', function () {
    var options = {
      method: 'POST',
      url: url.resolve(serviceEndpoint, '/v2/build'),
      encoding: 'binary'
    };
    log.debug('Invoking the CloudAppX service...');

    var req = request.post(options, function (err, resp, body) {
      if (err) {
        return callback && callback(err);
      }

      if (resp.statusCode !== 200) {
        return callback && callback(new Error('Failed to create the package. The CloudAppX service returned an error - ' + resp.statusMessage + ' (' + resp.statusCode + '): ' + body));
      }

      fs.writeFile(outputPath, body, { 'encoding': 'binary' }, function (err) {
        if (err) {
          return callback && callback(err);
        }

        fs.unlink(zipFile, function (err) {
          return callback && callback(err);
        });
      });
    });

    req.form().append('xml', fs.createReadStream(zipFile));
  });
}

// Quick sanity check to ensure that the placeholder parameters in the manifest
// have been replaced by the user with their publisher details before generating
// a package.
function validateManifestPublisherDetails(appFolder, callback) {
  var manifestPath = path.join(appFolder, 'appxmanifest.xml');
  fs.readFile(manifestPath, 'utf8', function (err, data) {
    if (!err) {
      var packageIdentityPlaceholders = /<Identity.*(Name\s*=\s*"INSERT-YOUR-PACKAGE-IDENTITY-NAME-HERE"|Publisher\s*=\s*"CN=INSERT-YOUR-PACKAGE-IDENTITY-PUBLISHER-HERE")/g;
      var publisherDisplayNamePlaceholder = /<PublisherDisplayName>\s*INSERT-YOUR-PACKAGE-PROPERTIES-PUBLISHERDISPLAYNAME-HERE\s*<\/PublisherDisplayName>/g;
      if (packageIdentityPlaceholders.test(data) || publisherDisplayNamePlaceholder.test(data)) {
        err = new Error('You must register the app in the Windows Store and obtain the Package/Identity/Name, Package/Identity/Publisher, and Package/Properties/PublisherDisplayName details. Update the placeholders in the appmanifest.xml file with this information before creating the App Store package.');
      }
    }

    return callback && callback(err);
  });
}

var makeAppx = function (appFolder, outputPath, callback) {
  var outputData = path.parse(outputPath);
  var options = { 'dir': appFolder, 'name': outputData.name, 'out': outputData.dir };
  validateManifestPublisherDetails(appFolder, function (err, result) {
    if (err) {
      return callback && callback(err);
    }

    cloudappx.makeAppx(options).then(
      function () {
        return callback && callback();
      },
      function () {
        log.debug('Unable to create the package locally. Invoking the CloudAppX service instead...');
        invokeCloudAppX(outputData.name, appFolder, outputPath, function (err) {
          return callback && callback(err);
        });
      });
  });
};

module.exports = {
  replaceManifestValues: replaceManifestValues,
  makeAppx: makeAppx
};
<commit_msg>Fixed jshint warnings<commit_after>'use strict';

var utils = require('../common/utils'),
    url = require('url'),
    fs = require('fs'),
    path = require('path'),
    request = require('request'),
    archiver = require('archiver'),
    cloudappx = require('cloudappx-server'),
    log = require('loglevel'),
    os = require('os');
var version = require('../common/version');
var metadataItemTemplate=  '\r\n\t\t<build:Item Name ="{0}" Version ="{1}" />';

var serviceEndpoint = 'http://cloudappx.azurewebsites.net';

function replaceManifestValues(w3cManifestInfo, content) {
  var w3cManifest = w3cManifestInfo.content;
  var timestamp = w3cManifestInfo.timestamp || new Date().toISOString().replace(/T/, ' ').replace(/\.[0-9]+/, ' ');
  var replacedContent = content;
  var guid = utils.newGuid();

  var applicationId = utils.sanitizeName(w3cManifest.short_name);

  // Update general properties
  replacedContent = replacedContent.replace(/{IdentityName}/g, guid)
                                    .replace(/{PhoneProductId}/g, guid)
                                    .replace(/{DisplayName}/g, w3cManifest.short_name)
                                    .replace(/{ApplicationId}/g, applicationId)
                                    .replace(/{StartPage}/g, w3cManifest.start_url)
                                    .replace(/{DisplayName}/g, w3cManifest.short_name)
                                    .replace(/{Description}/g, w3cManifest.name || w3cManifest.short_name)
                                    .replace(/{RotationPreference}/g, w3cManifest.orientation || 'portrait')
                                    .replace(/{ManifoldJSVersion}/g, version.getCurrentPackageVersion())
                                    .replace(/{GeneratedFrom}/g, w3cManifestInfo.generatedFrom || 'API')
                                    .replace(/{GenerationDate}/g, timestamp);

  // Add additional metadata items
  var metadataItems = '';
  if (w3cManifestInfo.generatedUrl) {
    metadataItems += metadataItemTemplate.replace(/\{0}/g, 'GeneratedURL')
                                         .replace(/\{1}/g, w3cManifestInfo.generatedUrl);
  }

  replacedContent = replacedContent.replace(/{MetadataItems}/g, metadataItems);

  // Update access rules
  var indentationChars = '\r\n\t\t\t\t';

  // Set the base access rule using the start_url's base url
  var baseUrlPattern = url.resolve(w3cManifest.start_url, '/');
  var baseApiAccess = 'none';
  if (w3cManifest.scope && w3cManifest.scope.length) {
    // If the scope is defined, the base access rule is defined by the scope
    var parsedScopeUrl = url.parse(w3cManifest.scope);

    if (parsedScopeUrl.host && parsedScopeUrl.protocol) {
      baseUrlPattern = w3cManifest.scope;
    } else {
      baseUrlPattern = url.resolve(baseUrlPattern, w3cManifest.scope);
    }
  }

  // If the base access rule ends with '/*', remove the '*'.
  if (baseUrlPattern.indexOf('/*', baseUrlPattern.length - 2) !== -1) {
    baseUrlPattern = baseUrlPattern.substring(0, baseUrlPattern.length - 1);
  }

  var applicationContentUriRules = '';

  // Add additional access rules
  if (w3cManifest.mjs_access_whitelist && w3cManifest.mjs_access_whitelist instanceof Array) {
    for (var j = 0; j < w3cManifest.mjs_access_whitelist.length; j++) {
      var accessUrl = w3cManifest.mjs_access_whitelist[j].url;
      // Ignore the '*' rule
      if (accessUrl !== '*') {
        // If the access url ends with '/*', remove the '*'.
        if (accessUrl.indexOf('/*', accessUrl.length - 2) !== -1) {
          accessUrl = accessUrl.substring(0, accessUrl.length - 1);
        }

        var apiAccess = w3cManifest.mjs_access_whitelist[j].apiAccess || 'none';

        if (accessUrl === baseUrlPattern) {
          baseApiAccess = apiAccess;
        } else  {
          applicationContentUriRules += indentationChars + '<uap:Rule Type="include" WindowsRuntimeAccess="' + apiAccess + '" Match="' + accessUrl + '" />';
        }
      }
    }
  }

  // Added base rule
  applicationContentUriRules = '<uap:Rule Type="include" WindowsRuntimeAccess="' + baseApiAccess + '" Match="' + baseUrlPattern + '" />' + applicationContentUriRules;

  replacedContent = replacedContent.replace(/{ApplicationContentUriRules}/g, applicationContentUriRules);

  return replacedContent;
}

function invokeCloudAppX(appName, appFolder, outputPath, callback) {
  var archive = archiver('zip');
  var zipFile = path.join(os.tmpdir(), appName + '.zip');
  var output = fs.createWriteStream(zipFile);
  archive.on('error', function (err) {
    return callback && callback(err);
  });

  archive.pipe(output);

  archive.directory(appFolder, appName);
  archive.finalize();
  output.on('close', function () {
    var options = {
      method: 'POST',
      url: url.resolve(serviceEndpoint, '/v2/build'),
      encoding: 'binary'
    };
    log.debug('Invoking the CloudAppX service...');

    var req = request.post(options, function (err, resp, body) {
      if (err) {
        return callback && callback(err);
      }

      if (resp.statusCode !== 200) {
        return callback && callback(new Error('Failed to create the package. The CloudAppX service returned an error - ' + resp.statusMessage + ' (' + resp.statusCode + '): ' + body));
      }

      fs.writeFile(outputPath, body, { 'encoding': 'binary' }, function (err) {
        if (err) {
          return callback && callback(err);
        }

        fs.unlink(zipFile, function (err) {
          return callback && callback(err);
        });
      });
    });

    req.form().append('xml', fs.createReadStream(zipFile));
  });
}

// Quick sanity check to ensure that the placeholder parameters in the manifest
// have been replaced by the user with their publisher details before generating
// a package.
function validateManifestPublisherDetails(appFolder, callback) {
  var manifestPath = path.join(appFolder, 'appxmanifest.xml');
  fs.readFile(manifestPath, 'utf8', function (err, data) {
    if (!err) {
      var packageIdentityPlaceholders = /<Identity.*(Name\s*=\s*"INSERT-YOUR-PACKAGE-IDENTITY-NAME-HERE"|Publisher\s*=\s*"CN=INSERT-YOUR-PACKAGE-IDENTITY-PUBLISHER-HERE")/g;
      var publisherDisplayNamePlaceholder = /<PublisherDisplayName>\s*INSERT-YOUR-PACKAGE-PROPERTIES-PUBLISHERDISPLAYNAME-HERE\s*<\/PublisherDisplayName>/g;
      if (packageIdentityPlaceholders.test(data) || publisherDisplayNamePlaceholder.test(data)) {
        err = new Error('You must register the app in the Windows Store and obtain the Package/Identity/Name, Package/Identity/Publisher, and Package/Properties/PublisherDisplayName details. Update the placeholders in the appmanifest.xml file with this information before creating the App Store package.');
      }
    }

    return callback && callback(err);
  });
}

var makeAppx = function (appFolder, outputPath, callback) {
  var outputData = path.parse(outputPath);
  var options = { 'dir': appFolder, 'name': outputData.name, 'out': outputData.dir };
  validateManifestPublisherDetails(appFolder, function (err) {
    if (err) {
      return callback && callback(err);
    }

    cloudappx.makeAppx(options).then(
      function () {
        return callback && callback();
      },
      function () {
        log.debug('Unable to create the package locally. Invoking the CloudAppX service instead...');
        invokeCloudAppX(outputData.name, appFolder, outputPath, function (err) {
          return callback && callback(err);
        });
      });
  });
};

module.exports = {
  replaceManifestValues: replaceManifestValues,
  makeAppx: makeAppx
};
<|endoftext|>