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