index.js 6.1 KB

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