cross-spawn-esm 1.1.3 → 1.2.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,38 @@
1
1
  import { ChildProcess, SpawnOptions, SpawnSyncOptions, SpawnSyncReturns } from "node:child_process";
2
2
  import { NonSharedBuffer } from "node:buffer";
3
+ //#region src/utils/escapes.d.ts
4
+ /**
5
+ * Remove line breaks (`\r` and `\n`) from the given string. Without this, an argument containing a newline could terminate the enclosing
6
+ * command early and inject an arbitrary second command into the shell.
7
+ *
8
+ * @example
9
+ * // Without escaping, cmd.exe would run two commands:
10
+ * // node -e "console.log('ok')
11
+ * // calc.exe"
12
+ * escapeLineBreaks("console.log('ok')\ncalc.exe");
13
+ * //=> "console.log('ok')calc.exe"
14
+ */
15
+ declare function escapeLineBreaks(arg: string): string;
16
+ /**
17
+ * Escape Windows `cmd.exe` shell meta characters (``/([()\][%!^"`<>&|;, *?])/g``) by prefixing each with `^`. Without this, a meta character in a command or argument would be interpreted
18
+ * by the shell, breaking the command or enabling arbitrary command execution.
19
+ *
20
+ * @example
21
+ * // Without escaping, cmd.exe would treat the spaces and `&` as separators:
22
+ * escapeMetaChars('notepad "a & b.txt"');
23
+ * //=> 'notepad^ ^"a^ ^&^ b.txt^"'
24
+ */
25
+ declare function escapeMetaChars(arg: string): string;
26
+ /**
27
+ * Escapes a command name for safe use in a Windows `cmd.exe` invocation.
28
+ */
29
+ declare function escapeCommand(arg: string): string;
30
+ /**
31
+ * Escape a single argument into a fully self-contained token for a Windows
32
+ * `cmd.exe` command line.
33
+ */
34
+ declare function escapeArgument(arg: unknown, doubleEscapeMetaChars?: boolean): string;
35
+ //#endregion
3
36
  //#region src/utils/resolve-command.d.ts
4
37
  interface ParsedCommand {
5
38
  command: string;
@@ -32,18 +65,25 @@ declare function resolveCommandAttempt(parsed: ParsedCommand, withoutPathExt?: b
32
65
  declare function resolveCommand(parsed: ParsedCommand): string | null;
33
66
  //#endregion
34
67
  //#region src/enoent.d.ts
68
+ interface SpawnError extends NodeJS.ErrnoException {
69
+ code: string;
70
+ errno: number;
71
+ path: string;
72
+ syscall: string;
73
+ spawnargs: ReadonlyArray<string>;
74
+ }
35
75
  /**
36
76
  * Builds an ENOENT error for a command that could not be found. Creates
37
77
  * an error whose shape matches what Node's own `spawn`/`spawnSync` would produce
38
78
  * when it fails to locate the command file. This makes the error interchangeable
39
79
  * with a real ENOENT from Node.
40
80
  */
41
- declare function notFoundError(original: ParsedCommand["original"], syscall: "spawn" | "spawnSync"): NodeJS.ErrnoException;
81
+ declare function notFoundError(original: ParsedCommand["original"], syscall: "spawn" | "spawnSync"): SpawnError;
42
82
  /**
43
83
  * Detects the Windows-only fake ENOENT that `cmd.exe` produces. On POSIX or
44
84
  * for any other exit code the error is not applicable, so `null` is returned.
45
85
  */
46
- declare function verifyENOENT(status: number | null, parsed: ParsedCommand, syscall: "spawn" | "spawnSync"): NodeJS.ErrnoException | null;
86
+ declare function verifyENOENT(status: number | null, parsed: ParsedCommand, syscall: "spawn" | "spawnSync"): SpawnError | null;
47
87
  /**
48
88
  * Hooks into a `ChildProcess` `exit` event so that a missing command is
49
89
  * surfaced as an `error` event instead of silently exiting with code `1`.
@@ -98,39 +138,6 @@ declare function readShebang(file: string): string | null;
98
138
  */
99
139
  declare function detectShebang(parsed: ParsedCommand): string | null;
100
140
  //#endregion
101
- //#region src/utils/escapes.d.ts
102
- /**
103
- * Remove line breaks (`\r` and `\n`) from the given string. Without this, an argument containing a newline could terminate the enclosing
104
- * command early and inject an arbitrary second command into the shell.
105
- *
106
- * @example
107
- * // Without escaping, cmd.exe would run two commands:
108
- * // node -e "console.log('ok')
109
- * // calc.exe"
110
- * escapeLineBreaks("console.log('ok')\ncalc.exe");
111
- * //=> "console.log('ok')calc.exe"
112
- */
113
- declare function escapeLineBreaks(arg: string): string;
114
- /**
115
- * Escape Windows `cmd.exe` shell meta characters (``/([()\][%!^"`<>&|;, *?])/g``) by prefixing each with `^`. Without this, a meta character in a command or argument would be interpreted
116
- * by the shell, breaking the command or enabling arbitrary command execution.
117
- *
118
- * @example
119
- * // Without escaping, cmd.exe would treat the spaces and `&` as separators:
120
- * escapeMetaChars('notepad "a & b.txt"');
121
- * //=> 'notepad^ ^"a^ ^&^ b.txt^"'
122
- */
123
- declare function escapeMetaChars(arg: string): string;
124
- /**
125
- * Escapes a command name for safe use in a Windows `cmd.exe` invocation.
126
- */
127
- declare function escapeCommand(arg: string): string;
128
- /**
129
- * Escape a single argument into a fully self-contained token for a Windows
130
- * `cmd.exe` command line.
131
- */
132
- declare function escapeArgument(arg: unknown, doubleEscapeMetaChars?: boolean): string;
133
- //#endregion
134
141
  //#region src/utils/path-key.d.ts
135
142
  interface PathKeyOptions {
136
143
  /**
@@ -139,14 +146,14 @@ interface PathKeyOptions {
139
146
  *
140
147
  * @see https://nodejs.org/api/process.html#process_process_env
141
148
  */
142
- env?: Record<string, string | undefined>;
149
+ readonly env?: NodeJS.ProcessEnv;
143
150
  /**
144
151
  * Get the PATH key for a specific platform.
145
152
  * @default process.platform
146
153
  *
147
154
  * @see https://nodejs.org/api/process.html#process_process_platform
148
155
  */
149
- platform?: NodeJS.Platform;
156
+ readonly platform?: NodeJS.Platform;
150
157
  }
151
158
  /**
152
159
  * Get the [PATH] environment variable key cross-platform.
@@ -227,4 +234,4 @@ export declare const _utils: {
227
234
  pathKey: typeof pathKey;
228
235
  };
229
236
  //#endregion
230
- export type { ParsedCommand, PathKeyOptions };
237
+ export type { ParsedCommand, PathKeyOptions, SpawnError };
package/dist/index.js CHANGED
@@ -12,9 +12,51 @@ const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
12
12
  const executableRegExp = /\.(?:exe)$/i;
13
13
  const cmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
14
14
  //#endregion
15
- //#region src/utils/path-key.ts
16
- function pathKey({ env = _env, platform = _platform } = {}) {
17
- return platform === "win32" ? Object.keys(env).reverse().find((key) => key.toUpperCase() === "PATH") ?? "Path" : "PATH";
15
+ //#region src/utils/escapes.ts
16
+ function escapeLineBreaks(arg) {
17
+ return arg.replace(lineBreaksRegExp, "");
18
+ }
19
+ function escapeMetaChars(arg) {
20
+ return arg.replace(metaCharsRegExp, "^$1");
21
+ }
22
+ function escapeCommand(arg) {
23
+ return escapeMetaChars(escapeLineBreaks(arg));
24
+ }
25
+ function escapeArgument(arg, doubleEscapeMetaChars) {
26
+ let argStr = `${arg}`;
27
+ argStr = escapeLineBreaks(argStr);
28
+ argStr = argStr.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
29
+ argStr = argStr.replace(/(?=(\\+?)?)\1$/, "$1$1");
30
+ argStr = escapeMetaChars(`"${argStr}"`);
31
+ if (doubleEscapeMetaChars) argStr = escapeMetaChars(argStr);
32
+ return argStr;
33
+ }
34
+ //#endregion
35
+ //#region src/enoent.ts
36
+ function notFoundError(original, syscall) {
37
+ return Object.assign(/* @__PURE__ */ new Error(`${syscall} ${original.command} ENOENT`), {
38
+ code: "ENOENT",
39
+ errno: -2,
40
+ syscall: `${syscall} ${original.command}`,
41
+ path: original.command,
42
+ spawnargs: original.args
43
+ });
44
+ }
45
+ function verifyENOENT(status, parsed, syscall) {
46
+ if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, syscall);
47
+ return null;
48
+ }
49
+ function hookChildProcess(cp, parsed) {
50
+ if (!isWin) return;
51
+ const originalEmit = cp.emit;
52
+ cp.emit = function(name, ...args) {
53
+ if (name === "exit") {
54
+ const status = args[0];
55
+ const err = verifyENOENT(status, parsed, "spawn");
56
+ if (err) return originalEmit.call(cp, "error", err);
57
+ }
58
+ return originalEmit.apply(cp, [name, ...args]);
59
+ };
18
60
  }
19
61
  //#endregion
20
62
  //#region src/utils/isexe.ts
@@ -54,7 +96,7 @@ const pathSeparatorRegExp = new RegExp(`[${path.posix.sep}${path.sep === path.po
54
96
  function whichSync(command, { cwd = _cwd, path: optPath = _env.PATH, pathExt: optPathExt = _env.PATHEXT } = {}) {
55
97
  const delimiter = path.delimiter;
56
98
  const dirs = command.match(pathSeparatorRegExp) ? [""] : [...isWin ? [cwd] : [], ...(optPath ?? "").split(delimiter)];
57
- const pathExtExe = isWin ? optPathExt ?? [
99
+ const pathExtExe = isWin ? optPathExt || [
58
100
  ".EXE",
59
101
  ".CMD",
60
102
  ".BAT",
@@ -72,6 +114,11 @@ function whichSync(command, { cwd = _cwd, path: optPath = _env.PATH, pathExt: op
72
114
  return null;
73
115
  }
74
116
  //#endregion
117
+ //#region src/utils/path-key.ts
118
+ function pathKey({ env = _env, platform = _platform } = {}) {
119
+ return platform === "win32" ? Object.keys(env).reverse().find((key) => key.toUpperCase() === "PATH") ?? "Path" : "PATH";
120
+ }
121
+ //#endregion
75
122
  //#region src/utils/resolve-command.ts
76
123
  function resolveCommandAttempt(parsed, withoutPathExt) {
77
124
  const env = parsed.options.env ?? _env;
@@ -85,33 +132,6 @@ function resolveCommand(parsed) {
85
132
  return resolveCommandAttempt(parsed) ?? (isWin ? resolveCommandAttempt(parsed, true) : null);
86
133
  }
87
134
  //#endregion
88
- //#region src/enoent.ts
89
- function notFoundError(original, syscall) {
90
- return Object.assign(/* @__PURE__ */ new Error(`${syscall} ${original.command} ENOENT`), {
91
- code: "ENOENT",
92
- errno: -2,
93
- syscall: `${syscall} ${original.command}`,
94
- path: original.command,
95
- spawnargs: original.args
96
- });
97
- }
98
- function verifyENOENT(status, parsed, syscall) {
99
- if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, syscall);
100
- return null;
101
- }
102
- function hookChildProcess(cp, parsed) {
103
- if (!isWin) return;
104
- const originalEmit = cp.emit;
105
- cp.emit = function(name, ...args) {
106
- if (name === "exit") {
107
- const status = args[0];
108
- const err = verifyENOENT(status, parsed, "spawn");
109
- if (err) return originalEmit.call(cp, "error", err);
110
- }
111
- return originalEmit.apply(cp, [name, ...args]);
112
- };
113
- }
114
- //#endregion
115
135
  //#region src/utils/shebang.ts
116
136
  function shebangCommand(string) {
117
137
  const match = string.match(shebangRegExp);
@@ -144,26 +164,6 @@ function detectShebang(parsed) {
144
164
  return parsed.file;
145
165
  }
146
166
  //#endregion
147
- //#region src/utils/escapes.ts
148
- function escapeLineBreaks(arg) {
149
- return arg.replace(lineBreaksRegExp, "");
150
- }
151
- function escapeMetaChars(arg) {
152
- return arg.replace(metaCharsRegExp, "^$1");
153
- }
154
- function escapeCommand(arg) {
155
- return escapeMetaChars(escapeLineBreaks(arg));
156
- }
157
- function escapeArgument(arg, doubleEscapeMetaChars) {
158
- let argStr = `${arg}`;
159
- argStr = escapeLineBreaks(argStr);
160
- argStr = argStr.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
161
- argStr = argStr.replace(/(?=(\\+?)?)\1$/, "$1$1");
162
- argStr = escapeMetaChars(`"${argStr}"`);
163
- if (doubleEscapeMetaChars) argStr = escapeMetaChars(argStr);
164
- return argStr;
165
- }
166
- //#endregion
167
167
  //#region src/parse.ts
168
168
  function parseNonShell(parsed) {
169
169
  if (!isWin) return parsed;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cross-spawn-esm",
3
3
  "type": "module",
4
- "version": "1.1.3",
4
+ "version": "1.2.0",
5
5
  "description": "ESM version of cross-spawn package",
6
6
  "author": "Timur Bikmuhametov <tim.bic22@gmail.com>",
7
7
  "license": "MIT",
@@ -35,17 +35,18 @@
35
35
  "devDependencies": {
36
36
  "@timbic/eslint-config": "1.2.2",
37
37
  "@timbic/prettier-config": "1.2.3",
38
- "@types/node": "26.6.1",
38
+ "@types/node": "26.6.4",
39
+ "@vitest/coverage-v8": "5.0.3",
39
40
  "bumpp": "12.3.0",
40
- "eslint": "10.11.0",
41
+ "eslint": "10.12.0",
41
42
  "husky": "9.1.7",
42
- "lint-staged": "17.5.1",
43
+ "lint-staged": "17.6.0",
43
44
  "mkdirp": "3.0.1",
44
- "prettier": "3.9.8",
45
+ "prettier": "3.9.9",
45
46
  "rimraf": "6.1.3",
46
47
  "tsdown": "0.23.0",
47
48
  "typescript": "6.0.3",
48
- "vitest": "5.0.1"
49
+ "vitest": "5.0.3"
49
50
  },
50
51
  "lint-staged": {
51
52
  "*/**/*.{js,ts,md,json}": [
@@ -63,6 +64,7 @@
63
64
  "typecheck": "tsc --noEmit",
64
65
  "test": "vitest run",
65
66
  "test:watch": "vitest",
67
+ "test:coverage": "vitest run --coverage",
66
68
  "release": "bumpp"
67
69
  }
68
70
  }