index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. var util = require('util');
  2. var path = require('path');
  3. var EE = require('events').EventEmitter;
  4. var extend = require('extend');
  5. var resolve = require('resolve');
  6. var flaggedRespawn = require('flagged-respawn');
  7. var isPlainObject = require('is-plain-object');
  8. var mapValues = require('object.map');
  9. var fined = require('fined');
  10. var findCwd = require('./lib/find_cwd');
  11. var findConfig = require('./lib/find_config');
  12. var fileSearch = require('./lib/file_search');
  13. var parseOptions = require('./lib/parse_options');
  14. var silentRequire = require('./lib/silent_require');
  15. var buildConfigName = require('./lib/build_config_name');
  16. var registerLoader = require('./lib/register_loader');
  17. var getNodeFlags = require('./lib/get_node_flags');
  18. function Liftoff(opts) {
  19. EE.call(this);
  20. extend(this, parseOptions(opts));
  21. }
  22. util.inherits(Liftoff, EE);
  23. Liftoff.prototype.requireLocal = function(module, basedir) {
  24. try {
  25. var result = require(resolve.sync(module, { basedir: basedir }));
  26. this.emit('require', module, result);
  27. return result;
  28. } catch (e) {
  29. this.emit('requireFail', module, e);
  30. }
  31. };
  32. Liftoff.prototype.buildEnvironment = function(opts) {
  33. opts = opts || {};
  34. // get modules we want to preload
  35. var preload = opts.require || [];
  36. // ensure items to preload is an array
  37. if (!Array.isArray(preload)) {
  38. preload = [preload];
  39. }
  40. // make a copy of search paths that can be mutated for this run
  41. var searchPaths = this.searchPaths.slice();
  42. // calculate current cwd
  43. var cwd = findCwd(opts);
  44. // if cwd was provided explicitly, only use it for searching config
  45. if (opts.cwd) {
  46. searchPaths = [cwd];
  47. } else {
  48. // otherwise just search in cwd first
  49. searchPaths.unshift(cwd);
  50. }
  51. // calculate the regex to use for finding the config file
  52. var configNameSearch = buildConfigName({
  53. configName: this.configName,
  54. extensions: Object.keys(this.extensions),
  55. });
  56. // calculate configPath
  57. var configPath = findConfig({
  58. configNameSearch: configNameSearch,
  59. searchPaths: searchPaths,
  60. configPath: opts.configPath,
  61. });
  62. // if we have a config path, save the directory it resides in.
  63. var configBase;
  64. if (configPath) {
  65. configBase = path.dirname(configPath);
  66. // if cwd wasn't provided explicitly, it should match configBase
  67. if (!opts.cwd) {
  68. cwd = configBase;
  69. }
  70. }
  71. // TODO: break this out into lib/
  72. // locate local module and package next to config or explicitly provided cwd
  73. /* eslint one-var: 0 */
  74. var modulePath, modulePackage;
  75. try {
  76. var delim = path.delimiter;
  77. var paths = (process.env.NODE_PATH ? process.env.NODE_PATH.split(delim) : []);
  78. modulePath = resolve.sync(this.moduleName, { basedir: configBase || cwd, paths: paths });
  79. modulePackage = silentRequire(fileSearch('package.json', [modulePath]));
  80. } catch (e) {}
  81. // if we have a configuration but we failed to find a local module, maybe
  82. // we are developing against ourselves?
  83. if (!modulePath && configPath) {
  84. // check the package.json sibling to our config to see if its `name`
  85. // matches the module we're looking for
  86. var modulePackagePath = fileSearch('package.json', [configBase]);
  87. modulePackage = silentRequire(modulePackagePath);
  88. if (modulePackage && modulePackage.name === this.moduleName) {
  89. // if it does, our module path is `main` inside package.json
  90. modulePath = path.join(path.dirname(modulePackagePath), modulePackage.main || 'index.js');
  91. cwd = configBase;
  92. } else {
  93. // clear if we just required a package for some other project
  94. modulePackage = {};
  95. }
  96. }
  97. var exts = this.extensions;
  98. var eventEmitter = this;
  99. var configFiles = {};
  100. if (isPlainObject(this.configFiles)) {
  101. var notfound = { path: null };
  102. configFiles = mapValues(this.configFiles, function(prop, name) {
  103. var defaultObj = { name: name, cwd: cwd, extensions: exts };
  104. return mapValues(prop, function(pathObj) {
  105. var found = fined(pathObj, defaultObj) || notfound;
  106. if (isPlainObject(found.extension)) {
  107. registerLoader(eventEmitter, found.extension, found.path, cwd);
  108. }
  109. return found.path;
  110. });
  111. });
  112. }
  113. return {
  114. cwd: cwd,
  115. require: preload,
  116. configNameSearch: configNameSearch,
  117. configPath: configPath,
  118. configBase: configBase,
  119. modulePath: modulePath,
  120. modulePackage: modulePackage || {},
  121. configFiles: configFiles,
  122. };
  123. };
  124. Liftoff.prototype.handleFlags = function(cb) {
  125. if (typeof this.v8flags === 'function') {
  126. this.v8flags(function(err, flags) {
  127. if (err) {
  128. cb(err);
  129. } else {
  130. cb(null, flags);
  131. }
  132. });
  133. } else {
  134. process.nextTick(function() {
  135. cb(null, this.v8flags);
  136. }.bind(this));
  137. }
  138. };
  139. Liftoff.prototype.prepare = function(opts, fn) {
  140. if (typeof fn !== 'function') {
  141. throw new Error('You must provide a callback function.');
  142. }
  143. process.title = this.processTitle;
  144. var completion = opts.completion;
  145. if (completion && this.completions) {
  146. return this.completions(completion);
  147. }
  148. var env = this.buildEnvironment(opts);
  149. fn.call(this, env);
  150. };
  151. Liftoff.prototype.execute = function(env, forcedFlags, fn) {
  152. if (typeof forcedFlags === 'function') {
  153. fn = forcedFlags;
  154. forcedFlags = undefined;
  155. }
  156. if (typeof fn !== 'function') {
  157. throw new Error('You must provide a callback function.');
  158. }
  159. this.handleFlags(function(err, flags) {
  160. if (err) {
  161. throw err;
  162. }
  163. flags = flags || [];
  164. flaggedRespawn(flags, process.argv, forcedFlags, execute.bind(this));
  165. function execute(ready, child, argv) {
  166. if (child !== process) {
  167. var execArgv = getNodeFlags.fromReorderedArgv(argv);
  168. this.emit('respawn', execArgv, child);
  169. }
  170. if (ready) {
  171. preloadModules(this, env);
  172. registerLoader(this, this.extensions, env.configPath, env.cwd);
  173. fn.call(this, env, argv);
  174. }
  175. }
  176. }.bind(this));
  177. };
  178. Liftoff.prototype.launch = function(opts, fn) {
  179. if (typeof fn !== 'function') {
  180. throw new Error('You must provide a callback function.');
  181. }
  182. var self = this;
  183. self.prepare(opts, function(env) {
  184. var forcedFlags = getNodeFlags.arrayOrFunction(opts.forcedFlags, env);
  185. self.execute(env, forcedFlags, fn);
  186. });
  187. };
  188. function preloadModules(inst, env) {
  189. var basedir = env.cwd;
  190. env.require.filter(toUnique).forEach(function(module) {
  191. inst.requireLocal(module, basedir);
  192. });
  193. }
  194. function toUnique(elem, index, array) {
  195. return array.indexOf(elem) === index;
  196. }
  197. module.exports = Liftoff;