UNPKG

6.86 kBJavaScriptView Raw
1const path = require('path');
2const fs = require('fs');
3const existsSync = fs.existsSync;
4const utils = require('../utils');
5
6module.exports = exec;
7module.exports.expandScript = expandScript;
8
9/**
10 * Reads the cwd/package.json file and looks to see if it can load a script
11 * and possibly an exec first from package.main, then package.start.
12 *
13 * @return {Object} exec & script if found
14 */
15function execFromPackage() {
16 // doing a try/catch because we can't use the path.exist callback pattern
17 // or we could, but the code would get messy, so this will do exactly
18 // what we're after - if the file doesn't exist, it'll throw.
19 try {
20 // note: this isn't nodemon's package, it's the user's cwd package
21 var pkg = require(path.join(process.cwd(), 'package.json'));
22 if (pkg.main !== undefined) {
23 // no app found to run - so give them a tip and get the feck out
24 return { exec: null, script: pkg.main };
25 }
26
27 if (pkg.scripts && pkg.scripts.start) {
28 return { exec: pkg.scripts.start };
29 }
30 } catch (e) { }
31
32 return null;
33}
34
35function replace(map, str) {
36 var re = new RegExp('{{(' + Object.keys(map).join('|') + ')}}', 'g');
37 return str.replace(re, function (all, m) {
38 return map[m] || all || '';
39 });
40}
41
42function expandScript(script, ext) {
43 if (!ext) {
44 ext = '.js';
45 }
46 if (script.indexOf(ext) !== -1) {
47 return script;
48 }
49
50 if (existsSync(path.resolve(script))) {
51 return script;
52 }
53
54 if (existsSync(path.resolve(script + ext))) {
55 return script + ext;
56 }
57
58 return script;
59}
60
61/**
62 * Discovers all the options required to run the script
63 * and if a custom exec has been passed in, then it will
64 * also try to work out what extensions to monitor and
65 * whether there's a special way of running that script.
66 *
67 * @param {Object} nodemonOptions
68 * @param {Object} execMap
69 * @return {Object} new and updated version of nodemonOptions
70 */
71function exec(nodemonOptions, execMap) {
72 if (!execMap) {
73 execMap = {};
74 }
75
76 var options = utils.clone(nodemonOptions || {});
77 var script;
78
79 // if there's no script passed, try to get it from the first argument
80 if (!options.script && (options.args || []).length) {
81 script = expandScript(options.args[0],
82 options.ext && ('.' + (options.ext || 'js').split(',')[0]));
83
84 // if the script was found, shift it off our args
85 if (script !== options.args[0]) {
86 options.script = script;
87 options.args.shift();
88 }
89 }
90
91 // if there's no exec found yet, then try to read it from the local
92 // package.json this logic used to sit in the cli/parse, but actually the cli
93 // should be parsed first, then the user options (via nodemon.json) then
94 // finally default down to pot shots at the directory via package.json
95 if (!options.exec && !options.script) {
96 var found = execFromPackage();
97 if (found !== null) {
98 if (found.exec) {
99 options.exec = found.exec;
100 }
101 if (!options.script) {
102 options.script = found.script;
103 }
104 if (Array.isArray(options.args) &&
105 options.scriptPosition === null) {
106 options.scriptPosition = options.args.length;
107 }
108 }
109 }
110
111 // var options = utils.clone(nodemonOptions || {});
112 script = path.basename(options.script || '');
113
114 var scriptExt = path.extname(script).slice(1);
115
116 var extension = options.ext;
117 if (extension === undefined) {
118 var isJS = scriptExt === 'js' || scriptExt === 'mjs';
119 extension = (isJS || !scriptExt) ? 'js,mjs' : scriptExt;
120 extension += ',json'; // Always watch JSON files
121 }
122
123 var execDefined = !!options.exec;
124
125 // allows the user to simplify cli usage:
126 // https://github.com/remy/nodemon/issues/195
127 // but always give preference to the user defined argument
128 if (!options.exec && execMap[scriptExt] !== undefined) {
129 options.exec = execMap[scriptExt];
130 execDefined = true;
131 }
132
133 options.execArgs = nodemonOptions.execArgs || [];
134
135 if (Array.isArray(options.exec)) {
136 options.execArgs = options.exec;
137 options.exec = options.execArgs.shift();
138 }
139
140 if (options.exec === undefined) {
141 options.exec = 'node';
142 } else {
143 // allow variable substitution for {{filename}} and {{pwd}}
144 var substitution = replace.bind(null, {
145 filename: options.script,
146 pwd: process.cwd(),
147 });
148
149 var newExec = substitution(options.exec);
150 if (newExec !== options.exec &&
151 options.exec.indexOf('{{filename}}') !== -1) {
152 options.script = null;
153 }
154 options.exec = newExec;
155
156 var newExecArgs = options.execArgs.map(substitution);
157 if (newExecArgs.join('') !== options.execArgs.join('')) {
158 options.execArgs = newExecArgs;
159 delete options.script;
160 }
161 }
162
163
164 if (options.exec === 'node' && options.nodeArgs && options.nodeArgs.length) {
165 options.execArgs = options.execArgs.concat(options.nodeArgs);
166 }
167
168 // note: indexOf('coffee') handles both .coffee and .litcoffee
169 if (!execDefined && options.exec === 'node' &&
170 scriptExt.indexOf('coffee') !== -1) {
171 options.exec = 'coffee';
172
173 // we need to get execArgs set before the script
174 // for example, in `nodemon --debug my-script.coffee --my-flag`, debug is an
175 // execArg, while my-flag is a script arg
176 var leadingArgs = (options.args || []).splice(0, options.scriptPosition);
177 options.execArgs = options.execArgs.concat(leadingArgs);
178 options.scriptPosition = 0;
179
180 if (options.execArgs.length > 0) {
181 // because this is the coffee executable, we need to combine the exec args
182 // into a single argument after the nodejs flag
183 options.execArgs = ['--nodejs', options.execArgs.join(' ')];
184 }
185 }
186
187 if (options.exec === 'coffee') {
188 // don't override user specified extension tracking
189 if (options.ext === undefined) {
190 if (extension) { extension += ','; }
191 extension += 'coffee,litcoffee';
192 }
193
194 // because windows can't find 'coffee', it needs the real file 'coffee.cmd'
195 if (utils.isWindows) {
196 options.exec += '.cmd';
197 }
198 }
199
200 // allow users to make a mistake on the extension to monitor
201 // converts .js, jade => js,jade
202 // BIG NOTE: user can't do this: nodemon -e *.js
203 // because the terminal will automatically expand the glob against
204 // the file system :(
205 extension = (extension.match(/[^,*\s]+/g) || [])
206 .map(ext => ext.replace(/^\./, ''))
207 .join(',');
208
209 options.ext = extension;
210
211 if (options.script) {
212 options.script = expandScript(options.script,
213 extension && ('.' + extension.split(',')[0]));
214 }
215
216 options.env = {};
217 // make sure it's an object (and since we don't have )
218 if (({}).toString.apply(nodemonOptions.env) === '[object Object]') {
219 options.env = utils.clone(nodemonOptions.env);
220 } else if (nodemonOptions.env !== undefined) {
221 throw new Error('nodemon env values must be an object: { PORT: 8000 }');
222 }
223
224 return options;
225}