1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | var os = require('os');
|
6 | var fs = require('fs');
|
7 | var glob = require('glob');
|
8 | var shell = require('..');
|
9 |
|
10 | var shellMethods = Object.create(shell);
|
11 |
|
12 | exports.extend = Object.assign;
|
13 |
|
14 |
|
15 | var isElectron = Boolean(process.versions.electron);
|
16 |
|
17 |
|
18 | var DEFAULT_CONFIG = {
|
19 | fatal: false,
|
20 | globOptions: {},
|
21 | maxdepth: 255,
|
22 | noglob: false,
|
23 | silent: false,
|
24 | verbose: false,
|
25 | execPath: null,
|
26 | bufLength: 64 * 1024,
|
27 | };
|
28 |
|
29 | var config = {
|
30 | reset: function () {
|
31 | Object.assign(this, DEFAULT_CONFIG);
|
32 | if (!isElectron) {
|
33 | this.execPath = process.execPath;
|
34 | }
|
35 | },
|
36 | resetForTesting: function () {
|
37 | this.reset();
|
38 | this.silent = true;
|
39 | },
|
40 | };
|
41 |
|
42 | config.reset();
|
43 | exports.config = config;
|
44 |
|
45 | var state = {
|
46 | error: null,
|
47 | errorCode: 0,
|
48 | currentCmd: 'shell.js',
|
49 | tempDir: null,
|
50 | };
|
51 | exports.state = state;
|
52 |
|
53 | delete process.env.OLDPWD;
|
54 |
|
55 |
|
56 | function isObject(a) {
|
57 | return typeof a === 'object' && a !== null;
|
58 | }
|
59 | exports.isObject = isObject;
|
60 |
|
61 | function log() {
|
62 |
|
63 | if (!config.silent) {
|
64 | console.error.apply(console, arguments);
|
65 | }
|
66 | }
|
67 | exports.log = log;
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | function convertErrorOutput(msg) {
|
73 | if (typeof msg !== 'string') {
|
74 | throw new TypeError('input must be a string');
|
75 | }
|
76 | return msg.replace(/\\/g, '/');
|
77 | }
|
78 | exports.convertErrorOutput = convertErrorOutput;
|
79 |
|
80 |
|
81 | function error(msg, _code, options) {
|
82 |
|
83 | if (typeof msg !== 'string') throw new Error('msg must be a string');
|
84 |
|
85 | var DEFAULT_OPTIONS = {
|
86 | continue: false,
|
87 | code: 1,
|
88 | prefix: state.currentCmd + ': ',
|
89 | silent: false,
|
90 | };
|
91 |
|
92 | if (typeof _code === 'number' && isObject(options)) {
|
93 | options.code = _code;
|
94 | } else if (isObject(_code)) {
|
95 | options = _code;
|
96 | } else if (typeof _code === 'number') {
|
97 | options = { code: _code };
|
98 | } else if (typeof _code !== 'number') {
|
99 | options = {};
|
100 | }
|
101 | options = Object.assign({}, DEFAULT_OPTIONS, options);
|
102 |
|
103 | if (!state.errorCode) state.errorCode = options.code;
|
104 |
|
105 | var logEntry = convertErrorOutput(options.prefix + msg);
|
106 | state.error = state.error ? state.error + '\n' : '';
|
107 | state.error += logEntry;
|
108 |
|
109 |
|
110 | if (config.fatal) throw new Error(logEntry);
|
111 | if (msg.length > 0 && !options.silent) log(logEntry);
|
112 |
|
113 | if (!options.continue) {
|
114 | throw {
|
115 | msg: 'earlyExit',
|
116 | retValue: (new ShellString('', state.error, state.errorCode)),
|
117 | };
|
118 | }
|
119 | }
|
120 | exports.error = error;
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | function ShellString(stdout, stderr, code) {
|
134 | var that;
|
135 | if (stdout instanceof Array) {
|
136 | that = stdout;
|
137 | that.stdout = stdout.join('\n');
|
138 | if (stdout.length > 0) that.stdout += '\n';
|
139 | } else {
|
140 | that = new String(stdout);
|
141 | that.stdout = stdout;
|
142 | }
|
143 | that.stderr = stderr;
|
144 | that.code = code;
|
145 |
|
146 |
|
147 | pipeMethods.forEach(function (cmd) {
|
148 | that[cmd] = shellMethods[cmd].bind(that);
|
149 | });
|
150 | return that;
|
151 | }
|
152 |
|
153 | exports.ShellString = ShellString;
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | function parseOptions(opt, map, errorOptions) {
|
162 |
|
163 | if (typeof opt !== 'string' && !isObject(opt)) {
|
164 | throw new Error('options must be strings or key-value pairs');
|
165 | } else if (!isObject(map)) {
|
166 | throw new Error('parseOptions() internal error: map must be an object');
|
167 | } else if (errorOptions && !isObject(errorOptions)) {
|
168 | throw new Error('parseOptions() internal error: errorOptions must be object');
|
169 | }
|
170 |
|
171 | if (opt === '--') {
|
172 |
|
173 | return {};
|
174 | }
|
175 |
|
176 |
|
177 | var options = {};
|
178 | Object.keys(map).forEach(function (letter) {
|
179 | var optName = map[letter];
|
180 | if (optName[0] !== '!') {
|
181 | options[optName] = false;
|
182 | }
|
183 | });
|
184 |
|
185 | if (opt === '') return options;
|
186 |
|
187 | if (typeof opt === 'string') {
|
188 | if (opt[0] !== '-') {
|
189 | throw new Error("Options string must start with a '-'");
|
190 | }
|
191 |
|
192 |
|
193 | var chars = opt.slice(1).split('');
|
194 |
|
195 | chars.forEach(function (c) {
|
196 | if (c in map) {
|
197 | var optionName = map[c];
|
198 | if (optionName[0] === '!') {
|
199 | options[optionName.slice(1)] = false;
|
200 | } else {
|
201 | options[optionName] = true;
|
202 | }
|
203 | } else {
|
204 | error('option not recognized: ' + c, errorOptions || {});
|
205 | }
|
206 | });
|
207 | } else {
|
208 | Object.keys(opt).forEach(function (key) {
|
209 |
|
210 | var c = key[1];
|
211 | if (c in map) {
|
212 | var optionName = map[c];
|
213 | options[optionName] = opt[key];
|
214 | } else {
|
215 | error('option not recognized: ' + c, errorOptions || {});
|
216 | }
|
217 | });
|
218 | }
|
219 | return options;
|
220 | }
|
221 | exports.parseOptions = parseOptions;
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | function expand(list) {
|
228 | if (!Array.isArray(list)) {
|
229 | throw new TypeError('must be an array');
|
230 | }
|
231 | var expanded = [];
|
232 | list.forEach(function (listEl) {
|
233 |
|
234 | if (typeof listEl !== 'string') {
|
235 | expanded.push(listEl);
|
236 | } else {
|
237 | var ret;
|
238 | try {
|
239 | ret = glob.sync(listEl, config.globOptions);
|
240 |
|
241 | ret = ret.length > 0 ? ret : [listEl];
|
242 | } catch (e) {
|
243 |
|
244 | ret = [listEl];
|
245 | }
|
246 | expanded = expanded.concat(ret);
|
247 | }
|
248 | });
|
249 | return expanded;
|
250 | }
|
251 | exports.expand = expand;
|
252 |
|
253 |
|
254 |
|
255 | var buffer = typeof Buffer.alloc === 'function' ?
|
256 | function (len) {
|
257 | return Buffer.alloc(len || config.bufLength);
|
258 | } :
|
259 | function (len) {
|
260 | return new Buffer(len || config.bufLength);
|
261 | };
|
262 | exports.buffer = buffer;
|
263 |
|
264 |
|
265 |
|
266 | function unlinkSync(file) {
|
267 | try {
|
268 | fs.unlinkSync(file);
|
269 | } catch (e) {
|
270 |
|
271 |
|
272 | if (e.code === 'EPERM') {
|
273 | fs.chmodSync(file, '0666');
|
274 | fs.unlinkSync(file);
|
275 | } else {
|
276 | throw e;
|
277 | }
|
278 | }
|
279 | }
|
280 | exports.unlinkSync = unlinkSync;
|
281 |
|
282 |
|
283 |
|
284 | function statFollowLinks() {
|
285 | return fs.statSync.apply(fs, arguments);
|
286 | }
|
287 | exports.statFollowLinks = statFollowLinks;
|
288 |
|
289 | function statNoFollowLinks() {
|
290 | return fs.lstatSync.apply(fs, arguments);
|
291 | }
|
292 | exports.statNoFollowLinks = statNoFollowLinks;
|
293 |
|
294 |
|
295 | function randomFileName() {
|
296 | function randomHash(count) {
|
297 | if (count === 1) {
|
298 | return parseInt(16 * Math.random(), 10).toString(16);
|
299 | }
|
300 | var hash = '';
|
301 | for (var i = 0; i < count; i++) {
|
302 | hash += randomHash(1);
|
303 | }
|
304 | return hash;
|
305 | }
|
306 |
|
307 | return 'shelljs_' + randomHash(20);
|
308 | }
|
309 | exports.randomFileName = randomFileName;
|
310 |
|
311 |
|
312 |
|
313 | function wrap(cmd, fn, options) {
|
314 | options = options || {};
|
315 | return function () {
|
316 | var retValue = null;
|
317 |
|
318 | state.currentCmd = cmd;
|
319 | state.error = null;
|
320 | state.errorCode = 0;
|
321 |
|
322 | try {
|
323 | var args = [].slice.call(arguments, 0);
|
324 |
|
325 |
|
326 | if (config.verbose) {
|
327 | console.error.apply(console, [cmd].concat(args));
|
328 | }
|
329 |
|
330 |
|
331 |
|
332 | state.pipedValue = (this && typeof this.stdout === 'string') ? this.stdout : '';
|
333 |
|
334 | if (options.unix === false) {
|
335 | retValue = fn.apply(this, args);
|
336 | } else {
|
337 | if (isObject(args[0]) && args[0].constructor.name === 'Object') {
|
338 |
|
339 | } else if (args.length === 0 || typeof args[0] !== 'string' || args[0].length <= 1 || args[0][0] !== '-') {
|
340 | args.unshift('');
|
341 | }
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 | args = args.reduce(function (accum, cur) {
|
348 | if (Array.isArray(cur)) {
|
349 | return accum.concat(cur);
|
350 | }
|
351 | accum.push(cur);
|
352 | return accum;
|
353 | }, []);
|
354 |
|
355 |
|
356 | args = args.map(function (arg) {
|
357 | if (isObject(arg) && arg.constructor.name === 'String') {
|
358 | return arg.toString();
|
359 | }
|
360 | return arg;
|
361 | });
|
362 |
|
363 |
|
364 | var homeDir = os.homedir();
|
365 | args = args.map(function (arg) {
|
366 | if (typeof arg === 'string' && arg.slice(0, 2) === '~/' || arg === '~') {
|
367 | return arg.replace(/^~/, homeDir);
|
368 | }
|
369 | return arg;
|
370 | });
|
371 |
|
372 |
|
373 |
|
374 | if (!config.noglob && options.allowGlobbing === true) {
|
375 | args = args.slice(0, options.globStart).concat(expand(args.slice(options.globStart)));
|
376 | }
|
377 |
|
378 | try {
|
379 |
|
380 | if (isObject(options.cmdOptions)) {
|
381 | args[0] = parseOptions(args[0], options.cmdOptions);
|
382 | }
|
383 |
|
384 | retValue = fn.apply(this, args);
|
385 | } catch (e) {
|
386 |
|
387 | if (e.msg === 'earlyExit') {
|
388 | retValue = e.retValue;
|
389 | } else {
|
390 | throw e;
|
391 | }
|
392 | }
|
393 | }
|
394 | } catch (e) {
|
395 |
|
396 | if (!state.error) {
|
397 |
|
398 | e.name = 'ShellJSInternalError';
|
399 | throw e;
|
400 | }
|
401 | if (config.fatal) throw e;
|
402 | }
|
403 |
|
404 | if (options.wrapOutput &&
|
405 | (typeof retValue === 'string' || Array.isArray(retValue))) {
|
406 | retValue = new ShellString(retValue, state.error, state.errorCode);
|
407 | }
|
408 |
|
409 | state.currentCmd = 'shell.js';
|
410 | return retValue;
|
411 | };
|
412 | }
|
413 | exports.wrap = wrap;
|
414 |
|
415 |
|
416 |
|
417 | function _readFromPipe() {
|
418 | return state.pipedValue;
|
419 | }
|
420 | exports.readFromPipe = _readFromPipe;
|
421 |
|
422 | var DEFAULT_WRAP_OPTIONS = {
|
423 | allowGlobbing: true,
|
424 | canReceivePipe: false,
|
425 | cmdOptions: null,
|
426 | globStart: 1,
|
427 | pipeOnly: false,
|
428 | wrapOutput: true,
|
429 | unix: true,
|
430 | };
|
431 |
|
432 |
|
433 | var pipeMethods = [];
|
434 |
|
435 |
|
436 | function _register(name, implementation, wrapOptions) {
|
437 | wrapOptions = wrapOptions || {};
|
438 |
|
439 |
|
440 | Object.keys(wrapOptions).forEach(function (option) {
|
441 | if (!DEFAULT_WRAP_OPTIONS.hasOwnProperty(option)) {
|
442 | throw new Error("Unknown option '" + option + "'");
|
443 | }
|
444 | if (typeof wrapOptions[option] !== typeof DEFAULT_WRAP_OPTIONS[option]) {
|
445 | throw new TypeError("Unsupported type '" + typeof wrapOptions[option] +
|
446 | "' for option '" + option + "'");
|
447 | }
|
448 | });
|
449 |
|
450 |
|
451 | wrapOptions = Object.assign({}, DEFAULT_WRAP_OPTIONS, wrapOptions);
|
452 |
|
453 | if (shell[name]) {
|
454 | throw new Error('Command `' + name + '` already exists');
|
455 | }
|
456 |
|
457 | if (wrapOptions.pipeOnly) {
|
458 | wrapOptions.canReceivePipe = true;
|
459 | shellMethods[name] = wrap(name, implementation, wrapOptions);
|
460 | } else {
|
461 | shell[name] = wrap(name, implementation, wrapOptions);
|
462 | }
|
463 |
|
464 | if (wrapOptions.canReceivePipe) {
|
465 | pipeMethods.push(name);
|
466 | }
|
467 | }
|
468 | exports.register = _register;
|