1 const fs = require('fs');
2 const util = require('util');
3 const path = require('path');
4 const EE = require('events').EventEmitter;
6 const extend = require('extend');
7 const resolve = require('resolve');
8 const flaggedRespawn = require('flagged-respawn');
9 const isPlainObject = require('lodash.isplainobject');
10 const mapValues = require('lodash.mapvalues');
11 const fined = require('fined');
13 const findCwd = require('./lib/find_cwd');
14 const findConfig = require('./lib/find_config');
15 const fileSearch = require('./lib/file_search');
16 const parseOptions = require('./lib/parse_options');
17 const silentRequire = require('./lib/silent_require');
18 const buildConfigName = require('./lib/build_config_name');
19 const registerLoader = require('./lib/register_loader');
22 function Liftoff (opts) {
24 extend(this, parseOptions(opts));
26 util.inherits(Liftoff, EE);
28 Liftoff.prototype.requireLocal = function (module, basedir) {
30 var result = require(resolve.sync(module, {basedir: basedir}));
31 this.emit('require', module, result);
34 this.emit('requireFail', module, e);
38 Liftoff.prototype.buildEnvironment = function (opts) {
41 // get modules we want to preload
42 var preload = opts.require || [];
44 // ensure items to preload is an array
45 if (!Array.isArray(preload)) {
49 // make a copy of search paths that can be mutated for this run
50 var searchPaths = this.searchPaths.slice();
52 // calculate current cwd
53 var cwd = findCwd(opts);
55 // if cwd was provided explicitly, only use it for searching config
59 // otherwise just search in cwd first
60 searchPaths.unshift(cwd);
63 // calculate the regex to use for finding the config file
64 var configNameSearch = buildConfigName({
65 configName: this.configName,
66 extensions: Object.keys(this.extensions)
69 // calculate configPath
70 var configPath = findConfig({
71 configNameSearch: configNameSearch,
72 searchPaths: searchPaths,
73 configPath: opts.configPath
76 // if we have a config path, save the directory it resides in.
79 configBase = path.dirname(configPath);
80 // if cwd wasn't provided explicitly, it should match configBase
84 // resolve symlink if needed
85 if (fs.lstatSync(configPath).isSymbolicLink()) {
86 configPath = fs.realpathSync(configPath);
90 // TODO: break this out into lib/
91 // locate local module and package next to config or explicitly provided cwd
92 var modulePath, modulePackage;
94 var delim = (process.platform === 'win32' ? ';' : ':'),
95 paths = (process.env.NODE_PATH ? process.env.NODE_PATH.split(delim) : []);
96 modulePath = resolve.sync(this.moduleName, {basedir: configBase || cwd, paths: paths});
97 modulePackage = silentRequire(fileSearch('package.json', [modulePath]));
100 // if we have a configuration but we failed to find a local module, maybe
101 // we are developing against ourselves?
102 if (!modulePath && configPath) {
103 // check the package.json sibling to our config to see if its `name`
104 // matches the module we're looking for
105 var modulePackagePath = fileSearch('package.json', [configBase]);
106 modulePackage = silentRequire(modulePackagePath);
107 if (modulePackage && modulePackage.name === this.moduleName) {
108 // if it does, our module path is `main` inside package.json
109 modulePath = path.join(path.dirname(modulePackagePath), modulePackage.main || 'index.js');
112 // clear if we just required a package for some other project
117 // load any modules which were requested to be required
118 if (preload.length) {
119 // unique results first
120 preload.filter(function (value, index, self) {
121 return self.indexOf(value) === index;
122 }).forEach(function (dep) {
123 this.requireLocal(dep, findCwd(opts));
127 var exts = this.extensions;
128 var eventEmitter = this;
129 registerLoader(eventEmitter, exts, configPath, cwd);
131 var configFiles = {};
132 if (isPlainObject(this.configFiles)) {
133 var notfound = { path: null };
134 configFiles = mapValues(this.configFiles, function(prop, name) {
135 var defaultObj = { name: name, cwd: cwd, extensions: exts };
136 return mapValues(prop, function(pathObj) {
137 var found = fined(pathObj, defaultObj) || notfound;
138 if (isPlainObject(found.extension)) {
139 registerLoader(eventEmitter, found.extension, found.path, cwd);
149 configNameSearch: configNameSearch,
150 configPath: configPath,
151 configBase: configBase,
152 modulePath: modulePath,
153 modulePackage: modulePackage || {},
154 configFiles: configFiles
158 Liftoff.prototype.handleFlags = function (cb) {
159 if (typeof this.v8flags === 'function') {
160 this.v8flags(function (err, flags) {
168 process.nextTick(function () {
169 cb(null, this.v8flags);
174 Liftoff.prototype.launch = function (opts, fn) {
175 if (typeof fn !== 'function') {
176 throw new Error('You must provide a callback function.');
178 process.title = this.processTitle;
180 var completion = opts.completion;
181 if (completion && this.completions) {
182 return this.completions(completion);
185 this.handleFlags(function (err, flags) {
190 flaggedRespawn(flags, process.argv, function (ready, child) {
191 if (child !== process) {
192 this.emit('respawn', process.argv.filter(function (arg) {
193 var flag = arg.split('=')[0];
194 return flags.indexOf(flag) !== -1;
195 }.bind(this)), child);
198 fn.call(this, this.buildEnvironment(opts));
202 fn.call(this, this.buildEnvironment(opts));
210 module.exports = Liftoff;