@villedemontreal/general-utils 5.19.2 → 5.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { spawn, StdioOptions } from 'child_process';
1
+ import { ChildProcess, spawn, StdioOptions } from 'child_process';
2
2
  import * as fs from 'fs';
3
3
  import * as getPort from 'get-port';
4
+ import { isArray, isDate, isEqual, isFunction, isNil, isObject, isString, trimEnd } from 'lodash';
4
5
  import * as pathUtils from 'path';
5
6
  import { rimraf } from 'rimraf';
6
7
  import * as tsconfig from 'tsconfig-extends';
7
8
  import { constants } from './config/constants';
8
- import { isArray, isDate, isEqual, isFunction, isNil, isObject, isString, trimEnd } from 'lodash';
9
9
 
10
10
  /**
11
11
  * General utilities
@@ -308,9 +308,9 @@ export class Utils {
308
308
 
309
309
  const cmd = 'node';
310
310
  const tscCmd = constants.findModulePath('node_modules/typescript/lib/tsc.js');
311
- const args = [tscCmd].concat(this.tscCompilerOptions).concat(files);
311
+ const args = [tscCmd, ...this.tscCompilerOptions, ...files];
312
312
 
313
- await this.execPromisified(cmd, args);
313
+ await this.exec(cmd, args, { useShellOption: false });
314
314
  }
315
315
 
316
316
  /**
@@ -428,6 +428,43 @@ export class Utils {
428
428
  return false;
429
429
  };
430
430
 
431
+ public shellescape(args: string[]) {
432
+ // Function inspired from: https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js
433
+ return args.map(this.shellescapeArgument).join(' ');
434
+ }
435
+
436
+ public shellescapeArgument(a: string) {
437
+ const trimSpaces = (str: string) => {
438
+ return str.replace(/^[ ]+|[ ]+$/g, '');
439
+ };
440
+ const innerQuotedArgEscape = (arg: string, quote: string): string => {
441
+ const re = new RegExp(quote, 'g');
442
+ let result = arg.substring(1, arg.length - 1);
443
+ result = result.replace(/'\\''/g, "'");
444
+ result = result.replace(/"\\""/g, '"');
445
+ result = result.replace(re, `${quote}\\${quote}${quote}`);
446
+ result = result.replace('\n', '\\n'); // handle new lines
447
+ result = result.replace('\t', '\\t'); // handle tabs
448
+ return `${quote}${result}${quote}`;
449
+ };
450
+ a = trimSpaces(a);
451
+ if (/[^A-Za-z0-9_/.$:=-]/.test(a)) {
452
+ if (a.startsWith('"') && a.endsWith('"')) {
453
+ return innerQuotedArgEscape(a, '"');
454
+ }
455
+ if (a.startsWith("'") && a.endsWith("'")) {
456
+ return innerQuotedArgEscape(a, "'");
457
+ }
458
+ a = "'" + a.replace(/'/g, "'\\''") + "'";
459
+ a = a
460
+ .replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
461
+ .replace(/\\'''/g, "\\'") // remove non-escaped single-quote if there are enclosed between 2 escaped
462
+ .replace('\n', '\\n') // handle new lines
463
+ .replace('\t', '\\t'); // handle tabs
464
+ }
465
+ return a;
466
+ }
467
+
431
468
  /**
432
469
  * @deprecated Use `exec()` instead.
433
470
  */
@@ -478,6 +515,9 @@ export class Utils {
478
515
  * https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
479
516
  * Defaults to `true`.
480
517
  *
518
+ * @param options.escapeArgs will automatically escape the submitted args.
519
+ * Defaults to `false` to avoid any breaking changes.
520
+ *
481
521
  * @returns The exit code
482
522
  *
483
523
  * @throws Will fail with a `ExecError` error if the process returns
@@ -494,10 +534,12 @@ export class Utils {
494
534
  disableConsoleOutputs?: boolean;
495
535
  stdio?: StdioOptions;
496
536
  useShellOption?: boolean;
537
+ escapeArgs?: boolean;
497
538
  },
498
539
  ): Promise<number> {
499
540
  const optionsClean = options ?? {};
500
541
  optionsClean.useShellOption = optionsClean.useShellOption ?? true;
542
+ optionsClean.escapeArgs = optionsClean.escapeArgs ?? false;
501
543
  optionsClean.successExitCodes = optionsClean.successExitCodes
502
544
  ? isArray(optionsClean.successExitCodes)
503
545
  ? optionsClean.successExitCodes
@@ -511,13 +553,27 @@ export class Utils {
511
553
  }
512
554
 
513
555
  return new Promise<number>((resolve, reject) => {
514
- const spawnedProcess = spawn(bin, args, {
515
- detached: false,
516
- stdio: optionsClean.stdio,
517
- shell: optionsClean.useShellOption,
518
- windowsVerbatimArguments: false,
519
- });
556
+ let spawnedProcess: ChildProcess;
557
+ if (optionsClean.useShellOption && optionsClean.escapeArgs) {
558
+ const cmd = this.shellescape([bin, ...args]);
559
+ spawnedProcess = spawn(cmd, {
560
+ detached: false,
561
+ stdio: optionsClean.stdio,
562
+ shell: optionsClean.useShellOption,
563
+ windowsVerbatimArguments: false,
564
+ });
565
+ } else {
566
+ spawnedProcess = spawn(bin, args, {
567
+ detached: false,
568
+ stdio: optionsClean.stdio,
569
+ shell: optionsClean.useShellOption,
570
+ windowsVerbatimArguments: false,
571
+ });
572
+ }
520
573
 
574
+ spawnedProcess.on('error', (err: Error) => {
575
+ reject(new ExecError(`Error while executing command: ${err.message}`, 1));
576
+ });
521
577
  spawnedProcess.on('close', (code: number) => {
522
578
  const successExitCodes = optionsClean.successExitCodes as number[];
523
579
  if (!successExitCodes.includes(code)) {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=bfcb8952-c362-4b5b-95df-f4f31730a8bb.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bfcb8952-c362-4b5b-95df-f4f31730a8bb.d.ts","sourceRoot":"","sources":["../../../test-data/test_throwNotManaged/bfcb8952-c362-4b5b-95df-f4f31730a8bb.ts"],"names":[],"mappings":""}