text: const fs = require('fs');
  const util = require('util');
  const path = require('path');
  const EE = require('events').EventEmitter;
  
  const extend = require('extend');
  const resolve = require('resolve');
  const flaggedRespawn = require('flagged-respawn');
  
  const findCwd = require('./lib/find_cwd');
  const findConfig = require('./lib/find_config');
  const fileSearch = require('./lib/file_search');
  const parseOptions = require('./lib/parse_options');
  const silentRequire = require('./lib/silent_require');
  const buildConfigName = require('./lib/build_config_name');
  
  function Liftoff (opts) {
    EE.call(this);
    extend(this, parseOptions(opts));
  }
  util.inherits(Liftoff, EE);
  
  Liftoff.prototype.requireLocal = function (module, basedir) {
    try {
      var result = require(resolve.sync(module, {basedir: basedir}));
      this.emit('require', module, result);
      return result;
    } catch (e) {
      this.emit('requireFail', module, e);
    }
  };
  
  Liftoff.prototype.buildEnvironment = function (opts) {
    opts = opts||{};
  
    // get modules we want to preload
    var preload = opts.require||[];
  
    // ensure items to preload is an array
    if (!Array.isArray(preload)) {
      preload = [preload];
    }
  
    // make a copy of search paths that can be mutated for this run
    var searchPaths = this.searchPaths.slice();
  
    // calculate current cwd
    var cwd = findCwd(opts);
  
    // if cwd was provided explicitly, only use it for searching config
    if (opts.cwd) {
      searchPaths = [cwd];
    } else {
      // otherwise just search in cwd first
      searchPaths.unshift(cwd);
    }
  
    // calculate the regex to use for finding the config file
    var configNameSearch = buildConfigName({
      configName: this.configName,
      extensions: Object.keys(this.extensions)
    });
  
    // calculate configPath
    var configPath = findConfig({
      configNameSearch: configNameSearch,
      searchPaths: searchPaths,
      configPath: opts.configPath
    });
  
    // if we have a config path, save the directory it resides in.
    var configBase;
    if (configPath) {
      configBase = path.dirname(configPath);
      // if cwd wasn't provided explicitly, it should match configBase
      if (!opts.cwd) {
        cwd = configBase;
      }
      // resolve symlink if needed
      if(fs.lstatSync(configPath).isSymbolicLink()) {
        configPath = fs.realpathSync(configPath);
      }
    }
  
    // TODO: break this out into lib/
    // locate local module and package next to config or explicitly provided cwd
    var modulePath, modulePackage;
    try {
      modulePath = resolve.sync(this.moduleName, {basedir: configBase || cwd});
      modulePackage = silentRequire(fileSearch('package.json', [modulePath]));
    } catch (e) {}
  
    // if we have a configuration but we failed to find a local module, maybe
    // we are developing against ourselves?
    if (!modulePath && configPath) {
      // check the package.json sibling to our config to see if its `name`
      // matches the module we're looking for
      modulePackage = silentRequire(fileSearch('package.json', [configBase]));
      if (modulePackage && modulePackage.name === this.moduleName) {
        // if it does, our module path is `main` inside package.json
        modulePath = path.join(configBase, modulePackage.main||'index.js');
        cwd = configBase;
      } else {
        // clear if we just required a package for some other project
        modulePackage = {};
      }
    }
  
    // get extension of config name, taking anything after the -first- dot
    var configExtension = /(\.[^\/]*)?$/.exec(configPath)[0];
  
    // preload module needed for config if any has been specified.
    var requireForExtension = this.extensions[configExtension];
    if (requireForExtension) {
      preload.push(requireForExtension);
    }
  
    // preload modules, if any
    if (preload.length) {
      // unique results first
      preload.filter(function (value, index, self) {
        return self.indexOf(value) === index;
      }).forEach(function (dep) {
        this.requireLocal(dep, findCwd(opts));
      }, this);
    }
  
    return {
      cwd: cwd,
      require: preload,
      configNameSearch: configNameSearch,
      configPath: configPath,
      configBase: configBase,
      modulePath: modulePath,
      modulePackage: modulePackage||{}
    };
  };
  
  Liftoff.prototype.launch = function (opts, fn) {
    if (typeof fn !== 'function') {
      throw new Error('You must provide a callback function.');
    }
  
    process.title = this.processTitle;
  
    var completion = opts.completion;
    if (completion && this.completions) {
      return this.completions(completion);
    }
  
    if (this.nodeFlags) {
      flaggedRespawn(this.nodeFlags, process.argv, function (ready, child) {
        if (child !== process) {
          this.emit('respawn', process.argv.filter(function (flag) {
            return this.nodeFlags.indexOf(flag) !== -1;
          }.bind(this)), child);
        }
        if (ready) {
          fn.call(this, this.buildEnvironment(opts));
        }
      }.bind(this));
    } else {
      fn.call(this, this.buildEnvironment(opts));
    }
  
  };
  
  module.exports = Liftoff;
  