<commit_before>import Proto from 'uberproto';
import filter from 'feathers-query-filters';
import { types as errors }
from 'feathers-errors';
import parseQuery from './parse';

// Create the service.
class Service {
  constructor(options) {
    if (!options) {
      throw new Error('RethinkDB options have to be provided.');
    }

    if (options.Model) {
      options.r = options.Model;
    } else {
      throw new Error('You must provide the RethinkDB object on options.Model');
    }

    // Make sure the user connected a database before creating the service.
    if (!options.r._poolMaster._options.db) {
      throw new Error('You must provide either an instance of r that is preconfigured with a db, or a provide options.db.');
    }

    if (!options.name) {
      throw new Error('You must provide a table name on options.name');
    }

    this.type = 'rethinkdb';
    this.id = options.id || 'id';
    this.table = options.r.table(options.name);
    this.options = options;
    this.paginate = options.paginate || {};
    this.events = ['created', 'updated', 'patched', 'removed'];
  }

  extend(obj) {
    return Proto.extend(obj, this);
  }

  _find(params = {}) {
    let r = this.options.r;

    params.query = params.query || {};

    // Start with finding all, and limit when necessary.
    let query = this.table.filter({});
    // Prepare the special query params.
    let filters = filter(params.query, this.paginate);

    // Handle $select
    if (filters.$select) {
      query = query.pluck(filters.$select);
    }

    // Handle $sort
    if (filters.$sort) {
      let fieldName = Object.keys(filters.$sort)[0];
      if (filters.$sort[fieldName] === 1) {
        query = query.orderBy(fieldName);
      } else {
        query = query.orderBy(r.desc(fieldName));
      }
    }

    // Handle $or
    // TODO (@marshallswain): Handle $or queries with nested specials.
    // Right now they won't work and we'd need to start diving
    // into nested where conditions.
    if (params.query.$or) {
      // orQuery will be built and passed to row('rowName').filter().
      let orQuery;
      // params.query.$or looks like [ { name: 'Alice' }, { name: 'Bob' } ]
      // Needs to become:
      // r.row("name").eq('Alice').or(r.row("name").eq('Bob'))
      params.query.$or.forEach((queryObject, i) => {
        // queryObject looks like { name: 'Alice' }
        let keys = Object.keys(queryObject);

        keys.forEach(qField => {
          // The queryObject's value: 'Alice'
          let qValue = queryObject[qField];

          // Build the subQuery based on the qField.
          let subQuery;
          // If the qValue is an object, it will have special params in it.
          if (typeof qValue !== 'object') {
            subQuery = r.row(qField).eq(qValue);
          }

          // At the end of the current set of attributes, determine placement.
          if (i === 0) {
            orQuery = subQuery;
          } else {
            orQuery = orQuery.or(subQuery);
          }
        });
      });

      query = query.filter(orQuery);
      delete params.query.$or;
    }
    query = parseQuery(this, query, params.query);

    let countQuery;

    // For pagination, count has to run as a separate query, but without limit.
    if (this.paginate.default) {
      countQuery = query.count().run();
    }

    // Handle $skip AFTER the count query but BEFORE $limit.
    if (filters.$skip) {
      query = query.skip(filters.$skip);
    }
    // Handle $limit AFTER the count query and $skip.
    if (filters.$limit) {
      query = query.limit(filters.$limit);
    }

    // Execute the query
    return Promise.all([query, countQuery]).then(([data, total]) => {
      if (this.paginate.default) {
        return {
          total,
          data,
          limit: filters.$limit,
          skip: filters.$skip || 0
        };
      }

      return data;
    });
  }

  find(...args) {
    return this._find(...args);
  }

  _get(id, params) {
    let query;
    // If an id was passed, just get the record.
    if (id !== null && id !== undefined) {
      query = this.table.get(id);

      // If no id was passed, use params.query
    } else {
      params = params || {
        query: {}
      };
      query = this.table.filter(params.query).limit(1);
    }

    return query.run().then(data => {
      if (Array.isArray(data)) {
        data = data[0];
      }
      if (!data) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }
      return data;
    });
  }

  get(...args) {
    return this._get(...args);
  }

  // STILL NEED TO ADD params argument here.
  create(data) {
    return this.table.insert(data).run()
      .then(res => Object.assign({
        id: res.generated_keys[0]
      }, data));
  }

  patch(id, data, params) {
    let query;

    if (id !== null && id !== undefined) {
      query = this._get(id);
    } else if (params) {
      query = this._find(params);
    } else {
      return Promise.reject(new Error('Patch requires an ID or params'));
    }

    // Find the original record(s), first, then patch them.
    return query.then(getData => {
      let query;
      if (Array.isArray(getData)) {
        query = this.table.getAll(...getData.map(item => item.id));
      } else {
        query = this.table.get(id);
      }
      return query.update(data, {
        returnChanges: true
      }).run().then(response => {
        let changes = response.changes.map(change => change.new_val);
        return changes.length === 1 ? changes[0] : changes;
      });
    });
  }

  update(id, data) {
    return this._get(id).then(getData => {
      data.id = id;
      return this.table.get(getData.id)
        .replace(data, {
          returnChanges: true
        }).run()
        .then(result => (result.changes && result.changes.length) ? result.changes[0].new_val : {});
    });
  }

  remove(id, params) {
    let query;

    // You have to pass id=null to remove all records.
    if (id !== null && id !== undefined) {
      query = this.table.get(id);
    } else if (id === null) {
      const queryParams = Object.assign({}, params && params.query);
      query = this.table.filter(queryParams);
    } else {
      return Promise.reject(new Error('You must pass either an id or params to remove.'));
    }

    return query.delete({
      returnChanges: true
    }).run().then(res => {
      if (res.changes && res.changes.length) {
        let changes = res.changes.map(change => change.old_val);
        return changes.length === 1 ? changes[0] : changes;
      } else {
        return [];
      }
    });
  }

  setup() {
    this._cursor = this.table.changes().run().then(cursor => {
      cursor.each((error, data) => {
        if (error || typeof this.emit !== 'function') {
          return;
        }
        if (data.old_val === null) {
          this.emit('created', data.new_val);
        } else if (data.new_val === null) {
          this.emit('removed', data.old_val);
        } else {
          this.emit('updated', data.new_val);
          this.emit('patched', data.new_val);
        }
      });

      return cursor;
    });
  }
}

export default function init(options) {
  return new Service(options);
}

init.Service = Service;
<commit_msg>Return data when there are no result changes<commit_after>import Proto from 'uberproto';
import filter from 'feathers-query-filters';
import { types as errors }
from 'feathers-errors';
import parseQuery from './parse';

// Create the service.
class Service {
  constructor(options) {
    if (!options) {
      throw new Error('RethinkDB options have to be provided.');
    }

    if (options.Model) {
      options.r = options.Model;
    } else {
      throw new Error('You must provide the RethinkDB object on options.Model');
    }

    // Make sure the user connected a database before creating the service.
    if (!options.r._poolMaster._options.db) {
      throw new Error('You must provide either an instance of r that is preconfigured with a db, or a provide options.db.');
    }

    if (!options.name) {
      throw new Error('You must provide a table name on options.name');
    }

    this.type = 'rethinkdb';
    this.id = options.id || 'id';
    this.table = options.r.table(options.name);
    this.options = options;
    this.paginate = options.paginate || {};
    this.events = ['created', 'updated', 'patched', 'removed'];
  }

  extend(obj) {
    return Proto.extend(obj, this);
  }

  _find(params = {}) {
    let r = this.options.r;

    params.query = params.query || {};

    // Start with finding all, and limit when necessary.
    let query = this.table.filter({});
    // Prepare the special query params.
    let filters = filter(params.query, this.paginate);

    // Handle $select
    if (filters.$select) {
      query = query.pluck(filters.$select);
    }

    // Handle $sort
    if (filters.$sort) {
      let fieldName = Object.keys(filters.$sort)[0];
      if (filters.$sort[fieldName] === 1) {
        query = query.orderBy(fieldName);
      } else {
        query = query.orderBy(r.desc(fieldName));
      }
    }

    // Handle $or
    // TODO (@marshallswain): Handle $or queries with nested specials.
    // Right now they won't work and we'd need to start diving
    // into nested where conditions.
    if (params.query.$or) {
      // orQuery will be built and passed to row('rowName').filter().
      let orQuery;
      // params.query.$or looks like [ { name: 'Alice' }, { name: 'Bob' } ]
      // Needs to become:
      // r.row("name").eq('Alice').or(r.row("name").eq('Bob'))
      params.query.$or.forEach((queryObject, i) => {
        // queryObject looks like { name: 'Alice' }
        let keys = Object.keys(queryObject);

        keys.forEach(qField => {
          // The queryObject's value: 'Alice'
          let qValue = queryObject[qField];

          // Build the subQuery based on the qField.
          let subQuery;
          // If the qValue is an object, it will have special params in it.
          if (typeof qValue !== 'object') {
            subQuery = r.row(qField).eq(qValue);
          }

          // At the end of the current set of attributes, determine placement.
          if (i === 0) {
            orQuery = subQuery;
          } else {
            orQuery = orQuery.or(subQuery);
          }
        });
      });

      query = query.filter(orQuery);
      delete params.query.$or;
    }
    query = parseQuery(this, query, params.query);

    let countQuery;

    // For pagination, count has to run as a separate query, but without limit.
    if (this.paginate.default) {
      countQuery = query.count().run();
    }

    // Handle $skip AFTER the count query but BEFORE $limit.
    if (filters.$skip) {
      query = query.skip(filters.$skip);
    }
    // Handle $limit AFTER the count query and $skip.
    if (filters.$limit) {
      query = query.limit(filters.$limit);
    }

    // Execute the query
    return Promise.all([query, countQuery]).then(([data, total]) => {
      if (this.paginate.default) {
        return {
          total,
          data,
          limit: filters.$limit,
          skip: filters.$skip || 0
        };
      }

      return data;
    });
  }

  find(...args) {
    return this._find(...args);
  }

  _get(id, params) {
    let query;
    // If an id was passed, just get the record.
    if (id !== null && id !== undefined) {
      query = this.table.get(id);

      // If no id was passed, use params.query
    } else {
      params = params || {
        query: {}
      };
      query = this.table.filter(params.query).limit(1);
    }

    return query.run().then(data => {
      if (Array.isArray(data)) {
        data = data[0];
      }
      if (!data) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }
      return data;
    });
  }

  get(...args) {
    return this._get(...args);
  }

  // STILL NEED TO ADD params argument here.
  create(data) {
    return this.table.insert(data).run()
      .then(res => Object.assign({
        id: res.generated_keys[0]
      }, data));
  }

  patch(id, data, params) {
    let query;

    if (id !== null && id !== undefined) {
      query = this._get(id);
    } else if (params) {
      query = this._find(params);
    } else {
      return Promise.reject(new Error('Patch requires an ID or params'));
    }

    // Find the original record(s), first, then patch them.
    return query.then(getData => {
      let query;
      if (Array.isArray(getData)) {
        query = this.table.getAll(...getData.map(item => item.id));
      } else {
        query = this.table.get(id);
      }
      return query.update(data, {
        returnChanges: true
      }).run().then(response => {
        let changes = response.changes.map(change => change.new_val);
        return changes.length === 1 ? changes[0] : changes;
      });
    });
  }

  update(id, data) {
    return this._get(id).then(getData => {
      data.id = id;
      return this.table.get(getData.id)
        .replace(data, {
          returnChanges: true
        }).run()
        .then(result =>
          (result.changes && result.changes.length) ? result.changes[0].new_val : data
        );
    });
  }

  remove(id, params) {
    let query;

    // You have to pass id=null to remove all records.
    if (id !== null && id !== undefined) {
      query = this.table.get(id);
    } else if (id === null) {
      const queryParams = Object.assign({}, params && params.query);
      query = this.table.filter(queryParams);
    } else {
      return Promise.reject(new Error('You must pass either an id or params to remove.'));
    }

    return query.delete({
      returnChanges: true
    }).run().then(res => {
      if (res.changes && res.changes.length) {
        let changes = res.changes.map(change => change.old_val);
        return changes.length === 1 ? changes[0] : changes;
      } else {
        return [];
      }
    });
  }

  setup() {
    this._cursor = this.table.changes().run().then(cursor => {
      cursor.each((error, data) => {
        if (error || typeof this.emit !== 'function') {
          return;
        }
        if (data.old_val === null) {
          this.emit('created', data.new_val);
        } else if (data.new_val === null) {
          this.emit('removed', data.old_val);
        } else {
          this.emit('updated', data.new_val);
          this.emit('patched', data.new_val);
        }
      });

      return cursor;
    });
  }
}

export default function init(options) {
  return new Service(options);
}

init.Service = Service;
<|endoftext|>


<commit_before>export * from './core/bad-argument-exception';
export * from './core/chart-registry';
export * from './core/config';
export * from './core/constants';
export * from './core/core';
export * from './core/events';
export * from './core/filters';
export * from './core/invalid-state-exception';
export * from './core/logger';
export * from './core/printers';
export * from './core/units';
export * from './core/utils';

export * from './base/base-mixin';
export * from './base/bubble-mixin';
export * from './base/cap-mixin';
export * from './base/color-mixin';
export * from './base/coordinate-grid-mixin';
export * from './base/d3.box';
export * from './base/margin-mixin';
export * from './base/stack-mixin';

export * from './charts/bar-chart';
export * from './charts/box-plot';
export * from './charts/bubble-chart';
export * from './charts/bubble-overlay';
export * from './charts/cbox-menu';
export * from './charts/composite-chart';
export * from './charts/data-count';
export * from './charts/data-grid';
export * from './charts/data-table';
export * from './charts/geo-choropleth-chart';
export * from './charts/heatmap';
export * from './charts/html-legend';
export * from './charts/legend';
export * from './charts/line-chart';
export * from './charts/number-display';
export * from './charts/pie-chart';
export * from './charts/row-chart';
export * from './charts/scatter-plot';
export * from './charts/select-menu';
export * from './charts/series-chart';
export * from './charts/sunburst-chart';
export * from './charts/text-filter-widget';
<commit_msg>Export dc compat functions<commit_after>export * from './core/bad-argument-exception';
export * from './core/chart-registry';
export * from './core/config';
export * from './core/constants';
export * from './core/core';
export * from './core/events';
export * from './core/filters';
export * from './core/invalid-state-exception';
export * from './core/logger';
export * from './core/printers';
export * from './core/units';
export * from './core/utils';
export * from './core/d3compat';

export * from './base/base-mixin';
export * from './base/bubble-mixin';
export * from './base/cap-mixin';
export * from './base/color-mixin';
export * from './base/coordinate-grid-mixin';
export * from './base/d3.box';
export * from './base/margin-mixin';
export * from './base/stack-mixin';

export * from './charts/bar-chart';
export * from './charts/box-plot';
export * from './charts/bubble-chart';
export * from './charts/bubble-overlay';
export * from './charts/cbox-menu';
export * from './charts/composite-chart';
export * from './charts/data-count';
export * from './charts/data-grid';
export * from './charts/data-table';
export * from './charts/geo-choropleth-chart';
export * from './charts/heatmap';
export * from './charts/html-legend';
export * from './charts/legend';
export * from './charts/line-chart';
export * from './charts/number-display';
export * from './charts/pie-chart';
export * from './charts/row-chart';
export * from './charts/scatter-plot';
export * from './charts/select-menu';
export * from './charts/series-chart';
export * from './charts/sunburst-chart';
export * from './charts/text-filter-widget';
<|endoftext|>


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

const program = require("commander");
const storage = require('node-persist');
const username = require('username');
const path = require("path");
const fs = require("fs");
const colors = require('colors');
const sync = require('./sync');
const validate = require('./validate');

let packageJson = require("../package.json");

program
    .version(packageJson.version)
    .usage("[options]")
    .option("-a, --add [repo]", "Add a git repo to manage list")
    .option("-l, --list", "List all tracked git repos")
    .option("-r, --remove [repo]", "Remove an entry from managed list")
    .option("    --remove-all", "Remove all repos from managed list")
    .option("-s, --sync", "Syncs all added repos to remote")
    .option("    --sync-one [repo]", "Syncs specific repo to remote")
    .option("--select [repo]", "Used to specify branch for further action")
    .option("-b, --branch [branch]", "Add branch to add to managed list, requires --select")
    .option("    --remove-branch [branch]", "Removes branch from managed list, requires --select")
    .parse(process.argv);

if (process.argv.length === 2) program.outputHelp();

const homePath = "/home/" + username.sync() + "/.node-persist/";

storage.init({dir: homePath}).then(() => {
    if (program.add) {
        let addPath = path.resolve(program.add);
        let addName = addPath.split("/").slice(-1)[0];
        console.log(addPath);
        if (!fs.existsSync(addPath + "/.git")) {
            console.log("Directory not a git");
            return
        }
        let allRepos = storage.getItemSync("allRepos");

        if (!allRepos) {
            allRepos = {}
        }
        for (repo in Object.keys(allRepos)) {
            if (repo[0] === addName) {
                console.log("Repo already added");
                return
            }
        }

        allRepos[addName] = [addPath, ["master"]];
        storage.setItem("allRepos", allRepos);
        console.log("Added " + addName);
    }

    if (program.list) {
        let gits = storage.getItemSync("allRepos") || [];
        if (!Object.keys(gits).length) {
            console.log("There are no gits added");
            console.log("You can add one with git-manage -a git-repo-name");
            return;
        }
        console.log("Added gits:");
        let gitsToPrint = [];
        for (key in gits) {
            let repoName = key;
            let repoPath = gits[key][0];
            let valid = fs.existsSync(repoPath + "/.git");
            if (valid) {
                let spaceCounter = 30 - repoName.length;
                let spaces = "";
                for (let i = 0; i < spaceCounter; i++) {
                    spaces += " ";
                }
                gitsToPrint.push(key + spaces + repoPath);
            } else {
                console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
                delete gits[repoName]
            }
            storage.setItemSync("allRepos", gits);
        }
        gitsToPrint.sort();
        gitsToPrint.forEach(index => console.log(index));

    }

    if (program.remove) {
        let gits = storage.getItemSync("allRepos");
        let index = -1;
        let rmPath = path.resolve(program.remove);
        let rmName = rmPath.split("/").slice(-1)[0];
        if (Object.keys(gits).indexOf(rmName) === -1) {
            console.log("Repo not found in list");
            return;
        }
        delete gits[rmName];

        storage.setItem("allRepos", gits);
        console.log("Removed: " + rmName);

    }

    if (program.removeAll) {
        storage.setItemSync("allRepos", {});
        console.log("Removed all repos from manage list");
    }

    if (program.sync) {

        let gits = storage.getItemSync("allRepos", {});

        for (git in gits) {
            let valid = fs.existsSync(gits[git][0] + "/.git");
            if (valid) {
                for (i in gits[git][1]) {
                    sync(gits[git][0], gits[git][1][i]);
                }
            } else {
                console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
                delete gits[repoName];
            }
        }
        storage.setItemSync("allRepos", gits);

    }
    if (program.syncOne) {
        let gits = storage.getItemSync("allRepos", {});
        let valid = fs.existsSync(gits[git] + "/.git");
        if (valid) {
            sync(gits[program.syncOne], "master");
        } else {
            console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
            delete gits[repoName];
        }
        storage.setItemSync("allRepos", gits);
    }
    if (typeof program.select === "string" && typeof program.branch === "string") {
        let gits = storage.getItemSync("allRepos", {});
        if (Object.keys(gits).indexOf(program.select) === -1) {
            console.log("Repo does not exist");
            return
        }
        let simplegit = require('simple-git')(gits[program.select][0])
            .raw([
                "remote",
                "update"
            ])
            .raw([
                "branch"
            ], (err, branches) => {
                if (!err) {
                    branchList = branches.trim().split('\n');
                    for (branch in branchList) {
                        branchList[branch] = branchList[branch].trim().split(' ');
                        branchList[branch] = branchList[branch][branchList[branch].length - 1];
                    }
                    if (branchList.indexOf(program.branch) === -1) {
                        console.log("Branch not found");
                    } else {
                        if (gits[program.select][1].indexOf(program.select) === -1) {
                            gits[program.select][1].push(program.branch);
                            storage.setItemSync("allRepos", gits);
                            console.log("Added " + program.branch + " to " + program.select);
                        } else {
                            console.log("Already added to " + program.select);
                        }
                    }
                } else {
                    console.log("Err: " + err)
                }
            });
    }
    if (typeof program.select === "string" && typeof program.removeBranch === "string") {
        let gits = storage.getItemSync("allRepos", {});
        if (Object.keys(gits).indexOf(program.select) === -1) {
            console.log("Repo does not exist");
            return
        }
        let index = gits[program.select][1].indexOf(program.removeBranch);
        if (index === -1) {
            console.log("Branch not managed");
        } else {
            gits[program.select][1].splice(index, 1);
            storage.setItemSync("allRepos", gits);
            console.log("Removed " + program.removeBranch + " from " + program.select);
        }
    }
}, (err) => {
    console.log(err);
});

<commit_msg>Going to sleep for a while<commit_after>#! /usr/bin/env node

const program = require("commander");
const storage = require('node-persist');
const username = require('username');
const path = require("path");
const fs = require("fs");
const colors = require('colors');
const sync = require('./sync');
const validate = require('./validate');

let packageJson = require("../package.json");

program
    .version(packageJson.version)
    .usage("[options]")
    .option("-a, --add [repo]", "Add a git repo to manage list")
    .option("-l, --list", "List all tracked git repos")
    .option("-r, --remove [repo]", "Remove an entry from managed list")
    .option("    --remove-all", "Remove all repos from managed list")
    .option("-s, --sync", "Syncs all added repos to remote")
    .option("    --sync-one [repo]", "Syncs specific repo to remote")
    .option("--select [repo]", "Used to specify branch for further action")
    .option("-b, --branch [branch]", "Add branch to add to managed list, requires --select")
    .option("    --remove-branch [branch]", "Removes branch from managed list, requires --select")
    .parse(process.argv);

if (process.argv.length === 2) program.outputHelp();

const homePath = "/home/" + username.sync() + "/.node-persist/";

storage.init({dir: homePath}).then(() => {
    if (program.add) {
        let addPath = path.resolve(program.add);
        let addName = addPath.split("/").slice(-1)[0];
        console.log(addPath);
        if (!fs.existsSync(addPath + "/.git")) {
            console.log("Directory not a git");
            return
        }
        let allRepos = storage.getItemSync("allRepos");

        if (!allRepos) {
            allRepos = {}
        }
        for (repo in Object.keys(allRepos)) {
            if (repo[0] === addName) {
                console.log("Repo already added");
                return
            }
        }

        allRepos[addName] = [addPath, ["master"]];
        storage.setItem("allRepos", allRepos);
        console.log("Added " + addName);
    }

    if (program.list) {
        let gits = storage.getItemSync("allRepos") || [];
        if (!Object.keys(gits).length) {
            console.log("There are no gits added");
            console.log("You can add one with git-manage -a git-repo-name");
            return;
        }
        console.log("Added gits:");
        let gitsToPrint = [];
        for (key in gits) {
            let repoName = key;
            let repoPath = gits[key][0];
            let valid = fs.existsSync(repoPath + "/.git");
            if (valid) {
                let spaceCounter = 30 - repoName.length;
                let spaces = "";
                for (let i = 0; i < spaceCounter; i++) {
                    spaces += " ";
                }
                gitsToPrint.push(key + spaces + repoPath);
            } else {
                console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
                delete gits[repoName]
            }
            storage.setItemSync("allRepos", gits);
        }
        gitsToPrint.sort();
        gitsToPrint.forEach(index => console.log(index));

    }

    if (program.remove) {
        let gits = storage.getItemSync("allRepos");
        let index = -1;
        let rmPath = path.resolve(program.remove);
        let rmName = rmPath.split("/").slice(-1)[0];
        if (Object.keys(gits).indexOf(rmName) === -1) {
            console.log("Repo not found in list");
            return;
        }
        delete gits[rmName];

        storage.setItem("allRepos", gits);
        console.log("Removed: " + rmName);

    }

    if (program.removeAll) {
        storage.setItemSync("allRepos", {});
        console.log("Removed all repos from manage list");
    }

    if (program.sync) {

        let gits = storage.getItemSync("allRepos", {});

        for (git in gits) {
            let valid = fs.existsSync(gits[git][0] + "/.git");
            if (valid) {
                for (i in gits[git][1]) {
                    sync(gits[git][0], gits[git][1][i]);
                }
            } else {
                console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
                delete gits[repoName];
            }
        }
        storage.setItemSync("allRepos", gits);

    }
    if (program.syncOne) {
        let gits = storage.getItemSync("allRepos", {});
        let valid = fs.existsSync(gits[git] + "/.git");
        if (valid) {
            sync(gits[program.syncOne], "master");
        } else {
            console.log("Removing " + repoName + " at " + repoPath + " because not valid repo");
            delete gits[repoName];
        }
        storage.setItemSync("allRepos", gits);
    }
    if (typeof program.select === "string" && typeof program.branch === "string") {
        let gits = storage.getItemSync("allRepos", {});
        if (Object.keys(gits).indexOf(program.select) === -1) {
            console.log("Repo does not exist");
            return
        }
        let simplegit = require('simple-git')(gits[program.select][0])
            .raw([
                "remote",
                "update"
            ])
            .raw([
                "branch"
            ], (err, branches) => {
                if (!err) {
                    branchList = branches.trim().split('\n');
                    for (branch in branchList) {
                        branchList[branch] = branchList[branch].trim().split(' ');
                        branchList[branch] = branchList[branch][branchList[branch].length - 1];
                    }
                    if (branchList.indexOf(program.branch) === -1) {
                        console.log("Branch not found");
                    } else {
                        if (gits[program.select][1].indexOf(program.select) === -1) {
                            gits[program.select][1].push(program.branch);
                            storage.setItemSync("allRepos", gits);
                            console.log("Added " + program.branch + " to " + program.select);
                        } else {
                            console.log("Already added to " + program.select);
                        }
                    }
                } else {
                    console.log("Err: " + err)
                }
            });
    }
    if (typeof program.select === "string" && typeof program.removeBranch === "string") {
        let gits = storage.getItemSync("allRepos", {});
        if (Object.keys(gits).indexOf(program.select) === -1) {
            console.log("Repo does not exist");
            return
        }
        let index = gits[program.select][1].indexOf(program.removeBranch);
        if (index === -1) {
            console.log("Branch not managed");
        } else {
            gits[program.select][1].splice(index, 1);
            storage.setItemSync("allRepos", gits);
            console.log("Removed " + program.removeBranch + " from " + program.select);
        }
    }
}, (err) => {
    console.log(err);
});

<|endoftext|>


<commit_before>/**
 * Copyright 2017, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

require('colors');

const assert = require('assert');
const childProcess = require('child_process');
const path = require('path');
const proxyquire = require('proxyquire').noPreserveCache();
const sinon = require(`sinon`);
const supertest = require('supertest');

const { install, app, deploy } = require('./api/testRunner');
const { onChange } = require('./webhook');
const utils = require('./api/utils');

exports.getRequest = (config) => {
  if (process.env.E2E_TESTS) {
    return supertest(utils.getUrl(config));
  }
  return supertest(proxyquire(path.join(config.cwd, 'app'), {}));
};

exports.install = install;
exports.testInstallation = install;

exports.testLocalApp = (config, done) => {
  return new Promise((resolve, reject) => {
    log(config, 'GETTING PORT...');
    getPort(config).then((port) => {
      log(config, `GOT PORT ${port}...`);
      log(config, 'TESTING LOCAL APP...');
      let calledDone = false;

      const opts = {
        cwd: config.cwd
      };

      opts.env = Object.assign({}, process.env);
      if (config.env) {
        Object.assign(opts.env, config.env);
      }
      opts.env.PORT = opts.env.PORT || config.port || port;

      const proc = childProcess.spawn(config.cmd || 'npm', config.args || ['start'], opts);
      log(config, `PID: ${proc.pid}`);

      proc.on('error', finish);

      proc.stdout.on('data', (data) => {
        process.stdout.write(`${config.test.bold}: ${data.toString()}`);
      });
      proc.stderr.on('data', (data) => {
        process.stderr.write(`${config.test.bold}: ${data.toString()}`);
      });

      let requestErr;

      proc.on('exit', (code, signal) => {
        if (code !== 0 && signal !== 'SIGKILL') {
          finish(new Error(`${config.test}: failed to run!`));
        } else {
          finish(requestErr);
        }
      });

      // Give the server time to start up
      setTimeout(() => {
        // Test that the app is working
        testRequest(config.url || `http://localhost:${config.port || port}`, config)
          .then(() => finish(), (err) => {
            requestErr = err;
            finish(requestErr);
          });
      }, 3000);

      function cleanup () {
        try {
          process.kill(proc.pid, 'SIGKILL');
        } catch (err) {
          // Ignore error
        }
        try {
          proc.kill('SIGKILL');
        } catch (err) {
          // Ignore error
        }
      }

      process.on('exit', cleanup);

      // Exit helper so we don't call "cb" more than once
      function finish (err) {
        if (err) {
          log(config, `ERROR: ${err.message}`);
        } else {
          log(config, 'DONE');
        }

        cleanup();
        process.removeListener('exit', cleanup);

        if (!calledDone) {
          calledDone = true;
          setTimeout(() => finalize(err, resolve, reject, done), 1000);
        }
      }
    });
  });
};

function changeScaling (config, yamlName) {
  const oldYamlPath = path.join(config.cwd, yamlName);
  const newYamlPath = path.join(config.cwd, `${config.test}-${config.now}.yaml`);

  let yaml = fs.readFileSync(oldYamlPath, 'utf8');

  yaml += `\n\nmanual_scaling:\n  instances: 1\n`;

  fs.writeFileSync(newYamlPath, yaml, 'utf8');

  return newYamlPath;
}

exports.testDeploy = (config, tries, done) => {
  return new Promise((resolve, reject) => {
    // Keep track off whether "done" has been called yet
    let calledDone = false;
    // Keep track off whether the logs have fully flushed
    let logFinished = false;

    // Manually set # of instances to 1
    const tmpAppYaml = changeScaling(config, config.yaml || 'app.yaml');

    const args = [
      'app',
      'deploy',
      path.parse(tmpAppYaml).base,
      // Skip prompt
      '-q',
      `--project=${PROJECT_ID}`,
      // Deploy over existing version so we don't have to clean up
      `--version=${config.test}`,
      '--no-promote'
    ];

    // Exit helper so we don't call "done" more than once
    function finish (err) {
      if (!calledDone) {
        calledDone = true;
        if (err) {
          finalize(err, resolve, reject, done);
          return;
        }
        if (logFinished) {
          finalize(null, resolve, reject, done);
          return;
        }
        const intervalId = setInterval(() => {
          if (logFinished) {
            clearInterval(intervalId);
            finalize(null, resolve, reject, done);
          }
        }, 1000);
      }
    }

    function attemptDeploy (triesLeft) {
      console.log(`${triesLeft} tries remaining...`);
      log(config, 'DEPLOYING... ');

      const logFile = path.join(config.cwd, `${config.test}-${Date.now()}.log`);
      const logStream = fs.createWriteStream(logFile, { flags: 'a' });

      let numEnded = 0;
      function finishLogs () {
        numEnded++;
        if (numEnded === 2) {
          logStream.end();
          log(config, `Saved logfile to ${logFile}`);
        }
      }

      // Don't use "npm run deploy" because we need extra flags
      const proc = childProcess.spawn('gcloud', args, {
        cwd: config.cwd,
        shell: true
      });

      log(config, `gcloud ${args.join(' ')}`);

      try {
        logStream.on('finish', () => {
          if (!logFinished) {
            logFinished = true;
          }
        });

        proc.stdout.pipe(logStream, { end: false });
        proc.stderr.pipe(logStream, { end: false });

        proc.stdout.on('end', finishLogs);
        proc.stderr.on('end', finishLogs);

        // This is called if the process fails to start. "error" event may or may
        // not be fired in addition to the "exit" event.
        proc.on('error', finish);

        // Process has completed
        proc.on('exit', (code, signal) => {
          if (signal === 'SIGKILL') {
            log(config, 'SIGKILL received!');
          }
          if (code !== 0 && signal !== 'SIGKILL') {
            // Deployment failed
            log(config, `ERROR ${code} ${signal}`);

            // Pass error as second argument so we don't short-circuit the
            // parallel tasks
            const error = new Error(`${config.test}: failed to deploy!`);
            if (triesLeft > 0) {
              console.error(error);
              attemptDeploy(triesLeft - 1);
            } else {
              finish(error);
            }
          } else {
            // Deployment succeeded
            log(config, 'App deployed...');

            // Give apps time to start
            setTimeout(() => {
              // Test versioned url of "default" module
              let demoUrl = getUrl(config);

              if (config.demoUrl) {
                demoUrl = config.demoUrl;
              }

              // Test that app is running successfully
              log(config, `Testing ${demoUrl}`);
              testRequest(demoUrl, config, (err) => {
                if (err && triesLeft > 0) {
                  console.error(err);
                  attemptDeploy(triesLeft - 1);
                } else {
                  log(config, 'Success!');
                  finish(err);
                }
              });
            }, 5000);
          }
        });
      } catch (err) {
        if (proc) {
          proc.kill('SIGKILL');
        }
        finish(err);
      }
    }

    attemptDeploy(tries);
  });
};

exports.onChange = onChange;

exports.run = (cmd, cwd) => {
  return childProcess.execSync(cmd, { cwd: cwd }).toString().trim();
};

exports.runAsync = (cmd, cwd, cb) => {
  return new Promise((resolve, reject) => {
    childProcess.exec(cmd, { cwd: cwd }, (err, stdout, stderr) => {
      if (err) {
        reject(err);
        return;
      }
      if (stdout) {
        resolve(stdout.toString().trim());
      } else {
        resolve(stdout);
      }
    });
  });
};

class Try {
  constructor (test) {
    this._maxTries = 10;
    this._maxDelay = 20000;
    this._timeout = 60000;
    this._iteration = 1;
    this._multiplier = 1.3;
    this._delay = 500;
    this._test = test;
  }

  execute () {
    if (this._iteration >= this._maxTries) {
      return this.reject(this._error || new Error('Reached maximum number of tries'));
    } else if ((Date.now() - this._start) >= this._timeout) {
      return this.reject(this._error || new Error('Test timed out'));
    }

    return Promise.resolve()
      .then(() => this._test(assert))
      .then(() => this.resolve())
      .catch((err) => {
        this._error = err;
        this._iteration++;
        this._delay = Math.min(this._delay * this._multiplier, this._maxDelay);
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            Promise.resolve()
              .then(() => this.execute())
              .then(resolve, reject);
          }, this._delay);
        });
      });
  }

  timeout (timeout) {
    this._timeout = timeout;
  }

  tries (maxTries) {
    this._maxTries = maxTries;
  }

  start () {
    this._start = Date.now();
    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
      this.execute().then(resolve, reject);
    });
    return this.promise;
  }
}

exports.tryTest = (test) => {
  return new Try(test);
};

exports.stubConsole = () => {
  if (typeof console.log.restore !== `function` && typeof console.error.restore !== `function`) {
    if (process.env.DEBUG) {
      sinon.spy(console, `error`);
      sinon.spy(console, `log`);
    } else {
      sinon.stub(console, `error`);
      sinon.stub(console, `log`).callsFake((a, b, c) => {
        if (typeof a === `string` && a.indexOf(`\u001b`) !== -1 && typeof b === `string`) {
          console.log.apply(console, arguments);
        }
      });
    }
  }
};

exports.restoreConsole = () => {
  if (typeof console.log.restore === `function`) {
    console.log.restore();
  }
  if (typeof console.error.restore === `function`) {
    console.error.restore();
  }
};

exports.checkCredentials = (t) => {
  if (t && typeof t.truthy === 'function') {
    t.truthy(process.env.GCLOUD_PROJECT, `Must set GCLOUD_PROJECT environment variable!`);
    t.truthy(process.env.GOOGLE_APPLICATION_CREDENTIALS, `Must set GOOGLE_APPLICATION_CREDENTIALS environment variable!`);
  } else {
    assert(process.env.GCLOUD_PROJECT, `Must set GCLOUD_PROJECT environment variable!`);
    assert(process.env.GOOGLE_APPLICATION_CREDENTIALS, `Must set GOOGLE_APPLICATION_CREDENTIALS environment variable!`);
  }
};
<commit_msg>Destructuring is a v6 feature, but we support v4<commit_after>/**
 * Copyright 2017, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

require('colors');

const assert = require('assert');
const childProcess = require('child_process');
const path = require('path');
const proxyquire = require('proxyquire').noPreserveCache();
const sinon = require(`sinon`);
const supertest = require('supertest');

const install = require('./api/testRunner/install');
const onChange = require('./webhook/onChange');

const MAX_TRIES = 8;
const PROJECT_ID = process.env.GCLOUD_PROJECT;

function finalize (err, resolve, reject, done) {
  if (err) {
    reject(err);
    if (typeof done === 'function') {
      done(err);
    }
  } else {
    resolve();
    if (typeof done === 'function') {
      done();
    }
  }
}

function log (config, msg) {
  console.log(`${config.test.bold}: ${msg}`);
}

// Send a request to the given url and test that the response body has the
// expected value
function testRequest (url, config, numTry) {
  log(config, `VERIFYING: ${url}`);
  if (!numTry) {
    numTry = 1;
  }

  return got(url)
    .then((response) => {
      const EXPECTED_STATUS_CODE = config.code || 200;

      const body = response.body || '';
      const code = response.statusCode;

      if (code !== EXPECTED_STATUS_CODE) {
        throw new Error(`${config.test}: failed verification!\nExpected status code: ${EXPECTED_STATUS_CODE}\nActual: ${code}`);
      } else if (!body.includes(config.msg)) {
        throw new Error(`${config.test}: failed verification!\nExpected body: ${config.msg}\nActual: ${body}`);
      } else if (config.testStr && !config.testStr.test(body)) {
        throw new Error(`${config.test}: failed verification!\nExpected body: ${config.testStr}\nActual: ${body}`);
      }
    }, (err) => {
      if (err && err.response) {
        const EXPECTED_STATUS_CODE = config.code || 200;

        const body = err.response.body || '';
        const code = err.response.statusCode;

        if (code !== EXPECTED_STATUS_CODE) {
          throw new Error(`${config.test}: failed verification!\nExpected status code: ${EXPECTED_STATUS_CODE}\nActual: ${code}`);
        } else if (!body.includes(config.msg)) {
          throw new Error(`${config.test}: failed verification!\nExpected body: ${config.msg}\nActual: ${body}`);
        } else if (config.testStr && !config.testStr.test(body)) {
          throw new Error(`${config.test}: failed verification!\nExpected body: ${config.testStr}\nActual: ${body}`);
        }
      } else {
        return Promise.reject(err);
      }
    })
    .catch((err) => {
      if (numTry >= MAX_TRIES) {
        return Promise.reject(err);
      }

      return new Promise((resolve, reject) => {
        setTimeout(() => {
          testRequest(url, config, numTry + 1).then(resolve, reject);
        }, 500 * Math.pow(numTry, 2));
      });
    });
}

function getUrl (config) {
  return `https://${config.test}-dot-${PROJECT_ID}.appspot-preview.com`;
}

let portrange = 45032;
const triedPorts = {};

function getPort (config) {
  let port = config.port || portrange;

  triedPorts[port] = true;

  while (triedPorts[port]) {
    port += 1;
  }

  return new Promise((resolve) => {
    const server = net.createServer();
    server.listen(port, () => {
      server.once('close', () => {
        resolve(port);
      });
      server.close();
    });
    server.on('error', () => {
      resolve(getPort(config));
    });
  });
}

// Delete an App Engine version
exports.deleteVersion = (config, done) => {
  return new Promise((resolve, reject) => {
    log(config, 'DELETING DEPLOYMENT...');
    // Keep track off whether "done" has been called yet
    let calledDone = false;

    const args = [
      `app`,
      `versions`,
      `delete`,
      config.test,
      `--project=${PROJECT_ID}`,
      `-q`
    ];

    const child = childProcess.spawn(`gcloud`, args, {
      cwd: config.cwd,
      // Shouldn't take more than 4 minutes to delete a deployed version
      timeout: 4 * 60 * 1000
    });

    log(config, `gcloud ${args.join(' ')}`);

    child.on('error', finish);

    child.stdout.on('data', (data) => {
      const str = data.toString();
      if (str.includes('\n')) {
        process.stdout.write(`${config.test.bold}: ${str}`);
      } else {
        process.stdout.write(str);
      }
    });
    child.stderr.on('data', (data) => {
      const str = data.toString();
      if (str.includes('\n')) {
        process.stderr.write(`${config.test.bold}: ${str}`);
      } else {
        process.stderr.write(str);
      }
    });

    child.on('exit', (code) => {
      if (code !== 0) {
        finish(new Error(`${config.test}: failed to delete deployment!`));
      } else {
        finish();
      }
    });

    // Exit helper so we don't call "cb" more than once
    function finish (err) {
      if (!calledDone) {
        calledDone = true;
        finalize(err, resolve, reject, done);
      }
    }
  });
};

exports.getRequest = (config) => {
  if (process.env.E2E_TESTS) {
    return supertest(utils.getUrl(config));
  }
  return supertest(proxyquire(path.join(config.cwd, 'app'), {}));
};

exports.install = install;
exports.testInstallation = install;

exports.testLocalApp = (config, done) => {
  return new Promise((resolve, reject) => {
    log(config, 'GETTING PORT...');
    getPort(config).then((port) => {
      log(config, `GOT PORT ${port}...`);
      log(config, 'TESTING LOCAL APP...');
      let calledDone = false;

      const opts = {
        cwd: config.cwd
      };

      opts.env = Object.assign({}, process.env);
      if (config.env) {
        Object.assign(opts.env, config.env);
      }
      opts.env.PORT = opts.env.PORT || config.port || port;

      const proc = childProcess.spawn(config.cmd || 'npm', config.args || ['start'], opts);
      log(config, `PID: ${proc.pid}`);

      proc.on('error', finish);

      proc.stdout.on('data', (data) => {
        process.stdout.write(`${config.test.bold}: ${data.toString()}`);
      });
      proc.stderr.on('data', (data) => {
        process.stderr.write(`${config.test.bold}: ${data.toString()}`);
      });

      let requestErr;

      proc.on('exit', (code, signal) => {
        if (code !== 0 && signal !== 'SIGKILL') {
          finish(new Error(`${config.test}: failed to run!`));
        } else {
          finish(requestErr);
        }
      });

      // Give the server time to start up
      setTimeout(() => {
        // Test that the app is working
        testRequest(config.url || `http://localhost:${config.port || port}`, config)
          .then(() => finish(), (err) => {
            requestErr = err;
            finish(requestErr);
          });
      }, 3000);

      function cleanup () {
        try {
          process.kill(proc.pid, 'SIGKILL');
        } catch (err) {
          // Ignore error
        }
        try {
          proc.kill('SIGKILL');
        } catch (err) {
          // Ignore error
        }
      }

      process.on('exit', cleanup);

      // Exit helper so we don't call "cb" more than once
      function finish (err) {
        if (err) {
          log(config, `ERROR: ${err.message}`);
        } else {
          log(config, 'DONE');
        }

        cleanup();
        process.removeListener('exit', cleanup);

        if (!calledDone) {
          calledDone = true;
          setTimeout(() => finalize(err, resolve, reject, done), 1000);
        }
      }
    });
  });
};

function changeScaling (config, yamlName) {
  const oldYamlPath = path.join(config.cwd, yamlName);
  const newYamlPath = path.join(config.cwd, `${config.test}-${config.now}.yaml`);

  let yaml = fs.readFileSync(oldYamlPath, 'utf8');

  yaml += `\n\nmanual_scaling:\n  instances: 1\n`;

  fs.writeFileSync(newYamlPath, yaml, 'utf8');

  return newYamlPath;
}

exports.testDeploy = (config, tries, done) => {
  return new Promise((resolve, reject) => {
    // Keep track off whether "done" has been called yet
    let calledDone = false;
    // Keep track off whether the logs have fully flushed
    let logFinished = false;

    // Manually set # of instances to 1
    const tmpAppYaml = changeScaling(config, config.yaml || 'app.yaml');

    const args = [
      'app',
      'deploy',
      path.parse(tmpAppYaml).base,
      // Skip prompt
      '-q',
      `--project=${PROJECT_ID}`,
      // Deploy over existing version so we don't have to clean up
      `--version=${config.test}`,
      '--no-promote'
    ];

    // Exit helper so we don't call "done" more than once
    function finish (err) {
      if (!calledDone) {
        calledDone = true;
        if (err) {
          finalize(err, resolve, reject, done);
          return;
        }
        if (logFinished) {
          finalize(null, resolve, reject, done);
          return;
        }
        const intervalId = setInterval(() => {
          if (logFinished) {
            clearInterval(intervalId);
            finalize(null, resolve, reject, done);
          }
        }, 1000);
      }
    }

    function attemptDeploy (triesLeft) {
      console.log(`${triesLeft} tries remaining...`);
      log(config, 'DEPLOYING... ');

      const logFile = path.join(config.cwd, `${config.test}-${Date.now()}.log`);
      const logStream = fs.createWriteStream(logFile, { flags: 'a' });

      let numEnded = 0;
      function finishLogs () {
        numEnded++;
        if (numEnded === 2) {
          logStream.end();
          log(config, `Saved logfile to ${logFile}`);
        }
      }

      // Don't use "npm run deploy" because we need extra flags
      const proc = childProcess.spawn('gcloud', args, {
        cwd: config.cwd,
        shell: true
      });

      log(config, `gcloud ${args.join(' ')}`);

      try {
        logStream.on('finish', () => {
          if (!logFinished) {
            logFinished = true;
          }
        });

        proc.stdout.pipe(logStream, { end: false });
        proc.stderr.pipe(logStream, { end: false });

        proc.stdout.on('end', finishLogs);
        proc.stderr.on('end', finishLogs);

        // This is called if the process fails to start. "error" event may or may
        // not be fired in addition to the "exit" event.
        proc.on('error', finish);

        // Process has completed
        proc.on('exit', (code, signal) => {
          if (signal === 'SIGKILL') {
            log(config, 'SIGKILL received!');
          }
          if (code !== 0 && signal !== 'SIGKILL') {
            // Deployment failed
            log(config, `ERROR ${code} ${signal}`);

            // Pass error as second argument so we don't short-circuit the
            // parallel tasks
            const error = new Error(`${config.test}: failed to deploy!`);
            if (triesLeft > 0) {
              console.error(error);
              attemptDeploy(triesLeft - 1);
            } else {
              finish(error);
            }
          } else {
            // Deployment succeeded
            log(config, 'App deployed...');

            // Give apps time to start
            setTimeout(() => {
              // Test versioned url of "default" module
              let demoUrl = getUrl(config);

              if (config.demoUrl) {
                demoUrl = config.demoUrl;
              }

              // Test that app is running successfully
              log(config, `Testing ${demoUrl}`);
              testRequest(demoUrl, config, (err) => {
                if (err && triesLeft > 0) {
                  console.error(err);
                  attemptDeploy(triesLeft - 1);
                } else {
                  log(config, 'Success!');
                  finish(err);
                }
              });
            }, 5000);
          }
        });
      } catch (err) {
        if (proc) {
          proc.kill('SIGKILL');
        }
        finish(err);
      }
    }

    attemptDeploy(tries);
  });
};

exports.onChange = onChange;

exports.run = (cmd, cwd) => {
  return childProcess.execSync(cmd, { cwd: cwd }).toString().trim();
};

exports.runAsync = (cmd, cwd, cb) => {
  return new Promise((resolve, reject) => {
    childProcess.exec(cmd, { cwd: cwd }, (err, stdout, stderr) => {
      if (err) {
        reject(err);
        return;
      }
      if (stdout) {
        resolve(stdout.toString().trim());
      } else {
        resolve(stdout);
      }
    });
  });
};

class Try {
  constructor (test) {
    this._maxTries = 10;
    this._maxDelay = 20000;
    this._timeout = 60000;
    this._iteration = 1;
    this._multiplier = 1.3;
    this._delay = 500;
    this._test = test;
  }

  execute () {
    if (this._iteration >= this._maxTries) {
      return this.reject(this._error || new Error('Reached maximum number of tries'));
    } else if ((Date.now() - this._start) >= this._timeout) {
      return this.reject(this._error || new Error('Test timed out'));
    }

    return Promise.resolve()
      .then(() => this._test(assert))
      .then(() => this.resolve())
      .catch((err) => {
        this._error = err;
        this._iteration++;
        this._delay = Math.min(this._delay * this._multiplier, this._maxDelay);
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            Promise.resolve()
              .then(() => this.execute())
              .then(resolve, reject);
          }, this._delay);
        });
      });
  }

  timeout (timeout) {
    this._timeout = timeout;
  }

  tries (maxTries) {
    this._maxTries = maxTries;
  }

  start () {
    this._start = Date.now();
    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
      this.execute().then(resolve, reject);
    });
    return this.promise;
  }
}

exports.tryTest = (test) => {
  return new Try(test);
};

exports.stubConsole = () => {
  if (typeof console.log.restore !== `function` && typeof console.error.restore !== `function`) {
    if (process.env.DEBUG) {
      sinon.spy(console, `error`);
      sinon.spy(console, `log`);
    } else {
      sinon.stub(console, `error`);
      sinon.stub(console, `log`).callsFake((a, b, c) => {
        if (typeof a === `string` && a.indexOf(`\u001b`) !== -1 && typeof b === `string`) {
          console.log.apply(console, arguments);
        }
      });
    }
  }
};

exports.restoreConsole = () => {
  if (typeof console.log.restore === `function`) {
    console.log.restore();
  }
  if (typeof console.error.restore === `function`) {
    console.error.restore();
  }
};

exports.checkCredentials = (t) => {
  if (t && typeof t.truthy === 'function') {
    t.truthy(process.env.GCLOUD_PROJECT, `Must set GCLOUD_PROJECT environment variable!`);
    t.truthy(process.env.GOOGLE_APPLICATION_CREDENTIALS, `Must set GOOGLE_APPLICATION_CREDENTIALS environment variable!`);
  } else {
    assert(process.env.GCLOUD_PROJECT, `Must set GCLOUD_PROJECT environment variable!`);
    assert(process.env.GOOGLE_APPLICATION_CREDENTIALS, `Must set GOOGLE_APPLICATION_CREDENTIALS environment variable!`);
  }
};
<|endoftext|>


<commit_before>'use strict'

const spdy = require('spdy-transport')

exports = module.exports = function (transport, isListener) {
  const muxer = spdy.connection.create(transport, {
    protocol: 'spdy',
    isServer: isListener
  })

  muxer.start(3.1)

  // method added to enable pure stream muxer feeling
  muxer.newStream = (callback) => {
    if (!callback) {
      callback = noop
    }

    return muxer.request({
      method: 'POST',
      path: '/',
      headers: {}
    }, callback)
  }

  // The rest of the API comes by default with SPDY
  // muxer.on('stream', (stream) => {})
  // muxer.on('close', () => {})
  // muxer.on('error', (err) => {})
  // muxer.end()

  // needed by other spdy impl that need the response headers in order
  // to confirm the stream can be open
  muxer.on('stream', (stream) => {
    stream.respond(200, {})
  })

  muxer.multicodec = exports.multicodec
  return muxer
}

exports.multicodec = '/spdy/3.1.0'

function noop () {}
<commit_msg>pass real Connection objs up<commit_after>'use strict'

const spdy = require('spdy-transport')
const Connection = require('interface-connection').Connection
const EE = require('events').EventEmitter

exports = module.exports = function (transport, isListener) {
  const muxer = spdy.connection.create(transport, {
    protocol: 'spdy',
    isServer: isListener
  })

  const proxyMuxer = new EE()

  muxer.start(3.1)

  // method added to enable pure stream muxer feeling
  proxyMuxer.newStream = (callback) => {
    if (!callback) {
      callback = noop
    }

    return new Connection(muxer.request({
      method: 'POST',
      path: '/',
      headers: {}
    }, callback))
  }

  // The rest of the API comes by default with SPDY
  muxer.on('close', () => {
    proxyMuxer.emit('close')
  })

  muxer.on('error', (err) => {
    proxyMuxer.emit('error', err)
  })

  proxyMuxer.end = () => {
    muxer.end()
  }

  // needed by other spdy impl that need the response headers
  // in order to confirm the stream can be open
  muxer.on('stream', (stream) => {
    stream.respond(200, {})
    const conn = new Connection(stream)
    proxyMuxer.emit('stream', conn)
  })

  proxyMuxer.multicodec = exports.multicodec
  return proxyMuxer
}

exports.multicodec = '/spdy/3.1.0'

function noop () {}
<|endoftext|>


<commit_before>var fs = require('fs')
var path = require('path')
var normalize = require('normalize-path')
var findParentDir = require('find-parent-dir')
var hooks = require('./hooks.json')
var pkg = require('../package.json')

function write (filename, data) {
  fs.writeFileSync(filename, data)
  fs.chmodSync(filename, parseInt('0755', 8))
}

function isHusky (filename) {
  var data = fs.readFileSync(filename, 'utf-8')
  return data.indexOf('#husky') !== -1
}

function findHooksDir (dirname) {
  var dir = findParentDir.sync(dirname, '.git')

  if (dir) {
    var gitDir = path.join(dir, '.git')
    var stats = fs.lstatSync(gitDir)

    if (stats.isFile()) {
      // Expect following format
      // git: pathToGit
      gitDir = fs
        .readFileSync(gitDir, 'utf-8')
        .split(':')[1]
        .trim()

      return path.join(dir, gitDir, 'hooks')
    }

    return path.join(gitDir, 'hooks')
  }
}

function getHookScript (hookName, relativePath, cmd) {
    // On Windows normalize path (i.e. convert \ to /)
  var normalizedPath = normalize(relativePath)

  // Hook script
  var arr = [
    '#!/bin/sh',
    '#husky ' + pkg.version,
    '',
    'command_exists () {',
    '  command -v "$1" >/dev/null 2>&1',
    '}',
    '',

    'load_nvm () {',
    '  export $1=$2',
    '  [ -s "$1/nvm.sh" ] && . $1/nvm.sh',
    '  exists nvm && [ -f .nvmrc ] && nvm use',
    '}',
    '',

    'has_hook_script () {',
    '  [ -f package.json ] && cat package.json | grep -q "\\"$1\\"\\s*:"',
    '}',
    ''
  ]

  arr = arr.concat([
    'cd ' + normalizedPath,
    '',
    // Fix for issue #16 #24
    // If script is not defined in package.json then exit
    'has_hook_script ' + cmd + ' || exit 0',
    ''
  ])

  // On OS X and Linux, try to use nvm if it's installed
  if (process.platform !== 'win32') {
    // ~ is unavaible, so $HOME is used
    var home = process.env.HOME

    if (process.platform === 'darwin') {
      // Add
      // Brew standard installation path /use/local/bin
      // Node standard installation path /usr/local
      // for GUI apps
      // https://github.com/typicode/husky/issues/49
      arr = arr.concat([
        'export PATH=$PATH:/usr/local/bin:/usr/local'
      ])
    }

    if (process.platform === 'darwin') {
      arr = arr.concat([
        'command_exists npm || load_nvm BREW_NVM_DIR /usr/local/opt/nvm',
        ''
      ])
    }

    arr = arr.concat([
      'command_exists npm || load_nvm NVM_DIR ' + home + '/.nvm',
      ''
    ])
  } else {
    // Add
    // Node standard installation path /c/Program Files/nodejs
    // for GUI apps
    // https://github.com/typicode/husky/issues/49
    arr = arr.concat([
      'export PATH="$PATH:/c/Program Files/nodejs"'
    ])
  }

  // Can't find npm message
  var npmNotFound = '> husky - Can\'t find npm in PATH. Skipping ' + cmd + ' script in package.json'

  arr = arr.concat([
    // Test if npm is in PATH
    'command_exists npm || {',
    '  echo >&2 "' + npmNotFound + '"',
    '  exit 0',
    '}',
    '',

    // Run script
    'echo',
    'echo "husky > npm run -s ' + cmd + '"',
    'echo',
    '',

    'export GIT_PARAMS="$*"',
    'npm run -s ' + cmd + ' || {',
    '  echo',
    '  echo "> husky - ' + hookName + ' hook failed (add --no-verify to bypass)"',
    '  echo "> husky - To debug, use \'npm run precommit\'"',
    '  exit 1',
    '}',
    ''
  ])

  return arr.join('\n')
}

function createHook (huskyDir, hooksDir, hookName, cmd) {
  var filename = path.join(hooksDir, hookName)

  // Assuming that this file is in node_modules/husky
  var packageDir = path.join(huskyDir, '..', '..')

  // Get project directory
  // When used in submodule, the project dir is the first .git that is found
  var projectDir = findParentDir.sync(huskyDir, '.git')

  // In order to support projects with package.json in a different directory
  // than .git, find relative path from project directory to package.json
  var relativePath = path.join('.', path.relative(projectDir, packageDir))

  var hookScript = getHookScript(hookName, relativePath, cmd)

  // Create hooks directory if needed
  if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir)

  if (!fs.existsSync(filename)) {
    write(filename, hookScript)
  } else {
    if (isHusky(filename)) {
      write(filename, hookScript)
    } else {
      console.log('skipping ' + hookName + ' hook (existing user hook)')
    }
  }
}

function removeHook (dir, name) {
  var filename = dir + '/' + name

  if (fs.existsSync(filename) && isHusky(filename)) {
    fs.unlinkSync(dir + '/' + name)
  }
}

function installFrom (huskyDir) {
  try {
    var isInSubNodeModule = (huskyDir.match(/node_modules/g) || []).length > 1
    if (isInSubNodeModule) {
      return console.log(
        'Trying to install from sub \'node_module\' directory,',
        'skipping Git hooks installation'
      )
    }

    var hooksDir = findHooksDir(huskyDir)

    if (hooksDir) {
      hooks.forEach(function (hookName) {
        var npmScriptName = hookName.replace(/-/g, '')
        createHook(huskyDir, hooksDir, hookName, npmScriptName)
      })
      console.log('done\n')
    } else {
      console.log('Can\'t find .git directory, skipping Git hooks installation')
    }
  } catch (e) {
    console.error(e)
  }
}

function uninstallFrom (huskyDir) {
  try {
    var hooksDir = findHooksDir(huskyDir)

    hooks.forEach(function (hookName) {
      removeHook(hooksDir, hookName)
    })
    console.log('done\n')
  } catch (e) {
    console.error(e)
  }
}

module.exports = {
  installFrom: installFrom,
  uninstallFrom: uninstallFrom
}
<commit_msg>Update output<commit_after>var fs = require('fs')
var path = require('path')
var normalize = require('normalize-path')
var findParentDir = require('find-parent-dir')
var hooks = require('./hooks.json')
var pkg = require('../package.json')

function write (filename, data) {
  fs.writeFileSync(filename, data)
  fs.chmodSync(filename, parseInt('0755', 8))
}

function isHusky (filename) {
  var data = fs.readFileSync(filename, 'utf-8')
  return data.indexOf('#husky') !== -1
}

function findHooksDir (dirname) {
  var dir = findParentDir.sync(dirname, '.git')

  if (dir) {
    var gitDir = path.join(dir, '.git')
    var stats = fs.lstatSync(gitDir)

    if (stats.isFile()) {
      // Expect following format
      // git: pathToGit
      gitDir = fs
        .readFileSync(gitDir, 'utf-8')
        .split(':')[1]
        .trim()

      return path.join(dir, gitDir, 'hooks')
    }

    return path.join(gitDir, 'hooks')
  }
}

function getHookScript (hookName, relativePath, cmd) {
    // On Windows normalize path (i.e. convert \ to /)
  var normalizedPath = normalize(relativePath)

  // Hook script
  var arr = [
    '#!/bin/sh',
    '#husky ' + pkg.version,
    '',
    'command_exists () {',
    '  command -v "$1" >/dev/null 2>&1',
    '}',
    '',

    'load_nvm () {',
    '  export $1=$2',
    '  [ -s "$1/nvm.sh" ] && . $1/nvm.sh',
    '  exists nvm && [ -f .nvmrc ] && nvm use',
    '}',
    '',

    'has_hook_script () {',
    '  [ -f package.json ] && cat package.json | grep -q "\\"$1\\"\\s*:"',
    '}',
    ''
  ]

  arr = arr.concat([
    'cd ' + normalizedPath,
    '',
    // Fix for issue #16 #24
    // If script is not defined in package.json then exit
    'has_hook_script ' + cmd + ' || exit 0',
    ''
  ])

  // On OS X and Linux, try to use nvm if it's installed
  if (process.platform !== 'win32') {
    // ~ is unavaible, so $HOME is used
    var home = process.env.HOME

    if (process.platform === 'darwin') {
      // Add
      // Brew standard installation path /use/local/bin
      // Node standard installation path /usr/local
      // for GUI apps
      // https://github.com/typicode/husky/issues/49
      arr = arr.concat([
        'export PATH=$PATH:/usr/local/bin:/usr/local'
      ])
    }

    if (process.platform === 'darwin') {
      arr = arr.concat([
        'command_exists npm || load_nvm BREW_NVM_DIR /usr/local/opt/nvm',
        ''
      ])
    }

    arr = arr.concat([
      'command_exists npm || load_nvm NVM_DIR ' + home + '/.nvm',
      ''
    ])
  } else {
    // Add
    // Node standard installation path /c/Program Files/nodejs
    // for GUI apps
    // https://github.com/typicode/husky/issues/49
    arr = arr.concat([
      'export PATH="$PATH:/c/Program Files/nodejs"'
    ])
  }

  // Can't find npm message
  var npmNotFound = '> husky - Can\'t find npm in PATH. Skipping ' + cmd + ' script in package.json'

  arr = arr.concat([
    // Test if npm is in PATH
    'command_exists npm || {',
    '  echo >&2 "' + npmNotFound + '"',
    '  exit 0',
    '}',
    '',

    // Run script
    'echo',
    'echo "> husky - npm run -s ' + cmd + '"',
    'echo',
    '',

    'export GIT_PARAMS="$*"',
    'npm run -s ' + cmd + ' || {',
    '  echo',
    '  echo "> husky - ' + hookName + ' hook failed (add --no-verify to bypass)"',
    '  echo "> husky - to debug, use \'npm run precommit\'"',
    '  exit 1',
    '}',
    ''
  ])

  return arr.join('\n')
}

function createHook (huskyDir, hooksDir, hookName, cmd) {
  var filename = path.join(hooksDir, hookName)

  // Assuming that this file is in node_modules/husky
  var packageDir = path.join(huskyDir, '..', '..')

  // Get project directory
  // When used in submodule, the project dir is the first .git that is found
  var projectDir = findParentDir.sync(huskyDir, '.git')

  // In order to support projects with package.json in a different directory
  // than .git, find relative path from project directory to package.json
  var relativePath = path.join('.', path.relative(projectDir, packageDir))

  var hookScript = getHookScript(hookName, relativePath, cmd)

  // Create hooks directory if needed
  if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir)

  if (!fs.existsSync(filename)) {
    write(filename, hookScript)
  } else {
    if (isHusky(filename)) {
      write(filename, hookScript)
    } else {
      console.log('skipping ' + hookName + ' hook (existing user hook)')
    }
  }
}

function removeHook (dir, name) {
  var filename = dir + '/' + name

  if (fs.existsSync(filename) && isHusky(filename)) {
    fs.unlinkSync(dir + '/' + name)
  }
}

function installFrom (huskyDir) {
  try {
    var isInSubNodeModule = (huskyDir.match(/node_modules/g) || []).length > 1
    if (isInSubNodeModule) {
      return console.log(
        'Trying to install from sub \'node_module\' directory,',
        'skipping Git hooks installation'
      )
    }

    var hooksDir = findHooksDir(huskyDir)

    if (hooksDir) {
      hooks.forEach(function (hookName) {
        var npmScriptName = hookName.replace(/-/g, '')
        createHook(huskyDir, hooksDir, hookName, npmScriptName)
      })
      console.log('done\n')
    } else {
      console.log('Can\'t find .git directory, skipping Git hooks installation')
    }
  } catch (e) {
    console.error(e)
  }
}

function uninstallFrom (huskyDir) {
  try {
    var hooksDir = findHooksDir(huskyDir)

    hooks.forEach(function (hookName) {
      removeHook(hooksDir, hookName)
    })
    console.log('done\n')
  } catch (e) {
    console.error(e)
  }
}

module.exports = {
  installFrom: installFrom,
  uninstallFrom: uninstallFrom
}
<|endoftext|>


<commit_before>import omit from 'lodash.omit';
import Proto from 'uberproto';
import filter from 'feathers-query-filters';
import errors from 'feathers-errors';
import { select } from 'feathers-commons';
import * as utils from './utils';

class Service {
  constructor (options) {
    if (!options) {
      throw new Error('Sequelize options have to be provided');
    }

    if (!options.Model) {
      throw new Error('You must provide a Sequelize Model');
    }

    this.paginate = options.paginate || {};
    this.Model = options.Model;
    this.id = options.id || 'id';
    this.events = options.events;
    this.raw = options.raw !== false;
  }

  applyScope (params) {
    if ((params.sequelize || {}).scope) {
      return this.Model.scope(params.sequelize.scope);
    }
    return this.Model;
  }

  extend (obj) {
    return Proto.extend(obj, this);
  }

  _find (params, getFilter = filter) {
    const { filters, query } = getFilter(params.query || {});
    const where = utils.getWhere(query);
    const order = utils.getOrder(filters.$sort);

    const q = Object.assign({
      where,
      order,
      limit: filters.$limit,
      offset: filters.$skip,
      raw: this.raw
    }, params.sequelize);

    if (filters.$select) {
      q.attributes = filters.$select;
    }

    let Model = this.applyScope(params);

    return Model.findAndCount(q).then(result => {
      return {
        total: result.count,
        limit: filters.$limit,
        skip: filters.$skip || 0,
        data: result.rows
      };
    }).catch(utils.errorHandler);
  }

  find (params) {
    const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate;
    const result = this._find(params, where => filter(where, paginate));

    if (!paginate.default) {
      return result.then(page => page.data);
    }

    return result;
  }

  _get (id, params) {
    const where = utils.getWhere(params.query);

    // Attach 'where' constraints, if any were used.
    const q = Object.assign({
      raw: this.raw,
      where: Object.assign({[this.id]: id}, where)
    }, params.sequelize);

    let Model = this.applyScope(params);

    // findById calls findAll under the hood. We use findAll so that
    // eager loading can be used without a separate code path.
    return Model.findAll(q).then(result => {
      if (result.length === 0) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }

      return result[0];
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }

  // returns either the model intance for an id or all unpaginated
  // items for `params` if id is null
  _getOrFind (id, params) {
    if (id === null) {
      return this._find(params).then(page => page.data);
    }

    return this._get(id, params);
  }

  get (id, params) {
    return this._get(id, params).then(select(params, this.id));
  }

  create (data, params) {
    const options = Object.assign({raw: this.raw}, params.sequelize);
    // Model.create's `raw` option is different from other methods.
    // In order to use `raw` consistently to serialize the result,
    // we need to shadow the Model.create use of raw, which we provide
    // access to by specifying `ignoreSetters`.
    const ignoreSetters = Boolean(options.ignoreSetters);
    const createOptions = Object.assign({}, options, {raw: ignoreSetters});
    const isArray = Array.isArray(data);
    let promise;

    let Model = this.applyScope(params);

    if (isArray) {
      promise = Model.bulkCreate(data, createOptions);
    } else {
      promise = Model.create(data, createOptions);
    }

    return promise.then(result => {
      const sel = select(params, this.id);
      if (options.raw === false) {
        return result;
      }
      if (isArray) {
        return result.map(item => sel(item.toJSON()));
      }
      return sel(result.toJSON());
    }).catch(utils.errorHandler);
  }

  patch (id, data, params) {
    const where = Object.assign({}, filter(params.query || {}).query);
    const mapIds = page => page.data.map(current => current[this.id]);

    if (id !== null) {
      where[this.id] = id;
    }

    const options = Object.assign({}, params.sequelize, { where });

    let Model = this.applyScope(params);

    // This is the best way to implement patch in sql, the other dialects 'should' use a transaction.
    if (Model.sequelize.options.dialect === 'postgres') {
      options.returning = true;
      return Model.update(omit(data, this.id), options)
            .then(results => {
              if (id === null) {
                return results[1];
              }

              if (!results[1].length) {
                throw new errors.NotFound(`No record found for id '${id}'`);
              }

              return results[1][0];
            })
            .then(select(params, this.id))
            .catch(utils.errorHandler);
    }

    // By default we will just query for the one id. For multi patch
    // we create a list of the ids of all items that will be changed
    // to re-query them after the update
    const ids = id === null ? this._find(params)
        .then(mapIds) : Promise.resolve([ id ]);

    return ids
      .then(idList => {
        // Create a new query that re-queries all ids that
        // were originally changed
        const findParams = Object.assign({}, params, {
          query: { [this.id]: { $in: idList } }
        });

        return Model.update(omit(data, this.id), options)
            .then(() => this._getOrFind(id, findParams));
      })
      .then(select(params, this.id))
      .catch(utils.errorHandler);
  }

  update (id, data, params) {
    const options = Object.assign({ raw: this.raw }, params.sequelize);

    if (Array.isArray(data)) {
      return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?'));
    }

    let Model = this.applyScope(params);

    // Force the {raw: false} option as the instance is needed to properly
    // update
    return Model.findById(id, { raw: false }).then(instance => {
      if (!instance) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }

      let copy = {};
      Object.keys(instance.toJSON()).forEach(key => {
        if (typeof data[key] === 'undefined') {
          copy[key] = null;
        } else {
          copy[key] = data[key];
        }
      });

      return instance.update(copy, options).then(instance => {
        if (options.raw === false) {
          return instance;
        }
        return instance.toJSON();
      });
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }

  remove (id, params) {
    const opts = Object.assign({ raw: this.raw }, params);
    return this._getOrFind(id, opts).then(data => {
      const where = Object.assign({}, filter(params.query || {}).query);

      if (id !== null) {
        where[this.id] = id;
      }

      const options = Object.assign({}, params.sequelize, { where });

      let Model = this.applyScope(params);

      return Model.destroy(options).then(() => data);
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }
}

export default function init (options) {
  return new Service(options);
}

init.Service = Service;
<commit_msg>Patch should default to raw queries (#147)<commit_after>import omit from 'lodash.omit';
import Proto from 'uberproto';
import filter from 'feathers-query-filters';
import errors from 'feathers-errors';
import { select } from 'feathers-commons';
import * as utils from './utils';

class Service {
  constructor (options) {
    if (!options) {
      throw new Error('Sequelize options have to be provided');
    }

    if (!options.Model) {
      throw new Error('You must provide a Sequelize Model');
    }

    this.paginate = options.paginate || {};
    this.Model = options.Model;
    this.id = options.id || 'id';
    this.events = options.events;
    this.raw = options.raw !== false;
  }

  applyScope (params) {
    if ((params.sequelize || {}).scope) {
      return this.Model.scope(params.sequelize.scope);
    }
    return this.Model;
  }

  extend (obj) {
    return Proto.extend(obj, this);
  }

  _find (params, getFilter = filter) {
    const { filters, query } = getFilter(params.query || {});
    const where = utils.getWhere(query);
    const order = utils.getOrder(filters.$sort);

    const q = Object.assign({
      where,
      order,
      limit: filters.$limit,
      offset: filters.$skip,
      raw: this.raw
    }, params.sequelize);

    if (filters.$select) {
      q.attributes = filters.$select;
    }

    let Model = this.applyScope(params);

    return Model.findAndCount(q).then(result => {
      return {
        total: result.count,
        limit: filters.$limit,
        skip: filters.$skip || 0,
        data: result.rows
      };
    }).catch(utils.errorHandler);
  }

  find (params) {
    const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate;
    const result = this._find(params, where => filter(where, paginate));

    if (!paginate.default) {
      return result.then(page => page.data);
    }

    return result;
  }

  _get (id, params) {
    const where = utils.getWhere(params.query);

    // Attach 'where' constraints, if any were used.
    const q = Object.assign({
      raw: this.raw,
      where: Object.assign({[this.id]: id}, where)
    }, params.sequelize);

    let Model = this.applyScope(params);

    // findById calls findAll under the hood. We use findAll so that
    // eager loading can be used without a separate code path.
    return Model.findAll(q).then(result => {
      if (result.length === 0) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }

      return result[0];
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }

  // returns either the model intance for an id or all unpaginated
  // items for `params` if id is null
  _getOrFind (id, params) {
    if (id === null) {
      return this._find(params).then(page => page.data);
    }

    return this._get(id, params);
  }

  get (id, params) {
    return this._get(id, params).then(select(params, this.id));
  }

  create (data, params) {
    const options = Object.assign({raw: this.raw}, params.sequelize);
    // Model.create's `raw` option is different from other methods.
    // In order to use `raw` consistently to serialize the result,
    // we need to shadow the Model.create use of raw, which we provide
    // access to by specifying `ignoreSetters`.
    const ignoreSetters = Boolean(options.ignoreSetters);
    const createOptions = Object.assign({}, options, {raw: ignoreSetters});
    const isArray = Array.isArray(data);
    let promise;

    let Model = this.applyScope(params);

    if (isArray) {
      promise = Model.bulkCreate(data, createOptions);
    } else {
      promise = Model.create(data, createOptions);
    }

    return promise.then(result => {
      const sel = select(params, this.id);
      if (options.raw === false) {
        return result;
      }
      if (isArray) {
        return result.map(item => sel(item.toJSON()));
      }
      return sel(result.toJSON());
    }).catch(utils.errorHandler);
  }

  patch (id, data, params) {
    const where = Object.assign({}, filter(params.query || {}).query);
    const mapIds = page => page.data.map(current => current[this.id]);

    if (id !== null) {
      where[this.id] = id;
    }

    const options = Object.assign({raw: this.raw}, params.sequelize, { where });

    let Model = this.applyScope(params);

    // This is the best way to implement patch in sql, the other dialects 'should' use a transaction.
    if (Model.sequelize.options.dialect === 'postgres') {
      options.returning = true;
      return Model.update(omit(data, this.id), options)
            .then(results => {
              if (id === null) {
                return results[1];
              }

              if (!results[1].length) {
                throw new errors.NotFound(`No record found for id '${id}'`);
              }

              return results[1][0];
            })
            .then(select(params, this.id))
            .catch(utils.errorHandler);
    }

    // By default we will just query for the one id. For multi patch
    // we create a list of the ids of all items that will be changed
    // to re-query them after the update
    const ids = id === null ? this._find(params)
        .then(mapIds) : Promise.resolve([ id ]);

    return ids
      .then(idList => {
        // Create a new query that re-queries all ids that
        // were originally changed
        const findParams = Object.assign({}, params, {
          query: { [this.id]: { $in: idList } }
        });

        return Model.update(omit(data, this.id), options)
            .then(() => this._getOrFind(id, findParams));
      })
      .then(select(params, this.id))
      .catch(utils.errorHandler);
  }

  update (id, data, params) {
    const options = Object.assign({ raw: this.raw }, params.sequelize);

    if (Array.isArray(data)) {
      return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?'));
    }

    let Model = this.applyScope(params);

    // Force the {raw: false} option as the instance is needed to properly
    // update
    return Model.findById(id, { raw: false }).then(instance => {
      if (!instance) {
        throw new errors.NotFound(`No record found for id '${id}'`);
      }

      let copy = {};
      Object.keys(instance.toJSON()).forEach(key => {
        if (typeof data[key] === 'undefined') {
          copy[key] = null;
        } else {
          copy[key] = data[key];
        }
      });

      return instance.update(copy, options).then(instance => {
        if (options.raw === false) {
          return instance;
        }
        return instance.toJSON();
      });
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }

  remove (id, params) {
    const opts = Object.assign({ raw: this.raw }, params);
    return this._getOrFind(id, opts).then(data => {
      const where = Object.assign({}, filter(params.query || {}).query);

      if (id !== null) {
        where[this.id] = id;
      }

      const options = Object.assign({}, params.sequelize, { where });

      let Model = this.applyScope(params);

      return Model.destroy(options).then(() => data);
    })
    .then(select(params, this.id))
    .catch(utils.errorHandler);
  }
}

export default function init (options) {
  return new Service(options);
}

init.Service = Service;
<|endoftext|>