just-scripts 2.4.2 → 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 (75) hide show
  1. package/CHANGELOG.json +85 -2
  2. package/CHANGELOG.md +27 -2
  3. package/lib/index.d.ts +1 -1
  4. package/lib/index.d.ts.map +1 -1
  5. package/lib/index.js +4 -3
  6. package/lib/index.js.map +1 -1
  7. package/lib/interfaces/PackageJson.d.ts +16 -0
  8. package/lib/interfaces/PackageJson.d.ts.map +1 -0
  9. package/lib/interfaces/PackageJson.js +3 -0
  10. package/lib/interfaces/PackageJson.js.map +1 -0
  11. package/lib/tasks/eslintTask.d.ts.map +1 -1
  12. package/lib/tasks/eslintTask.js +6 -7
  13. package/lib/tasks/eslintTask.js.map +1 -1
  14. package/lib/tasks/jestTask.d.ts +8 -0
  15. package/lib/tasks/jestTask.d.ts.map +1 -1
  16. package/lib/tasks/jestTask.js +10 -11
  17. package/lib/tasks/jestTask.js.map +1 -1
  18. package/lib/tasks/nodeExecTask.d.ts +22 -8
  19. package/lib/tasks/nodeExecTask.d.ts.map +1 -1
  20. package/lib/tasks/nodeExecTask.js +17 -11
  21. package/lib/tasks/nodeExecTask.js.map +1 -1
  22. package/lib/tasks/prettierTask.d.ts +1 -0
  23. package/lib/tasks/prettierTask.d.ts.map +1 -1
  24. package/lib/tasks/prettierTask.js +9 -23
  25. package/lib/tasks/prettierTask.js.map +1 -1
  26. package/lib/tasks/tscTask.d.ts.map +1 -1
  27. package/lib/tasks/tscTask.js +21 -39
  28. package/lib/tasks/tscTask.js.map +1 -1
  29. package/lib/tasks/tslintTask.d.ts.map +1 -1
  30. package/lib/tasks/tslintTask.js +7 -8
  31. package/lib/tasks/tslintTask.js.map +1 -1
  32. package/lib/tasks/webpackCliInitTask.js +2 -1
  33. package/lib/tasks/webpackCliInitTask.js.map +1 -1
  34. package/lib/tasks/webpackCliTask.js +3 -3
  35. package/lib/tasks/webpackCliTask.js.map +1 -1
  36. package/lib/tasks/webpackDevServerTask.d.ts.map +1 -1
  37. package/lib/tasks/webpackDevServerTask.js +3 -3
  38. package/lib/tasks/webpackDevServerTask.js.map +1 -1
  39. package/lib/utils/exec.d.ts +37 -0
  40. package/lib/utils/exec.d.ts.map +1 -0
  41. package/lib/utils/exec.js +107 -0
  42. package/lib/utils/exec.js.map +1 -0
  43. package/lib/utils/index.d.ts +5 -0
  44. package/lib/utils/index.d.ts.map +1 -0
  45. package/lib/utils/index.js +19 -0
  46. package/lib/utils/index.js.map +1 -0
  47. package/lib/utils/mergePackageJson.d.ts +20 -0
  48. package/lib/utils/mergePackageJson.d.ts.map +1 -0
  49. package/lib/utils/mergePackageJson.js +61 -0
  50. package/lib/utils/mergePackageJson.js.map +1 -0
  51. package/lib/utils/paths.d.ts +13 -0
  52. package/lib/utils/paths.d.ts.map +1 -0
  53. package/lib/utils/paths.js +26 -0
  54. package/lib/utils/paths.js.map +1 -0
  55. package/lib/utils/readPackageJson.d.ts +8 -0
  56. package/lib/utils/readPackageJson.d.ts.map +1 -0
  57. package/lib/utils/readPackageJson.js +19 -0
  58. package/lib/utils/readPackageJson.js.map +1 -0
  59. package/package.json +4 -3
  60. package/src/index.ts +1 -1
  61. package/src/interfaces/PackageJson.ts +16 -0
  62. package/src/tasks/eslintTask.ts +7 -6
  63. package/src/tasks/jestTask.ts +18 -9
  64. package/src/tasks/nodeExecTask.ts +34 -18
  65. package/src/tasks/prettierTask.ts +9 -26
  66. package/src/tasks/tscTask.ts +21 -44
  67. package/src/tasks/tslintTask.ts +8 -7
  68. package/src/tasks/webpackCliInitTask.ts +1 -1
  69. package/src/tasks/webpackCliTask.ts +2 -2
  70. package/src/tasks/webpackDevServerTask.ts +3 -3
  71. package/src/utils/exec.ts +112 -0
  72. package/src/utils/index.ts +4 -0
  73. package/src/utils/mergePackageJson.ts +64 -0
  74. package/src/utils/paths.ts +26 -0
  75. package/src/utils/readPackageJson.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "just-scripts",
3
- "version": "2.4.2",
3
+ "version": "2.6.0",
4
4
  "description": "Just Stack Scripts",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -28,11 +28,11 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "chalk": "^4.0.0",
31
+ "cross-spawn": "^7.0.6",
31
32
  "diff-match-patch": "1.0.5",
32
33
  "fs-extra": "^11.0.0",
33
34
  "glob": "^7.1.3",
34
- "just-scripts-utils": "^2.1.0",
35
- "just-task": ">=1.12.0 <2.0.0",
35
+ "just-task": "^1.13.0",
36
36
  "prompts": "^2.4.0",
37
37
  "run-parallel-limit": "^1.0.6",
38
38
  "semver": "^7.0.0",
@@ -40,6 +40,7 @@
40
40
  "webpack-merge": "^5.7.3"
41
41
  },
42
42
  "devDependencies": {
43
+ "@types/cross-spawn": "^6.0.6",
43
44
  "@types/diff-match-patch": "^1.0.32",
44
45
  "@types/glob": "^7.2.0",
45
46
  "@types/prompts": "^2.4.2",
package/src/index.ts CHANGED
@@ -43,4 +43,4 @@ export { copyInstructions };
43
43
 
44
44
  export * from 'just-task';
45
45
 
46
- export { spawn } from 'just-scripts-utils';
46
+ export { encodeArgs, spawn } from './utils';
@@ -0,0 +1,16 @@
1
+ export interface Dependencies {
2
+ [key: string]: string;
3
+ }
4
+
5
+ export interface PackageJson {
6
+ name: string;
7
+ description?: string;
8
+ dependencies?: Dependencies;
9
+ devDependencies?: Dependencies;
10
+ keywords?: string;
11
+ just?: {
12
+ /** Stack that the package is tracking */
13
+ stack?: string;
14
+ };
15
+ [key: string]: any;
16
+ }
@@ -1,5 +1,5 @@
1
- import { resolve, logger, resolveCwd, TaskFunction } from 'just-task';
2
- import { encodeArgs, spawn } from 'just-scripts-utils';
1
+ import { resolve, resolveCwd, TaskFunction } from 'just-task';
2
+ import { logNodeCommand, spawn } from '../utils';
3
3
  import * as fs from 'fs';
4
4
 
5
5
  /**
@@ -70,7 +70,7 @@ export function eslintTask(options: EsLintTaskOptions = {}): TaskFunction {
70
70
  eslintCmd,
71
71
  ...(files ? files : ['.']),
72
72
  ...['--ext', extensions ? extensions : '.js,.jsx,.ts,.tsx'],
73
- ...(noEslintRc ? '--no-eslintrc' : []),
73
+ ...(noEslintRc ? ['--no-eslintrc'] : []),
74
74
  ...(eslintConfigPath ? ['--config', eslintConfigPath] : []),
75
75
  ...(eslintIgnorePath ? ['--ignore-path', eslintIgnorePath] : []),
76
76
  ...(resolvePluginsPath ? ['--resolve-plugins-relative-to', resolvePluginsPath] : []),
@@ -95,10 +95,11 @@ export function eslintTask(options: EsLintTaskOptions = {}): TaskFunction {
95
95
  env.ESLINT_USE_FLAT_CONFIG = JSON.stringify(useFlatConfig);
96
96
  }
97
97
 
98
- logger.info(encodeArgs(eslintArgs).join(' '));
98
+ logNodeCommand(eslintArgs);
99
99
  return spawn(process.execPath, eslintArgs, { stdio: 'inherit', env });
100
- } else {
101
- return Promise.resolve();
102
100
  }
101
+
102
+ // undertaker apparently requires returning a promise, async function, or function that calls done()
103
+ return Promise.resolve();
103
104
  };
104
105
  }
@@ -1,5 +1,5 @@
1
1
  import { resolve, logger, resolveCwd, TaskFunction, argv } from 'just-task';
2
- import { spawn, encodeArgs, readPackageJson } from 'just-scripts-utils';
2
+ import { spawn, readPackageJson, logNodeCommand } from '../utils';
3
3
  import { existsSync } from 'fs';
4
4
  import * as supportsColor from 'supports-color';
5
5
 
@@ -14,7 +14,15 @@ export interface JestTaskOptions {
14
14
  passWithNoTests?: boolean;
15
15
  clearCache?: boolean;
16
16
  silent?: boolean;
17
+ /**
18
+ * This is not available in jest 30+
19
+ * Consider updating to jest 30 and using testPathPatterns (plural) instead.
20
+ */
17
21
  testPathPattern?: string;
22
+ /**
23
+ * Compatible with jest 30+ only
24
+ */
25
+ testPathPatterns?: string;
18
26
  testNamePattern?: string;
19
27
  // The maximum number of workers to use in jest for parallel test execution
20
28
  maxWorkers?: number;
@@ -54,7 +62,6 @@ export function jestTask(options: JestTaskOptions = {}): TaskFunction {
54
62
 
55
63
  if ((configFileExists || packageConfigExists) && jestCmd) {
56
64
  logger.info(`Running Jest`);
57
- const cmd = process.execPath;
58
65
 
59
66
  const positional = argv()._.slice(1);
60
67
 
@@ -71,20 +78,22 @@ export function jestTask(options: JestTaskOptions = {}): TaskFunction {
71
78
  ...(options.watch ? ['--watch'] : []),
72
79
  ...(options.silent ? ['--silent'] : []),
73
80
  ...(options.testPathPattern ? ['--testPathPattern', options.testPathPattern] : []),
81
+ ...(options.testPathPatterns ? ['--testPathPatterns', options.testPathPatterns] : []),
74
82
  ...(options.testNamePattern ? ['--testNamePattern', options.testNamePattern] : []),
75
- ...(options.maxWorkers ? ['--maxWorkers', options.maxWorkers]: []),
76
- ...(options.u || options.updateSnapshot ? ['--updateSnapshot'] : ['']),
83
+ ...(options.maxWorkers ? ['--maxWorkers', `${options.maxWorkers}`] : []),
84
+ ...(options.u || options.updateSnapshot ? ['--updateSnapshot'] : []),
77
85
  // Only include the positional args if `options._` wasn't specified
78
86
  // (to avoid possibly including them twice)
79
87
  ...(options._ || positional),
80
88
  ].filter(arg => !!arg) as string[];
81
89
 
82
- logger.info(cmd, encodeArgs(args).join(' '));
90
+ logNodeCommand(args);
83
91
 
84
- return spawn(cmd, args, { stdio: 'inherit', env: options.env });
85
- } else {
86
- logger.warn('no jest configuration found, skipping jest');
87
- return Promise.resolve();
92
+ return spawn(process.execPath, args, { stdio: 'inherit', env: options.env });
88
93
  }
94
+
95
+ logger.warn('no jest configuration found, skipping jest');
96
+ // undertaker apparently requires returning a promise, async function, or function that calls done()
97
+ return Promise.resolve();
89
98
  };
90
99
  }
@@ -1,42 +1,56 @@
1
1
  import { SpawnOptions } from 'child_process';
2
- import { spawn } from 'just-scripts-utils';
2
+ import { spawn } from '../utils';
3
3
  import { logger, TaskFunction } from 'just-task';
4
4
  import { resolveCwd, _tryResolve } from 'just-task/lib/resolve';
5
5
  import { getTsNodeEnv } from '../typescript/getTsNodeEnv';
6
6
 
7
7
  export interface NodeExecTaskOptions {
8
8
  /**
9
- * Arguments to be passed into a spawn call for webpack dev server. This can be used to do things
10
- * like increase the heap space for the JS engine to address out of memory issues.
9
+ * Arguments to be passed into a spawn call, including the script path to execute.
10
+ * The script path should be **absolute** to prevent unpredictable resolution.
11
+ *
12
+ * **WARNING: If `options.shell` is enabled, do not pass unsanitized user input as `args`.
13
+ * Any input containing shell metacharacters may be used to trigger arbitrary command execution.**
11
14
  */
12
- args?: string[];
15
+ args: string[];
13
16
 
14
17
  /**
15
- * Environment variables to be passed to the webpack-cli
18
+ * Environment variables to be passed to the spawned process.
19
+ * Defaults to `process.env`.
16
20
  */
17
21
  env?: NodeJS.ProcessEnv;
18
22
 
19
23
  /**
20
- * Should this nodeExec task be using something like ts-node to execute the binary
24
+ * Whether to use `ts-node` to execute the script
21
25
  */
22
26
  enableTypeScript?: boolean;
23
27
 
24
28
  /**
25
- * The tsconfig file to pass to ts-node for Typescript config
29
+ * tsconfig file path to pass to `ts-node`
26
30
  */
27
31
  tsconfig?: string;
28
32
 
29
33
  /**
30
- * Transpile the config only
34
+ * Whether to use `transpileOnly` mode for `ts-node`
31
35
  */
32
36
  transpileOnly?: boolean;
33
37
 
34
38
  /**
35
- * Custom spawn options
39
+ * Custom spawn options.
40
+ *
41
+ * **WARNING: If the `shell` option is enabled, do not pass unsanitized user input as `args`.
42
+ * Any input containing shell metacharacters may be used to trigger arbitrary command execution.**
36
43
  */
37
44
  spawnOptions?: SpawnOptions;
38
45
  }
39
46
 
47
+ /**
48
+ * Create a task to execute a command in a new process.
49
+ * Uses `cross-spawn` to avoid issues with spaces in arguments, but does not do any additional escaping.
50
+ *
51
+ * **WARNING: If the `shell` option is enabled, do not pass unsanitized user input to this task.
52
+ * Any input containing shell metacharacters may be used to trigger arbitrary command execution.**
53
+ */
40
54
  export function nodeExecTask(options: NodeExecTaskOptions): TaskFunction {
41
55
  return function () {
42
56
  const { spawnOptions, enableTypeScript, tsconfig, transpileOnly } = options;
@@ -44,17 +58,19 @@ export function nodeExecTask(options: NodeExecTaskOptions): TaskFunction {
44
58
  const tsNodeRegister = resolveCwd('ts-node/register');
45
59
  const nodeExecPath = process.execPath;
46
60
 
47
- if (enableTypeScript && tsNodeRegister) {
48
- options.args = options.args || [];
49
- options.args.unshift(tsNodeRegister);
50
- options.args.unshift('-r');
61
+ const args = [...(options.args || [])];
62
+ // Preserve the default behavior of inheriting process.env if no options are specified
63
+ let env = options.env ? { ...options.env } : { ...process.env };
64
+ const isTS = enableTypeScript && tsNodeRegister;
51
65
 
52
- options.env = { ...options.env, ...getTsNodeEnv(tsconfig, transpileOnly) };
53
- logger.info('Executing [TS]: ' + [nodeExecPath, ...(options.args || [])].join(' '));
54
- } else {
55
- logger.info('Executing: ' + [nodeExecPath, ...(options.args || [])].join(' '));
66
+ if (isTS) {
67
+ args.unshift('-r', tsNodeRegister);
68
+
69
+ env = { ...env, ...getTsNodeEnv(tsconfig, transpileOnly) };
56
70
  }
57
71
 
58
- return spawn(nodeExecPath, options.args, { stdio: 'inherit', env: options.env, ...spawnOptions });
72
+ logger.info([`Executing${isTS ? ' [TS]' : ''}:`, nodeExecPath, ...args].join(' '));
73
+
74
+ return spawn(nodeExecPath, args, { stdio: 'inherit', env, ...spawnOptions });
59
75
  };
60
76
  }
@@ -1,5 +1,5 @@
1
1
  import { logger, resolve, TaskFunction } from 'just-task';
2
- import { spawn } from 'just-scripts-utils';
2
+ import { logNodeCommand, spawn } from '../utils';
3
3
  import { splitArrayIntoChunks } from '../arrayUtils/splitArrayIntoChunks';
4
4
  import * as path from 'path';
5
5
  import { arrayify } from '../arrayUtils/arrayify';
@@ -16,10 +16,12 @@ export interface PrettierTaskOptions {
16
16
  files?: string[] | string;
17
17
  ignorePath?: string;
18
18
  configPath?: string;
19
+ check?: boolean;
19
20
  }
20
21
 
21
22
  export function prettierTask(options: PrettierTaskOptions = {}): TaskFunction {
22
- const prettierBin = resolve('prettier/bin-prettier.js');
23
+ // check v2 or v3 path
24
+ const prettierBin = resolve('prettier/bin-prettier.js') || resolve('prettier/bin/prettier.cjs');
23
25
 
24
26
  if (prettierBin) {
25
27
  return function prettier() {
@@ -32,38 +34,19 @@ export function prettierTask(options: PrettierTaskOptions = {}): TaskFunction {
32
34
  options.files || path.resolve(process.cwd(), '**', '*.{ts,tsx,js,jsx,json,scss,html,yml,md}'),
33
35
  ),
34
36
  },
35
- check: false,
37
+ check: !!options.check,
36
38
  });
37
39
  };
38
40
  }
39
41
 
40
- return function () {
42
+ // undertaker apparently requires returning a promise, async function, or function that calls done()
43
+ return async () => {
41
44
  logger.warn('Prettier is not available, ignoring this task');
42
45
  };
43
46
  }
44
47
 
45
48
  export function prettierCheckTask(options: PrettierTaskOptions = {}): TaskFunction {
46
- const prettierBin = resolve('prettier/bin-prettier.js');
47
-
48
- if (prettierBin) {
49
- return function prettierCheck() {
50
- return runPrettierAsync({
51
- prettierBin,
52
- ...{ configPath: options.configPath || undefined },
53
- ...{ ignorePath: options.ignorePath || undefined },
54
- ...{
55
- files: arrayify(
56
- options.files || path.resolve(process.cwd(), '**', '*.{ts,tsx,js,jsx,json,scss,html,yml,md}'),
57
- ),
58
- },
59
- check: true,
60
- });
61
- };
62
- }
63
-
64
- return function () {
65
- logger.warn('Prettier is not available, ignoring this task');
66
- };
49
+ return prettierTask({ ...options, check: true });
67
50
  }
68
51
 
69
52
  function runPrettierAsync(context: PrettierContext) {
@@ -81,7 +64,7 @@ function runPrettierAsync(context: PrettierContext) {
81
64
  ...chunk,
82
65
  ];
83
66
 
84
- logger.info(process.execPath + ' ' + prettierArgs.join(' '));
67
+ logNodeCommand(prettierArgs);
85
68
 
86
69
  return finishPromise.then(() => spawn(process.execPath, prettierArgs, { stdio: 'inherit' }));
87
70
  }, Promise.resolve());
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { resolve, logger, resolveCwd, TaskFunction } from 'just-task';
3
- import { exec, encodeArgs, spawn } from 'just-scripts-utils';
3
+ import { logNodeCommand, spawn } from '../utils';
4
4
  import * as fs from 'fs';
5
5
 
6
6
  export type TscTaskOptions = { [key in keyof ts.CompilerOptions]?: string | boolean | string[] } & {
@@ -25,9 +25,8 @@ export function tscTask(options: TscTaskOptions = {}): TaskFunction {
25
25
  logger.info(`Running ${tscCmd} with ${options.project || options.build}`);
26
26
 
27
27
  const args = argsFromOptions(tscCmd, options);
28
- const cmd = encodeArgs([process.execPath, ...args]).join(' ');
29
- logger.info(`Executing: ${cmd}`);
30
- return exec(cmd);
28
+ logNodeCommand(args);
29
+ return spawn(process.execPath, args, { stdio: 'inherit' });
31
30
  }
32
31
  return Promise.resolve();
33
32
  };
@@ -37,25 +36,7 @@ export function tscTask(options: TscTaskOptions = {}): TaskFunction {
37
36
  * Returns a task that runs the TSC CLI in watch mode.
38
37
  */
39
38
  export function tscWatchTask(options: TscTaskOptions = {}): TaskFunction {
40
- const tscCmd = resolve('typescript/lib/tsc.js');
41
-
42
- if (!tscCmd) {
43
- throw new Error('cannot find tsc');
44
- }
45
-
46
- return function tscWatch() {
47
- options = { ...options, ...getProjectOrBuildOptions(options) };
48
-
49
- if (isValidProject(options)) {
50
- logger.info(`Running ${tscCmd} with ${options.project || options.build} in watch mode`);
51
-
52
- const args = argsFromOptions(tscCmd, options);
53
- const cmd = [...args, '--watch'];
54
- logger.info(encodeArgs(cmd).join(' '));
55
- return spawn(process.execPath, cmd, { stdio: 'inherit' });
56
- }
57
- return Promise.resolve();
58
- };
39
+ return tscTask({ ...options, watch: true });
59
40
  }
60
41
 
61
42
  /**
@@ -81,9 +62,7 @@ function isValidProject(options: TscTaskOptions) {
81
62
  (typeof options.project === 'string' && fs.existsSync(options.project)) ||
82
63
  (typeof options.build === 'string' && fs.existsSync(options.build)) ||
83
64
  (Array.isArray(options.build) &&
84
- options.build.reduce((currentIsValid, buildPath) => {
85
- return currentIsValid && typeof buildPath === 'string' && fs.existsSync(buildPath);
86
- }, true as boolean))
65
+ options.build.every(buildPath => typeof buildPath === 'string' && fs.existsSync(buildPath)))
87
66
  );
88
67
  }
89
68
 
@@ -91,23 +70,21 @@ function isValidProject(options: TscTaskOptions) {
91
70
  * Returns an array of CLI arguments for TSC given the `options`.
92
71
  */
93
72
  function argsFromOptions(tscCmd: string, options: TscTaskOptions): string[] {
94
- const { nodeArgs, ...rest } = options;
73
+ const { nodeArgs, build, ...rest } = options;
74
+
75
+ const args = [...(nodeArgs || []), tscCmd];
76
+ // --build must be the first arg if specified
77
+ const argEntries = [...(build !== undefined ? [['build', build]] : []), ...Object.entries(rest)];
78
+
79
+ for (const [option, optionValue] of argEntries) {
80
+ if (typeof optionValue === 'string') {
81
+ args.push('--' + option, optionValue);
82
+ } else if (typeof optionValue === 'boolean' && optionValue) {
83
+ args.push('--' + option);
84
+ } else if (Array.isArray(optionValue)) {
85
+ args.push('--' + option, ...optionValue);
86
+ }
87
+ }
95
88
 
96
- return [
97
- ...(nodeArgs ? nodeArgs : []),
98
- ...Object.keys(rest).reduce(
99
- (currentArgs, option) => {
100
- const optionValue = options[option];
101
- if (typeof optionValue === 'string') {
102
- return currentArgs.concat(['--' + option, optionValue]);
103
- } else if (typeof optionValue === 'boolean' && optionValue) {
104
- return currentArgs.concat(['--' + option]);
105
- } else if (Array.isArray(optionValue)) {
106
- return currentArgs.concat(['--' + option, ...optionValue]);
107
- }
108
- return currentArgs;
109
- },
110
- [tscCmd],
111
- ),
112
- ];
89
+ return args;
113
90
  }
@@ -1,7 +1,7 @@
1
1
  import { resolve, logger, resolveCwd, TaskFunction } from 'just-task';
2
- import { exec, encodeArgs } from 'just-scripts-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 'just-scripts-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 'just-scripts-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
  }
@@ -0,0 +1,112 @@
1
+ import * as cp from 'child_process';
2
+ import { spawn as crossSpawn } from 'cross-spawn';
3
+ import { logger } from 'just-task';
4
+
5
+ // `exec` and `execSync` were removed due to security issues (keeping filename for history)
6
+
7
+ /**
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.)
11
+ */
12
+ export function encodeArgs(cmdArgs: string[]): string[] {
13
+ return quoteSpaces(cmdArgs);
14
+ }
15
+
16
+ /**
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.)
21
+ */
22
+ function quoteSpaces(cmdArgs: string[]): string[] {
23
+ // Taken from https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js
24
+ // However, we needed to use double quotes because that's the norm in more platforms
25
+ if (!cmdArgs) {
26
+ return cmdArgs;
27
+ }
28
+
29
+ return cmdArgs.map(arg => {
30
+ if (/[^\w/:=-]/.test(arg)) {
31
+ arg = `"${arg.replace(/"/g, '"\\"')}"`;
32
+ arg = arg.replace(/^(?:"")+/g, '').replace(/\\"""/g, '\\"');
33
+ }
34
+
35
+ return arg;
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Log `Running: <process.execPath> <...args>`
41
+ */
42
+ export function logNodeCommand(...args: (string | string[])[]): void {
43
+ logger.info(`Running: ${process.execPath} ${quoteSpaces(args.flat()).join(' ')}`);
44
+ }
45
+
46
+ /**
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.**
53
+ *
54
+ * @param cmd Command to execute
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
+ *
59
+ * @returns Promise which will settle when the command completes. If the promise is rejected, the error will
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`.
65
+ */
66
+ export function spawn(
67
+ cmd: string,
68
+ args: ReadonlyArray<string> = [],
69
+ opts: cp.SpawnOptions & { stdout?: NodeJS.WritableStream; stderr?: NodeJS.WritableStream } = {},
70
+ ): Promise<void> {
71
+ return new Promise((resolve, reject) => {
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);
82
+ if (code) {
83
+ const error = new Error('Command failed: ' + [cmd, ...args].join(' '));
84
+ (error as any).code = code;
85
+ reject(error);
86
+ } else if (signal) {
87
+ const error = new Error(`Command terminated by signal ${signal}: ` + [cmd, ...args].join(' '));
88
+ (error as any).signal = signal;
89
+ reject(error);
90
+ } else {
91
+ resolve();
92
+ }
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);
104
+
105
+ if (opts.stdout) {
106
+ child.stdout?.pipe(opts.stdout);
107
+ }
108
+ if (opts.stderr) {
109
+ child.stderr?.pipe(opts.stderr);
110
+ }
111
+ });
112
+ }
@@ -0,0 +1,4 @@
1
+ export * from './exec';
2
+ export { mergePackageJson } from './mergePackageJson';
3
+ export * from './paths';
4
+ export * from './readPackageJson';