index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 rechoir = require('rechoir');
  9. const findCwd = require('./lib/find_cwd');
  10. const findConfig = require('./lib/find_config');
  11. const fileSearch = require('./lib/file_search');
  12. const parseOptions = require('./lib/parse_options');
  13. const silentRequire = require('./lib/silent_require');
  14. const buildConfigName = require('./lib/build_config_name');
  15. function Liftoff (opts) {
  16. EE.call(this);
  17. extend(this, parseOptions(opts));
  18. }
  19. util.inherits(Liftoff, EE);
  20. Liftoff.prototype.requireLocal = function (module, basedir) {
  21. try {
  22. var result = require(resolve.sync(module, {basedir: basedir}));
  23. this.emit('require', module, result);
  24. return result;
  25. } catch (e) {
  26. this.emit('requireFail', module, e);
  27. }
  28. };
  29. Liftoff.prototype.buildEnvironment = function (opts) {
  30. opts = opts || {};
  31. // get modules we want to preload
  32. var preload = opts.require || [];
  33. // ensure items to preload is an array
  34. if (!Array.isArray(preload)) {
  35. preload = [preload];
  36. }
  37. // make a copy of search paths that can be mutated for this run
  38. var searchPaths = this.searchPaths.slice();
  39. // calculate current cwd
  40. var cwd = findCwd(opts);
  41. // if cwd was provided explicitly, only use it for searching config
  42. if (opts.cwd) {
  43. searchPaths = [cwd];
  44. } else {
  45. // otherwise just search in cwd first
  46. searchPaths.unshift(cwd);
  47. }
  48. // calculate the regex to use for finding the config file
  49. var configNameSearch = buildConfigName({
  50. configName: this.configName,
  51. extensions: Object.keys(this.extensions)
  52. });
  53. // calculate configPath
  54. var configPath = findConfig({
  55. configNameSearch: configNameSearch,
  56. searchPaths: searchPaths,
  57. configPath: opts.configPath
  58. });
  59. // if we have a config path, save the directory it resides in.
  60. var configBase;
  61. if (configPath) {
  62. configBase = path.dirname(configPath);
  63. // if cwd wasn't provided explicitly, it should match configBase
  64. if (!opts.cwd) {
  65. cwd = configBase;
  66. }
  67. // resolve symlink if needed
  68. if (fs.lstatSync(configPath).isSymbolicLink()) {
  69. configPath = fs.realpathSync(configPath);
  70. }
  71. }
  72. // TODO: break this out into lib/
  73. // locate local module and package next to config or explicitly provided cwd
  74. var modulePath, modulePackage;
  75. try {
  76. var delim = (process.platform === 'win32' ? ';' : ':'),
  77. 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. // load any modules which were requested to be required
  98. if (preload.length) {
  99. // unique results first
  100. preload.filter(function (value, index, self) {
  101. return self.indexOf(value) === index;
  102. }).forEach(function (dep) {
  103. this.requireLocal(dep, findCwd(opts));
  104. }, this);
  105. }
  106. // use rechoir to autoload any required modules
  107. var autoloads;
  108. if (configPath) {
  109. autoloads = rechoir.prepare(this.extensions, configPath, cwd, true);
  110. if (autoloads instanceof Error) {
  111. autoloads = autoloads.failures;
  112. }
  113. if (Array.isArray(autoloads)) {
  114. autoloads.forEach(function (attempt) {
  115. if (attempt.error) {
  116. this.emit('requireFail', attempt.moduleName, attempt.error);
  117. } else {
  118. this.emit('require', attempt.moduleName, attempt.module);
  119. }
  120. }, this);
  121. }
  122. }
  123. return {
  124. cwd: cwd,
  125. require: preload,
  126. configNameSearch: configNameSearch,
  127. configPath: configPath,
  128. configBase: configBase,
  129. modulePath: modulePath,
  130. modulePackage: modulePackage || {}
  131. };
  132. };
  133. Liftoff.prototype.handleFlags = function (cb) {
  134. if (typeof this.v8flags === 'function') {
  135. this.v8flags(function (err, flags) {
  136. if (err) {
  137. cb(err);
  138. } else {
  139. cb(null, flags);
  140. }
  141. });
  142. } else {
  143. process.nextTick(function () {
  144. cb(null, this.v8flags);
  145. }.bind(this));
  146. }
  147. };
  148. Liftoff.prototype.launch = function (opts, fn) {
  149. if (typeof fn !== 'function') {
  150. throw new Error('You must provide a callback function.');
  151. }
  152. process.title = this.processTitle;
  153. var completion = opts.completion;
  154. if (completion && this.completions) {
  155. return this.completions(completion);
  156. }
  157. this.handleFlags(function (err, flags) {
  158. if (err) {
  159. throw err;
  160. } else {
  161. if (flags) {
  162. flaggedRespawn(flags, process.argv, function (ready, child) {
  163. if (child !== process) {
  164. this.emit('respawn', process.argv.filter(function (arg) {
  165. var flag = arg.split('=')[0];
  166. return flags.indexOf(flag) !== -1;
  167. }.bind(this)), child);
  168. }
  169. if (ready) {
  170. fn.call(this, this.buildEnvironment(opts));
  171. }
  172. }.bind(this));
  173. } else {
  174. fn.call(this, this.buildEnvironment(opts));
  175. }
  176. }
  177. }.bind(this));
  178. };
  179. module.exports = Liftoff;