版博士V2.0程序
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

308 строки
8.5 KiB

  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').isPlainObject;
  8. var mapValues = require('object.map');
  9. var fined = require('fined');
  10. var findCwd = require('./lib/find_cwd');
  11. var arrayFind = require('./lib/array_find');
  12. var findConfig = require('./lib/find_config');
  13. var fileSearch = require('./lib/file_search');
  14. var needsLookup = require('./lib/needs_lookup');
  15. var parseOptions = require('./lib/parse_options');
  16. var silentRequire = require('./lib/silent_require');
  17. var buildConfigName = require('./lib/build_config_name');
  18. var registerLoader = require('./lib/register_loader');
  19. var getNodeFlags = require('./lib/get_node_flags');
  20. function Liftoff(opts) {
  21. EE.call(this);
  22. extend(this, parseOptions(opts));
  23. }
  24. util.inherits(Liftoff, EE);
  25. Liftoff.prototype.requireLocal = function (moduleName, basedir) {
  26. try {
  27. this.emit('preload:before', moduleName);
  28. var result = require(resolve.sync(moduleName, { basedir: basedir }));
  29. this.emit('preload:success', moduleName, result);
  30. return result;
  31. } catch (e) {
  32. this.emit('preload:failure', moduleName, e);
  33. }
  34. };
  35. Liftoff.prototype.buildEnvironment = function (opts) {
  36. opts = opts || {};
  37. // get modules we want to preload
  38. var preload = opts.preload || [];
  39. // ensure items to preload is an array
  40. if (!Array.isArray(preload)) {
  41. preload = [preload];
  42. }
  43. // make a copy of search paths that can be mutated for this run
  44. var searchPaths = this.searchPaths.slice();
  45. // calculate current cwd
  46. var cwd = findCwd(opts);
  47. var exts = this.extensions;
  48. var eventEmitter = this;
  49. function findAndRegisterLoader(pathObj, defaultObj) {
  50. var found = fined(pathObj, defaultObj);
  51. if (!found) {
  52. return;
  53. }
  54. if (isPlainObject(found.extension)) {
  55. registerLoader(eventEmitter, found.extension, found.path, cwd);
  56. }
  57. return found.path;
  58. }
  59. function getModulePath(cwd, xtends) {
  60. // If relative, we need to use fined to look up the file. If not, assume a node_module
  61. if (needsLookup(xtends)) {
  62. var defaultObj = { cwd: cwd, extensions: exts };
  63. // Using `xtends` like this should allow people to use a string or any object that fined accepts
  64. var foundPath = findAndRegisterLoader(xtends, defaultObj);
  65. if (!foundPath) {
  66. var name;
  67. if (typeof xtends === 'string') {
  68. name = xtends;
  69. } else {
  70. name = xtends.path || xtends.name;
  71. }
  72. var msg = 'Unable to locate one of your extends.';
  73. if (name) {
  74. msg += ' Looking for file: ' + path.resolve(cwd, name);
  75. }
  76. throw new Error(msg);
  77. }
  78. return foundPath;
  79. }
  80. return xtends;
  81. }
  82. var visited = {};
  83. function loadConfig(cwd, xtends, preferred) {
  84. var configFilePath = getModulePath(cwd, xtends);
  85. if (visited[configFilePath]) {
  86. throw new Error(
  87. 'We encountered a circular extend for file: ' +
  88. configFilePath +
  89. '. Please remove the recursive extends.'
  90. );
  91. }
  92. var configFile;
  93. try {
  94. configFile = require(configFilePath);
  95. } catch (e) {
  96. // TODO: Consider surfacing the `require` error
  97. throw new Error(
  98. 'Encountered error when loading config file: ' + configFilePath
  99. );
  100. }
  101. visited[configFilePath] = true;
  102. if (configFile && configFile.extends) {
  103. var nextCwd = path.dirname(configFilePath);
  104. return loadConfig(nextCwd, configFile.extends, configFile);
  105. }
  106. // Always extend into an empty object so we can call `delete` on `config.extends`
  107. var config = extend(true /* deep */, {}, configFile, preferred);
  108. delete config.extends;
  109. return config;
  110. }
  111. var configFiles = {};
  112. if (isPlainObject(this.configFiles)) {
  113. configFiles = mapValues(this.configFiles, function (searchPaths, fileStem) {
  114. var defaultObj = { name: fileStem, cwd: cwd, extensions: exts };
  115. var foundPath = arrayFind(searchPaths, function (pathObj) {
  116. return findAndRegisterLoader(pathObj, defaultObj);
  117. });
  118. return foundPath;
  119. });
  120. }
  121. var config = mapValues(configFiles, function (startingLocation) {
  122. var defaultConfig = {};
  123. if (!startingLocation) {
  124. return defaultConfig;
  125. }
  126. return loadConfig(cwd, startingLocation, defaultConfig);
  127. });
  128. // if cwd was provided explicitly, only use it for searching config
  129. if (opts.cwd) {
  130. searchPaths = [cwd];
  131. } else {
  132. // otherwise just search in cwd first
  133. searchPaths.unshift(cwd);
  134. }
  135. // calculate the regex to use for finding the config file
  136. var configNameSearch = buildConfigName({
  137. configName: this.configName,
  138. extensions: Object.keys(this.extensions),
  139. });
  140. // calculate configPath
  141. var configPath = findConfig({
  142. configNameSearch: configNameSearch,
  143. searchPaths: searchPaths,
  144. configPath: opts.configPath,
  145. });
  146. // if we have a config path, save the directory it resides in.
  147. var configBase;
  148. if (configPath) {
  149. configBase = path.dirname(configPath);
  150. // if cwd wasn't provided explicitly, it should match configBase
  151. if (!opts.cwd) {
  152. cwd = configBase;
  153. }
  154. }
  155. // TODO: break this out into lib/
  156. // locate local module and package next to config or explicitly provided cwd
  157. var modulePath;
  158. var modulePackage;
  159. try {
  160. var delim = path.delimiter;
  161. var paths = process.env.NODE_PATH ? process.env.NODE_PATH.split(delim) : [];
  162. modulePath = resolve.sync(this.moduleName, {
  163. basedir: configBase || cwd,
  164. paths: paths,
  165. });
  166. modulePackage = silentRequire(fileSearch('package.json', [modulePath]));
  167. } catch (e) {}
  168. // if we have a configuration but we failed to find a local module, maybe
  169. // we are developing against ourselves?
  170. if (!modulePath && configPath) {
  171. // check the package.json sibling to our config to see if its `name`
  172. // matches the module we're looking for
  173. var modulePackagePath = fileSearch('package.json', [configBase]);
  174. modulePackage = silentRequire(modulePackagePath);
  175. if (modulePackage && modulePackage.name === this.moduleName) {
  176. // if it does, our module path is `main` inside package.json
  177. modulePath = path.join(
  178. path.dirname(modulePackagePath),
  179. modulePackage.main || 'index.js'
  180. );
  181. cwd = configBase;
  182. } else {
  183. // clear if we just required a package for some other project
  184. modulePackage = {};
  185. }
  186. }
  187. return {
  188. cwd: cwd,
  189. preload: preload,
  190. completion: opts.completion,
  191. configNameSearch: configNameSearch,
  192. configPath: configPath,
  193. configBase: configBase,
  194. modulePath: modulePath,
  195. modulePackage: modulePackage || {},
  196. configFiles: configFiles,
  197. config: config,
  198. };
  199. };
  200. Liftoff.prototype.handleFlags = function (cb) {
  201. if (typeof this.v8flags === 'function') {
  202. this.v8flags(function (err, flags) {
  203. if (err) {
  204. cb(err);
  205. } else {
  206. cb(null, flags);
  207. }
  208. });
  209. } else {
  210. process.nextTick(
  211. function () {
  212. cb(null, this.v8flags);
  213. }.bind(this)
  214. );
  215. }
  216. };
  217. Liftoff.prototype.prepare = function (opts, fn) {
  218. if (typeof fn !== 'function') {
  219. throw new Error('You must provide a callback function.');
  220. }
  221. process.title = this.processTitle;
  222. var env = this.buildEnvironment(opts);
  223. fn.call(this, env);
  224. };
  225. Liftoff.prototype.execute = function (env, forcedFlags, fn) {
  226. var completion = env.completion;
  227. if (completion && this.completions) {
  228. return this.completions(completion);
  229. }
  230. if (typeof forcedFlags === 'function') {
  231. fn = forcedFlags;
  232. forcedFlags = undefined;
  233. }
  234. if (typeof fn !== 'function') {
  235. throw new Error('You must provide a callback function.');
  236. }
  237. this.handleFlags(
  238. function (err, flags) {
  239. if (err) {
  240. throw err;
  241. }
  242. flags = flags || [];
  243. flaggedRespawn(flags, process.argv, forcedFlags, execute.bind(this));
  244. function execute(ready, child, argv) {
  245. if (child !== process) {
  246. var execArgv = getNodeFlags.fromReorderedArgv(argv);
  247. this.emit('respawn', execArgv, child);
  248. }
  249. if (ready) {
  250. preloadModules(this, env);
  251. registerLoader(this, this.extensions, env.configPath, env.cwd);
  252. fn.call(this, env, argv);
  253. }
  254. }
  255. }.bind(this)
  256. );
  257. };
  258. function preloadModules(inst, env) {
  259. var basedir = env.cwd;
  260. env.preload.filter(toUnique).forEach(function (module) {
  261. inst.requireLocal(module, basedir);
  262. });
  263. }
  264. function toUnique(elem, index, array) {
  265. return array.indexOf(elem) === index;
  266. }
  267. module.exports = Liftoff;