just-scripts 2.5.0 → 2.6.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.json +48 -1
  2. package/CHANGELOG.md +18 -2
  3. package/lib/tasks/eslintTask.d.ts.map +1 -1
  4. package/lib/tasks/eslintTask.js +4 -5
  5. package/lib/tasks/eslintTask.js.map +1 -1
  6. package/lib/tasks/jestTask.d.ts +8 -0
  7. package/lib/tasks/jestTask.d.ts.map +1 -1
  8. package/lib/tasks/jestTask.js +8 -9
  9. package/lib/tasks/jestTask.js.map +1 -1
  10. package/lib/tasks/nodeExecTask.d.ts +22 -8
  11. package/lib/tasks/nodeExecTask.d.ts.map +1 -1
  12. package/lib/tasks/nodeExecTask.js +16 -10
  13. package/lib/tasks/nodeExecTask.js.map +1 -1
  14. package/lib/tasks/prettierTask.d.ts +1 -0
  15. package/lib/tasks/prettierTask.d.ts.map +1 -1
  16. package/lib/tasks/prettierTask.js +7 -21
  17. package/lib/tasks/prettierTask.js.map +1 -1
  18. package/lib/tasks/tscTask.d.ts.map +1 -1
  19. package/lib/tasks/tscTask.js +20 -38
  20. package/lib/tasks/tscTask.js.map +1 -1
  21. package/lib/tasks/tslintTask.d.ts.map +1 -1
  22. package/lib/tasks/tslintTask.js +7 -8
  23. package/lib/tasks/tslintTask.js.map +1 -1
  24. package/lib/tasks/webpackCliInitTask.js +2 -1
  25. package/lib/tasks/webpackCliInitTask.js.map +1 -1
  26. package/lib/tasks/webpackCliTask.js +1 -1
  27. package/lib/tasks/webpackCliTask.js.map +1 -1
  28. package/lib/tasks/webpackDevServerTask.d.ts.map +1 -1
  29. package/lib/tasks/webpackDevServerTask.js +1 -1
  30. package/lib/tasks/webpackDevServerTask.js.map +1 -1
  31. package/lib/utils/exec.d.ts +20 -31
  32. package/lib/utils/exec.d.ts.map +1 -1
  33. package/lib/utils/exec.js +53 -61
  34. package/lib/utils/exec.js.map +1 -1
  35. package/package.json +4 -2
  36. package/src/tasks/eslintTask.ts +7 -6
  37. package/src/tasks/jestTask.ts +18 -9
  38. package/src/tasks/nodeExecTask.ts +33 -17
  39. package/src/tasks/prettierTask.ts +9 -26
  40. package/src/tasks/tscTask.ts +21 -44
  41. package/src/tasks/tslintTask.ts +8 -7
  42. package/src/tasks/webpackCliInitTask.ts +1 -1
  43. package/src/tasks/webpackCliTask.ts +2 -2
  44. package/src/tasks/webpackDevServerTask.ts +3 -3
  45. package/src/utils/exec.ts +52 -66
@@ -1,7 +1,7 @@
1
1
  import { resolve, logger, resolveCwd, TaskFunction } from 'just-task';
2
- import { exec, encodeArgs } from '../utils';
3
2
  import * as path from 'path';
4
3
  import * as fs from 'fs';
4
+ import { logNodeCommand, spawn } from '../utils';
5
5
 
6
6
  export interface TsLintTaskOptions {
7
7
  config?: string;
@@ -12,7 +12,7 @@ export interface TsLintTaskOptions {
12
12
  export function tslintTask(options: TsLintTaskOptions = {}): TaskFunction {
13
13
  const projectFile = options.project || resolveCwd('./tsconfig.json');
14
14
 
15
- return function tslint() {
15
+ return async function tslint() {
16
16
  const tslintCmd = resolve('tslint/lib/tslintCli.js');
17
17
 
18
18
  if (projectFile && tslintCmd && fs.existsSync(projectFile)) {
@@ -31,11 +31,12 @@ export function tslintTask(options: TsLintTaskOptions = {}): TaskFunction {
31
31
  args.push('--fix');
32
32
  }
33
33
 
34
- const cmd = encodeArgs([process.execPath, tslintCmd, ...args]).join(' ');
35
- logger.info(cmd);
36
- return exec(cmd, { stdout: process.stdout, stderr: process.stderr });
37
- } else {
38
- return Promise.resolve();
34
+ const allArgs = [tslintCmd, ...args];
35
+ logNodeCommand(allArgs);
36
+ return spawn(process.execPath, allArgs, { stdio: 'inherit' });
39
37
  }
38
+
39
+ // undertaker apparently requires returning a promise, async function, or function that calls done()
40
+ return Promise.resolve();
40
41
  };
41
42
  }
@@ -9,7 +9,7 @@ import { tryRequire } from '../tryRequire';
9
9
  */
10
10
  export function webpackCliInitTask(customScaffold?: string, auto = false): TaskFunction {
11
11
  return function webpackCli() {
12
- const init = tryRequire('@webpack-cli/init').default;
12
+ const init = tryRequire('@webpack-cli/init')?.default;
13
13
  if (!init) {
14
14
  logger.warn('webpack-cli init requires three dependencies: @webpack-cli/init (preferred - as a devDependency)');
15
15
  return;
@@ -1,5 +1,5 @@
1
1
  import { logger, TaskFunction, resolve } from 'just-task';
2
- import { spawn } from '../utils';
2
+ import { logNodeCommand, spawn } from '../utils';
3
3
  import { getTsNodeEnv } from '../typescript/getTsNodeEnv';
4
4
  import { findWebpackConfig } from '../webpack/findWebpackConfig';
5
5
 
@@ -67,7 +67,7 @@ export function webpackCliTask(options: WebpackCliTaskOptions = {}): TaskFunctio
67
67
  }
68
68
  }
69
69
 
70
- logger.info(`webpack-cli arguments: ${process.execPath} ${args.join(' ')}`);
70
+ logNodeCommand(args);
71
71
 
72
72
  return spawn(process.execPath, args, { stdio: 'inherit', env: options.env });
73
73
  };
@@ -1,7 +1,7 @@
1
1
  // // WARNING: Careful about add more imports - only import types from webpack
2
2
  import { Configuration } from 'webpack';
3
- import { encodeArgs, spawn } from '../utils';
4
- import { logger, resolve, resolveCwd, TaskFunction } from 'just-task';
3
+ import { logNodeCommand, spawn } from '../utils';
4
+ import { resolve, resolveCwd, TaskFunction } from 'just-task';
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import { WebpackCliTaskOptions } from './webpackCliTask';
@@ -93,7 +93,7 @@ export function webpackDevServerTask(options: WebpackDevServerTaskOptions = {}):
93
93
  args = [...args, ...options.webpackCliArgs];
94
94
  }
95
95
 
96
- logger.info(process.execPath, encodeArgs(args).join(' '));
96
+ logNodeCommand(args);
97
97
  return spawn(process.execPath, args, { stdio: 'inherit', env: options.env });
98
98
  };
99
99
  }
package/src/utils/exec.ts CHANGED
@@ -1,52 +1,25 @@
1
1
  import * as cp from 'child_process';
2
- import { resolve } from 'path';
2
+ import { spawn as crossSpawn } from 'cross-spawn';
3
+ import { logger } from 'just-task';
3
4
 
4
- export interface ExecError extends cp.ExecException {
5
- stdout?: string;
6
- stderr?: string;
7
- }
8
-
9
- const SEPARATOR = process.platform === 'win32' ? ';' : ':';
5
+ // `exec` and `execSync` were removed due to security issues (keeping filename for history)
10
6
 
11
7
  /**
12
- * Execute a command.
13
- *
14
- * @param cmd Command to execute
15
- * @param opts Normal exec options plus stdout/stderr for piping output. Can pass `process` for this param.
16
- * @returns Promise which will settle when the command completes. If output was not piped, it will be
17
- * returned as the promise's value. If the promise was rejected, the error will be of type `ExecError`.
8
+ * @deprecated This prevents issues from spaces in args, but does NOT escape shell metacharacters.
9
+ * For full escaping, consider a library such as `shell-quote` instead. (Note that `spawn` and tasks
10
+ * from this package now use `cross-spawn` which escapes spaces internally.)
18
11
  */
19
- export function exec(
20
- cmd: string,
21
- opts: cp.ExecOptions & { stdout?: NodeJS.WritableStream; stderr?: NodeJS.WritableStream } = {},
22
- ): Promise<string | undefined> {
23
- return new Promise((resolve, reject) => {
24
- const child = cp.exec(cmd, opts, (error: ExecError | null, stdout?: string, stderr?: string) => {
25
- if (error) {
26
- error.stdout = stdout;
27
- error.stderr = stderr;
28
- reject(error);
29
- } else {
30
- resolve(stdout);
31
- }
32
- });
33
-
34
- if (opts.stdout) {
35
- child.stdout?.pipe(opts.stdout);
36
- }
37
-
38
- if (opts.stderr) {
39
- child.stderr?.pipe(opts.stderr);
40
- }
41
- });
12
+ export function encodeArgs(cmdArgs: string[]): string[] {
13
+ return quoteSpaces(cmdArgs);
42
14
  }
43
15
 
44
16
  /**
45
- * Encode args for a shell command.
46
- * @param cmdArgs Args to encode
47
- * @returns Encoded args
17
+ * Quote arguments containing spaces. Note that this does NOT do any other escaping!
18
+ * For more complete escaping, consider a library such as `shell-quote`, or use safer APIs which
19
+ * don't require escaping. (Note that `spawn` from this package now uses `cross-spawn` which
20
+ * escapes spaces internally.)
48
21
  */
49
- export function encodeArgs(cmdArgs: string[]): string[] {
22
+ function quoteSpaces(cmdArgs: string[]): string[] {
50
23
  // Taken from https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js
51
24
  // However, we needed to use double quotes because that's the norm in more platforms
52
25
  if (!cmdArgs) {
@@ -64,36 +37,31 @@ export function encodeArgs(cmdArgs: string[]): string[] {
64
37
  }
65
38
 
66
39
  /**
67
- * Execute a command synchronously.
68
- *
69
- * @param cmd Command to execute
70
- * @param cwd Working directory in which to run the command (default: `process.cwd()`)
71
- * @param returnOutput If true, return the command's output. If false/unspecified,
72
- * inherit stdio from the parent process (so the child's output goes to the console).
73
- * @returns If `returnOutput` is true, returns the command's output. Otherwise returns undefined.
40
+ * Log `Running: <process.execPath> <...args>`
74
41
  */
75
- export function execSync(cmd: string, cwd?: string, returnOutput?: boolean): string | undefined {
76
- cwd = cwd || process.cwd();
77
-
78
- const env = { ...process.env };
79
- env.PATH = resolve('./node_modules/.bin') + SEPARATOR + env.PATH;
80
-
81
- const output = cp.execSync(cmd, {
82
- cwd,
83
- env,
84
- stdio: returnOutput ? undefined : 'inherit',
85
- });
86
- return returnOutput ? (output || '').toString('utf8') : undefined;
42
+ export function logNodeCommand(...args: (string | string[])[]): void {
43
+ logger.info(`Running: ${process.execPath} ${quoteSpaces(args.flat()).join(' ')}`);
87
44
  }
88
45
 
89
46
  /**
90
- * Execute a command in a new process.
47
+ * Execute a command in a new process. Uses `cross-spawn` to avoid issues with spaces in arguments,
48
+ * but does not do any additional escaping. (For further enhancements, consider using the `execa`
49
+ * library instead.)
50
+ *
51
+ * **WARNING: If the `shell` option is enabled, do not pass unsanitized user input to this function.
52
+ * Any input containing shell metacharacters may be used to trigger arbitrary command execution.**
91
53
  *
92
54
  * @param cmd Command to execute
93
- * @param args Args for the command
94
- * @param opts Normal spawn options plus stdout/stderr for piping output. Can pass `process` for this param.
55
+ * @param args Args for the command. Quoting spaces is handled internally by `cross-spawn`.
56
+ * @param opts Normal spawn options plus stdout/stderr for piping output. (To inherit stdio from the
57
+ * parent process, just use `stdio: 'inherit'` instead.)
58
+ *
95
59
  * @returns Promise which will settle when the command completes. If the promise is rejected, the error will
96
- * include the child process's exit code.
60
+ * include the child process's exit code (`error.code`) or signal (`error.signal`) if relevant.
61
+ * The returned promise also has a `child` property with the spawned `ChildProcess` instance.
62
+ *
63
+ * @deprecated This function is not recommended for use outside the `just-scripts` package.
64
+ * Instead, consider a more mature, purpose-specific library such as `execa` or `nano-spawn`.
97
65
  */
98
66
  export function spawn(
99
67
  cmd: string,
@@ -101,8 +69,16 @@ export function spawn(
101
69
  opts: cp.SpawnOptions & { stdout?: NodeJS.WritableStream; stderr?: NodeJS.WritableStream } = {},
102
70
  ): Promise<void> {
103
71
  return new Promise((resolve, reject) => {
104
- const child = cp.spawn(cmd, args, opts);
105
- child.on('exit', (code: number | null, signal: string | null) => {
72
+ let child: cp.ChildProcess;
73
+ try {
74
+ child = crossSpawn(cmd, args, opts);
75
+ } catch (error) {
76
+ reject(error);
77
+ return;
78
+ }
79
+
80
+ const onExit = (code: number | null, signal: NodeJS.Signals | null): void => {
81
+ child.off('error', onError);
106
82
  if (code) {
107
83
  const error = new Error('Command failed: ' + [cmd, ...args].join(' '));
108
84
  (error as any).code = code;
@@ -114,7 +90,17 @@ export function spawn(
114
90
  } else {
115
91
  resolve();
116
92
  }
117
- });
93
+ };
94
+
95
+ // Some error circumstances may fire 'error' rather than 'exit'
96
+ // https://nodejs.org/docs/latest/api/child_process.html#event-error
97
+ const onError = (error: Error): void => {
98
+ reject(error);
99
+ child.off('exit', onExit);
100
+ };
101
+
102
+ child.on('exit', onExit);
103
+ child.on('error', onError);
118
104
 
119
105
  if (opts.stdout) {
120
106
  child.stdout?.pipe(opts.stdout);