Source: service_control/index.js

/**
 * @module serviceControl
 */
// @flow

const _ = require('lodash');
const Promise = require('bluebird');
const universe = require('../universe');

const serviceControlNS = universe.namespaceFactory('_cukelib');
const { get, set, log3, unset, hasKey, getCukeContext, initializeWith } = serviceControlNS;

const listServices = (serviceRoot: string, depth: number) => {
  let serviceList = [];
  _.forIn(get(serviceRoot ? `_services.${serviceRoot}` : '_services'), (candidate, name) => {
    const namePath = serviceRoot ? `${serviceRoot}.${name}` : name;
    if (candidate.cukeContext) {
      serviceList.push(namePath);
    } else if (depth < 10) {
      const deepServiceList = listServices(namePath, depth + 1);
      serviceList = serviceList.concat(deepServiceList);
    }
  });
  return serviceList;
};

const serviceControl =
module.exports = {
  serviceControlNS,

  addBoilerPlate(prefix: string, serviceObject: Object) {
    return _.defaults(serviceObject, {
      initialize() {
        return serviceControl.initialize.call(this);
      },
      getService(name) {
        return serviceControl.getService(`${prefix}.${name}`);
      },
    });
  },

  initialize() {
    if (hasKey('_services')) return;
    initializeWith.call(this, { _services: {} });

    const cukeContextKiller = (cukeContext) => {
      const killTheseServices = listServices('', 0)
        .filter((namePath) => get(`_services.${namePath}`).cukeContext === cukeContext);
      log3('log3', 'killTheseServices', killTheseServices);
      return Promise.map(killTheseServices, (name) => serviceControl.stopService(name));
    };

    this.After(cukeContextKiller.bind(null, 'scenario'));
    this.registerHandler('AfterFeature', cukeContextKiller.bind(null, 'feature'));
    this.registerHandler('AfterFeatures', cukeContextKiller.bind(null, 'universe'));
  },

  stopService(name: string) {
    log3('log3', 'serviceControl/stopService', name);
    const service = get(`_services.${name}`);
    if (!service) return Promise.resolve(`no service for ${name}`);
    unset(`_services.${name}`);
    if (service.stop) {
      return service.stop();
    } else if ((service.proc || {}).kill) {
      service.removeListeners();
      service.proc.kill('SIGTERM');
      return service.exitPromise;
    }
    throw new Error(`Don't know how to stop service "${name}"`);
  },

  /**
   * @param {string} name service name
   *
   * @returns {Object} universe service object root
   */
  getService(name: string) {
    return get(`_services.${name}`);
  },

  launchService(name: string, start: () => Object) {
    log3('log3', 'serviceControl/launchService', name);
    if (!get('_services')) {
      throw new Error('tried to launchService before service_control was initialized');
    }
    return serviceControl.stopService(name)
    .then(start)
    .then((service) => {
      if (!service.stop || !_.isFunction(service.stop)) {
        throw new Error(`service '${name}' is missing a stop function`);
      }
      set(`_services.${name}`, service);
      service.name = name; // eslint-disable-line no-param-reassign
      service.cukeContext = getCukeContext(); // eslint-disable-line no-param-reassign
      return service;
    });
  },
};