@procedures/shell 0.54.1 → 0.55.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/CHANGELOG.md +17 -0
- package/dist/command/program.d.ts +72 -0
- package/dist/command/program.d.ts.map +1 -0
- package/dist/command/program.js +152 -0
- package/dist/command/program.js.map +1 -0
- package/dist/command/spawn.d.ts +4 -0
- package/dist/command/spawn.d.ts.map +1 -1
- package/dist/command/spawn.js +15 -3
- package/dist/command/spawn.js.map +1 -1
- package/dist/command/tree-kill.d.ts.map +1 -1
- package/dist/command/tree-kill.js +8 -2
- package/dist/command/tree-kill.js.map +1 -1
- package/dist/procedures/shell/which.d.ts.map +1 -1
- package/dist/procedures/shell/which.js +4 -2
- package/dist/procedures/shell/which.js.map +1 -1
- package/package.json +3 -3
- package/src/command/program.ts +177 -0
- package/src/command/spawn.ts +16 -4
- package/src/command/tree-kill.ts +9 -2
- package/src/procedures/shell/which.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @procedures/shell
|
|
2
2
|
|
|
3
|
+
## 0.55.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @procedures/client@0.55.1
|
|
8
|
+
|
|
9
|
+
## 0.55.0
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 2918e97: Two security fixes:
|
|
14
|
+
|
|
15
|
+
- `@procedures/shell`: on Windows, a program name with no folder starts only from a folder of `PATH`, never from the working folder of the call. Before, a call such as "run `git` in this cloned repository" started a `git.exe` from the top folder of the repository. When no folder of `PATH` has the program, the start fails with `ENOENT`, as for a missing program, and the error names the program as before. `taskkill` starts by its full path in the system folder, and `shell.which` gives a match from `PATH` only.
|
|
16
|
+
- `@procedures/fs`: under a root, a link to a missing target is resolved against the real folder of the link. Before, the check resolved a relative target against the text of the path. When an earlier part of the path was a link to a folder, the check said "inside the root" for a write that went outside it. A `..` in a relative target follows the rule of the system: POSIX goes up from the real folder of the name before it, and Windows removes that name as text. The archive procedures and the other users of `@procedures/fs/root` get the same fix.
|
|
17
|
+
- Updated dependencies [fd9c6d4]
|
|
18
|
+
- @procedures/client@0.55.0
|
|
19
|
+
|
|
3
20
|
## 0.54.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file of a program: the one place that answers which file a command starts.
|
|
3
|
+
*
|
|
4
|
+
* On Windows, Node (libuv) looks for a program name with no folder first in the working folder of
|
|
5
|
+
* the child, and only then in the folders of `PATH`. So "run `git` in this cloned repository"
|
|
6
|
+
* started a `git.exe` from the top folder of the repository. The helpers give `spawn` the
|
|
7
|
+
* absolute path from `startFile()`, which searches only the folders of `PATH`. The functions take
|
|
8
|
+
* the operating system, the environment and the file test as inputs, so each system tests them.
|
|
9
|
+
*/
|
|
10
|
+
/** The options of `programPath`. Each one has a default from the host, so the tests can set it. */
|
|
11
|
+
export interface ProgramPathOptions {
|
|
12
|
+
/** The operating system (default: `process.platform`). */
|
|
13
|
+
platform?: NodeJS.Platform | undefined;
|
|
14
|
+
/** The environment of the host, for a child environment with no `PATH` (default: `process.env`). */
|
|
15
|
+
hostEnv?: Readonly<Record<string, string | undefined>> | undefined;
|
|
16
|
+
/** The function that tells if a path is a file (default: `fs.statSync`). */
|
|
17
|
+
isFile?: ((path: string) => boolean) | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The value of `PATH` in an environment that Node gives to a child on Windows. The names of the
|
|
21
|
+
* variables have no fixed case there. Node keeps the first name in sorted order (`PATH` before
|
|
22
|
+
* `Path`), and leaves out a variable with no value.
|
|
23
|
+
*/
|
|
24
|
+
export declare function windowsPathValue(env: Readonly<Record<string, string | undefined>>): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The folders of a `PATH` value on Windows, in order, as libuv reads them: a `;` separates two
|
|
27
|
+
* folders, and quotes around a folder are not part of it. The list leaves out an empty folder and
|
|
28
|
+
* a folder that is not fully qualified, because both lead to the working folder.
|
|
29
|
+
*/
|
|
30
|
+
export declare function windowsPathFolders(value: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* This function gives the file that `command` starts, for `spawn`:
|
|
33
|
+
* - On POSIX, and for a command with a folder part, it gives `command`. The system finds it as before.
|
|
34
|
+
* - On Windows, for a name with no folder, it gives the absolute path of the first match in the
|
|
35
|
+
* folders of the `PATH` of `env`, the environment of the child. It never looks in the working
|
|
36
|
+
* folder. It tries the names that libuv tries: the exact name when it has an extension, then
|
|
37
|
+
* the name with `.com`, then the name with `.exe`.
|
|
38
|
+
* - It gives `undefined` when no folder of `PATH` has the program.
|
|
39
|
+
*
|
|
40
|
+
* When `env` has no `PATH`, libuv uses the `PATH` of the host. This function does the same.
|
|
41
|
+
*/
|
|
42
|
+
export declare function programPath(command: string, env: Readonly<Record<string, string | undefined>>, options?: ProgramPathOptions): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* This function gives the file that `startChild` gives to `spawn`. It is the result of
|
|
45
|
+
* `programPath()` when there is one. When no folder of `PATH` has the program, it gives a fully
|
|
46
|
+
* qualified path that does not exist, in the system folder. So the start fails with ENOENT, as for
|
|
47
|
+
* a missing program, and the system does not look in the working folder.
|
|
48
|
+
*/
|
|
49
|
+
export declare function startFile(command: string, env: Readonly<Record<string, string | undefined>>, options?: ProgramPathOptions): string;
|
|
50
|
+
/**
|
|
51
|
+
* When `file` is not `command`, this function changes each start error of `child` to name
|
|
52
|
+
* `command`, as Node names it for a start by name: the `message` (`spawn git ENOENT`), the
|
|
53
|
+
* `stack`, the `syscall` and the `path`. So the error of a call is the same as before the lookup.
|
|
54
|
+
* A child with no other listener of `error` still throws the error, as Node does.
|
|
55
|
+
*/
|
|
56
|
+
export declare function nameStartErrors(child: NodeJS.EventEmitter, command: string, file: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* The program and the arguments of `shell.which` for a command. On Windows, `where name` gives a
|
|
59
|
+
* match in the working folder first. `where $PATH:name` looks only in the folders of `PATH`, as a
|
|
60
|
+
* start of a program by its name does. A command with a folder part goes to `where` as it is.
|
|
61
|
+
*/
|
|
62
|
+
export declare function whichCall(command: string, platform?: NodeJS.Platform): {
|
|
63
|
+
program: string;
|
|
64
|
+
args: string[];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The path of a program of the system folder of Windows, for example `taskkill.exe`. The folder
|
|
68
|
+
* comes from `SystemRoot`, then `windir`, then `C:\Windows`. A value that is not fully qualified
|
|
69
|
+
* is not used.
|
|
70
|
+
*/
|
|
71
|
+
export declare function systemProgram(name: string, env?: Readonly<Record<string, string | undefined>>): string;
|
|
72
|
+
//# sourceMappingURL=program.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../src/command/program.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,mGAAmG;AACnG,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvC,oGAAoG;IACpG,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IACnE,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;CAClD;AAeD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,GAAG,SAAS,CAGtG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB1D;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACjD,OAAO,GAAE,kBAAuB,GAC/B,MAAM,GAAG,SAAS,CAgBpB;AAQD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACjD,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAGR;AAUD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAa/F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAG5H;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAAG,MAAM,CAGnH"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file of a program: the one place that answers which file a command starts.
|
|
3
|
+
*
|
|
4
|
+
* On Windows, Node (libuv) looks for a program name with no folder first in the working folder of
|
|
5
|
+
* the child, and only then in the folders of `PATH`. So "run `git` in this cloned repository"
|
|
6
|
+
* started a `git.exe` from the top folder of the repository. The helpers give `spawn` the
|
|
7
|
+
* absolute path from `startFile()`, which searches only the folders of `PATH`. The functions take
|
|
8
|
+
* the operating system, the environment and the file test as inputs, so each system tests them.
|
|
9
|
+
*/
|
|
10
|
+
import { statSync } from "node:fs";
|
|
11
|
+
import { win32 } from "node:path";
|
|
12
|
+
function isFile(path) {
|
|
13
|
+
try {
|
|
14
|
+
return statSync(path).isFile();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** True for a path with a drive and a separator (`C:\x`), or a UNC path (`\\server\share`). */
|
|
21
|
+
function fullyQualified(path) {
|
|
22
|
+
return /^(?:[a-zA-Z]:[\\/]|[\\/]{2}[^\\/])/.test(path);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The value of `PATH` in an environment that Node gives to a child on Windows. The names of the
|
|
26
|
+
* variables have no fixed case there. Node keeps the first name in sorted order (`PATH` before
|
|
27
|
+
* `Path`), and leaves out a variable with no value.
|
|
28
|
+
*/
|
|
29
|
+
export function windowsPathValue(env) {
|
|
30
|
+
const key = Object.keys(env).sort().find((name) => name.toUpperCase() === "PATH");
|
|
31
|
+
return key === undefined ? undefined : env[key];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The folders of a `PATH` value on Windows, in order, as libuv reads them: a `;` separates two
|
|
35
|
+
* folders, and quotes around a folder are not part of it. The list leaves out an empty folder and
|
|
36
|
+
* a folder that is not fully qualified, because both lead to the working folder.
|
|
37
|
+
*/
|
|
38
|
+
export function windowsPathFolders(value) {
|
|
39
|
+
const folders = [];
|
|
40
|
+
let start = 0;
|
|
41
|
+
while (start <= value.length) {
|
|
42
|
+
const quote = value[start];
|
|
43
|
+
let from = start;
|
|
44
|
+
if (quote === '"' || quote === "'") {
|
|
45
|
+
const end = value.indexOf(quote, start + 1);
|
|
46
|
+
from = end === -1 ? value.length : end;
|
|
47
|
+
}
|
|
48
|
+
const semicolon = value.indexOf(";", from);
|
|
49
|
+
const end = semicolon === -1 ? value.length : semicolon;
|
|
50
|
+
const folder = value.slice(start, end).replace(/^["']/, "").replace(/["']$/, "");
|
|
51
|
+
if (fullyQualified(folder))
|
|
52
|
+
folders.push(folder);
|
|
53
|
+
start = end + 1;
|
|
54
|
+
}
|
|
55
|
+
return folders;
|
|
56
|
+
}
|
|
57
|
+
/** True when a command has a folder part, for example `.\x`, `bin/x` or `C:x`. */
|
|
58
|
+
function hasFolder(command) {
|
|
59
|
+
return /[\\/:]/.test(command);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* This function gives the file that `command` starts, for `spawn`:
|
|
63
|
+
* - On POSIX, and for a command with a folder part, it gives `command`. The system finds it as before.
|
|
64
|
+
* - On Windows, for a name with no folder, it gives the absolute path of the first match in the
|
|
65
|
+
* folders of the `PATH` of `env`, the environment of the child. It never looks in the working
|
|
66
|
+
* folder. It tries the names that libuv tries: the exact name when it has an extension, then
|
|
67
|
+
* the name with `.com`, then the name with `.exe`.
|
|
68
|
+
* - It gives `undefined` when no folder of `PATH` has the program.
|
|
69
|
+
*
|
|
70
|
+
* When `env` has no `PATH`, libuv uses the `PATH` of the host. This function does the same.
|
|
71
|
+
*/
|
|
72
|
+
export function programPath(command, env, options = {}) {
|
|
73
|
+
if ((options.platform ?? process.platform) !== "win32")
|
|
74
|
+
return command;
|
|
75
|
+
// An empty name stays as it is: spawn gives its own error for it
|
|
76
|
+
if (command === "" || hasFolder(command))
|
|
77
|
+
return command;
|
|
78
|
+
if (command === ".")
|
|
79
|
+
return undefined;
|
|
80
|
+
const dot = command.indexOf(".");
|
|
81
|
+
const names = [...(dot !== -1 && dot < command.length - 1 ? [command] : []), `${command}.com`, `${command}.exe`];
|
|
82
|
+
const value = windowsPathValue(env) ?? windowsPathValue(options.hostEnv ?? process.env) ?? "";
|
|
83
|
+
const test = options.isFile ?? isFile;
|
|
84
|
+
for (const folder of windowsPathFolders(value)) {
|
|
85
|
+
for (const name of names) {
|
|
86
|
+
const path = win32.join(folder, name);
|
|
87
|
+
if (test(path))
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A folder name that no file system of Windows allows: NTFS refuses `<` and `>` in a name. A path
|
|
95
|
+
* through it does not exist, so a start of it fails with ENOENT and searches no other folder.
|
|
96
|
+
*/
|
|
97
|
+
const NO_FOLDER = "<not in PATH>";
|
|
98
|
+
/**
|
|
99
|
+
* This function gives the file that `startChild` gives to `spawn`. It is the result of
|
|
100
|
+
* `programPath()` when there is one. When no folder of `PATH` has the program, it gives a fully
|
|
101
|
+
* qualified path that does not exist, in the system folder. So the start fails with ENOENT, as for
|
|
102
|
+
* a missing program, and the system does not look in the working folder.
|
|
103
|
+
*/
|
|
104
|
+
export function startFile(command, env, options = {}) {
|
|
105
|
+
// No join: a join would remove a name such as `..`, and the path would be a folder that exists
|
|
106
|
+
return programPath(command, env, options) ?? `${systemProgram(NO_FOLDER, options.hostEnv ?? process.env)}\\${command}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* When `file` is not `command`, this function changes each start error of `child` to name
|
|
110
|
+
* `command`, as Node names it for a start by name: the `message` (`spawn git ENOENT`), the
|
|
111
|
+
* `stack`, the `syscall` and the `path`. So the error of a call is the same as before the lookup.
|
|
112
|
+
* A child with no other listener of `error` still throws the error, as Node does.
|
|
113
|
+
*/
|
|
114
|
+
export function nameStartErrors(child, command, file) {
|
|
115
|
+
if (file === command)
|
|
116
|
+
return;
|
|
117
|
+
const fileCall = `spawn ${file}`;
|
|
118
|
+
const commandCall = `spawn ${command}`;
|
|
119
|
+
child.prependListener("error", (error) => {
|
|
120
|
+
if (error.syscall === fileCall) {
|
|
121
|
+
error.syscall = commandCall;
|
|
122
|
+
if (error.path === file)
|
|
123
|
+
error.path = command;
|
|
124
|
+
if (error.message.startsWith(fileCall))
|
|
125
|
+
error.message = commandCall + error.message.slice(fileCall.length);
|
|
126
|
+
if (error.stack !== undefined)
|
|
127
|
+
error.stack = error.stack.replace(fileCall, commandCall);
|
|
128
|
+
}
|
|
129
|
+
if (child.listenerCount("error") === 1)
|
|
130
|
+
throw error;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The program and the arguments of `shell.which` for a command. On Windows, `where name` gives a
|
|
135
|
+
* match in the working folder first. `where $PATH:name` looks only in the folders of `PATH`, as a
|
|
136
|
+
* start of a program by its name does. A command with a folder part goes to `where` as it is.
|
|
137
|
+
*/
|
|
138
|
+
export function whichCall(command, platform = process.platform) {
|
|
139
|
+
if (platform !== "win32")
|
|
140
|
+
return { program: "which", args: [command] };
|
|
141
|
+
return { program: "where", args: [hasFolder(command) ? command : `$PATH:${command}`] };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The path of a program of the system folder of Windows, for example `taskkill.exe`. The folder
|
|
145
|
+
* comes from `SystemRoot`, then `windir`, then `C:\Windows`. A value that is not fully qualified
|
|
146
|
+
* is not used.
|
|
147
|
+
*/
|
|
148
|
+
export function systemProgram(name, env = process.env) {
|
|
149
|
+
const root = [env["SystemRoot"], env["windir"]].find((value) => value !== undefined && fullyQualified(value));
|
|
150
|
+
return win32.join(root ?? "C:\\Windows", "System32", name);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../src/command/program.ts"],"names":[],"mappings":"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"}
|
package/dist/command/spawn.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export interface SpawnOptions {
|
|
|
19
19
|
* This function starts `command` with `shell: false` and `windowsHide`. On POSIX the child gets
|
|
20
20
|
* its own process group, so `killTree` reaches its descendants. The child is recorded until it
|
|
21
21
|
* ends, so the end of the host kills it.
|
|
22
|
+
*
|
|
23
|
+
* On Windows, a command with no folder starts from a folder of `PATH` only (`startFile`), never
|
|
24
|
+
* from the working folder. When no folder of `PATH` has it, the start fails with ENOENT, as for
|
|
25
|
+
* a missing program. The error names the command, as before.
|
|
22
26
|
*/
|
|
23
27
|
export declare function startChild(command: string, options: SpawnOptions, stdin: "pipe" | "ignore"): ChildProcess;
|
|
24
28
|
/** The text of an error that kept a program from starting, for example `spawn x ENOENT`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/command/spawn.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAS,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/command/spawn.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAyC,MAAM,oBAAoB,CAAC;AAIrG,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACrC,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,YAAY,CAmBzG;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD"}
|
package/dist/command/spawn.js
CHANGED
|
@@ -2,22 +2,34 @@
|
|
|
2
2
|
* Start a child process the one way the helpers allow: an argument list, no shell, no window.
|
|
3
3
|
*/
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { nameStartErrors, startFile } from "./program.js";
|
|
5
6
|
import { OWN_PROCESS_GROUP, track } from "./tree-kill.js";
|
|
6
7
|
/**
|
|
7
8
|
* This function starts `command` with `shell: false` and `windowsHide`. On POSIX the child gets
|
|
8
9
|
* its own process group, so `killTree` reaches its descendants. The child is recorded until it
|
|
9
10
|
* ends, so the end of the host kills it.
|
|
11
|
+
*
|
|
12
|
+
* On Windows, a command with no folder starts from a folder of `PATH` only (`startFile`), never
|
|
13
|
+
* from the working folder. When no folder of `PATH` has it, the start fails with ENOENT, as for
|
|
14
|
+
* a missing program. The error names the command, as before.
|
|
10
15
|
*/
|
|
11
16
|
export function startChild(command, options, stdin) {
|
|
12
|
-
const
|
|
17
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
18
|
+
const file = startFile(command, env);
|
|
19
|
+
const spawnOptions = {
|
|
13
20
|
cwd: options.cwd,
|
|
14
|
-
env
|
|
21
|
+
env,
|
|
15
22
|
shell: false,
|
|
16
23
|
windowsHide: true,
|
|
17
24
|
detached: OWN_PROCESS_GROUP,
|
|
18
25
|
stdio: [stdin, "pipe", "pipe"],
|
|
26
|
+
// The program gets the name of the call as its first argument, as before
|
|
27
|
+
argv0: command,
|
|
19
28
|
...(options.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
20
|
-
}
|
|
29
|
+
};
|
|
30
|
+
const args = [...(options.args ?? [])];
|
|
31
|
+
const child = spawn(file, args, spawnOptions);
|
|
32
|
+
nameStartErrors(child, command, file);
|
|
21
33
|
track(child);
|
|
22
34
|
return child;
|
|
23
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/command/spawn.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/command/spawn.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAA4D,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAgB1D;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAqB,EAAE,KAAwB;IACzF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IAC3E,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,YAAY,GAAqB;QACrC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG;QACH,KAAK,EAAE,KAAK;QACZ,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,iBAAiB;QAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;QAC9B,yEAAyE;QACzE,KAAK,EAAE,OAAO;QACd,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChF,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;IAC9C,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-kill.d.ts","sourceRoot":"","sources":["../../src/command/tree-kill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"tree-kill.d.ts","sourceRoot":"","sources":["../../src/command/tree-kill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAW5E;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAAkB,CAAC;AAMnD,iGAAiG;AACjG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,GAAE,MAAM,CAAC,OAAmB,GAAG,IAAI,CAgBtF;AAsCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAOrD;AAgBD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAU/C"}
|
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
* still runs at the exit.
|
|
17
17
|
*/
|
|
18
18
|
import { execFile, spawnSync } from "node:child_process";
|
|
19
|
+
import { systemProgram } from "./program.js";
|
|
19
20
|
const WINDOWS = process.platform === "win32";
|
|
21
|
+
/**
|
|
22
|
+
* The full path of `taskkill.exe` in the system folder. A bare name started a `taskkill.exe` of
|
|
23
|
+
* the working folder of the host first.
|
|
24
|
+
*/
|
|
25
|
+
const TASKKILL = WINDOWS ? systemProgram("taskkill.exe") : "taskkill";
|
|
20
26
|
/**
|
|
21
27
|
* True when a child must start with `detached: true`. On POSIX, `detached` puts the child in a
|
|
22
28
|
* new process group, which `killTree` needs. On Windows, `detached` opens a new console.
|
|
@@ -31,7 +37,7 @@ export function killTree(child, signal = "SIGTERM") {
|
|
|
31
37
|
return;
|
|
32
38
|
const pid = child.pid;
|
|
33
39
|
if (WINDOWS) {
|
|
34
|
-
execFile(
|
|
40
|
+
execFile(TASKKILL, ["/T", "/F", "/PID", String(pid)], { windowsHide: true }, (error) => {
|
|
35
41
|
// taskkill fails when the process ended in the meantime, or when taskkill is missing
|
|
36
42
|
if (error && running(child))
|
|
37
43
|
child.kill(signal);
|
|
@@ -53,7 +59,7 @@ function killTreeSync(child) {
|
|
|
53
59
|
const pid = child.pid;
|
|
54
60
|
try {
|
|
55
61
|
if (WINDOWS) {
|
|
56
|
-
spawnSync(
|
|
62
|
+
spawnSync(TASKKILL, ["/T", "/F", "/PID", String(pid)], { windowsHide: true, stdio: "ignore" });
|
|
57
63
|
}
|
|
58
64
|
else {
|
|
59
65
|
process.kill(-pid, "SIGKILL");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-kill.js","sourceRoot":"","sources":["../../src/command/tree-kill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAqB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"tree-kill.js","sourceRoot":"","sources":["../../src/command/tree-kill.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"which.d.ts","sourceRoot":"","sources":["../../../src/procedures/shell/which.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"which.d.ts","sourceRoot":"","sources":["../../../src/procedures/shell/which.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAExE,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,GAAG,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,GACzC,OAAO,CAAC,gBAAgB,CAAC,CAc3B"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Find the full path to a command.
|
|
5
5
|
*/
|
|
6
|
+
import { whichCall } from "../../command/program.js";
|
|
6
7
|
import { runCommand } from "../../command/run.js";
|
|
7
8
|
export async function shellWhich(input, ctx) {
|
|
8
9
|
// The name is one argument of where/which, with no shell. (Before, `where ${command}` ran in a
|
|
@@ -10,8 +11,9 @@ export async function shellWhich(input, ctx) {
|
|
|
10
11
|
if (input.command.startsWith("-")) {
|
|
11
12
|
return { path: null, found: false };
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
const
|
|
14
|
+
// On Windows, only the folders of PATH, as the start of a program by its name
|
|
15
|
+
const { program, args } = whichCall(input.command);
|
|
16
|
+
const result = await runCommand(program, { args, signal: ctx?.signal, timeout: 30_000 });
|
|
15
17
|
if (!result.success) {
|
|
16
18
|
return { path: null, found: false };
|
|
17
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"which.js","sourceRoot":"","sources":["../../../src/procedures/shell/which.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB,EACtB,GAA0C;IAE1C,+FAA+F;IAC/F,8EAA8E;IAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,
|
|
1
|
+
{"version":3,"file":"which.js","sourceRoot":"","sources":["../../../src/procedures/shell/which.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB,EACtB,GAA0C;IAE1C,+FAA+F;IAC/F,8EAA8E;IAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;IACD,8EAA8E;IAC9E,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACzF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClE,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAChF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/shell",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.55.1",
|
|
5
5
|
"description": "Safe command execution for TypeScript: shell.run with argv and no shell, shell.stream line streaming with backpressure, process-tree kill, timeouts and output caps.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"shell",
|
|
@@ -72,13 +72,13 @@
|
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@mark1russell7/cue": "^2.1.1",
|
|
75
|
-
"@procedures/fs": "0.
|
|
75
|
+
"@procedures/fs": "0.55.1",
|
|
76
76
|
"@types/node": "^22.0.0",
|
|
77
77
|
"typescript": "^7.0.2",
|
|
78
78
|
"vitest": "^5.0.3"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@procedures/client": "0.
|
|
81
|
+
"@procedures/client": "0.55.1",
|
|
82
82
|
"zod": "^4.6.5"
|
|
83
83
|
},
|
|
84
84
|
"client": {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file of a program: the one place that answers which file a command starts.
|
|
3
|
+
*
|
|
4
|
+
* On Windows, Node (libuv) looks for a program name with no folder first in the working folder of
|
|
5
|
+
* the child, and only then in the folders of `PATH`. So "run `git` in this cloned repository"
|
|
6
|
+
* started a `git.exe` from the top folder of the repository. The helpers give `spawn` the
|
|
7
|
+
* absolute path from `startFile()`, which searches only the folders of `PATH`. The functions take
|
|
8
|
+
* the operating system, the environment and the file test as inputs, so each system tests them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { statSync } from "node:fs";
|
|
12
|
+
import { win32 } from "node:path";
|
|
13
|
+
|
|
14
|
+
/** The options of `programPath`. Each one has a default from the host, so the tests can set it. */
|
|
15
|
+
export interface ProgramPathOptions {
|
|
16
|
+
/** The operating system (default: `process.platform`). */
|
|
17
|
+
platform?: NodeJS.Platform | undefined;
|
|
18
|
+
/** The environment of the host, for a child environment with no `PATH` (default: `process.env`). */
|
|
19
|
+
hostEnv?: Readonly<Record<string, string | undefined>> | undefined;
|
|
20
|
+
/** The function that tells if a path is a file (default: `fs.statSync`). */
|
|
21
|
+
isFile?: ((path: string) => boolean) | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isFile(path: string): boolean {
|
|
25
|
+
try {
|
|
26
|
+
return statSync(path).isFile();
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** True for a path with a drive and a separator (`C:\x`), or a UNC path (`\\server\share`). */
|
|
33
|
+
function fullyQualified(path: string): boolean {
|
|
34
|
+
return /^(?:[a-zA-Z]:[\\/]|[\\/]{2}[^\\/])/.test(path);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The value of `PATH` in an environment that Node gives to a child on Windows. The names of the
|
|
39
|
+
* variables have no fixed case there. Node keeps the first name in sorted order (`PATH` before
|
|
40
|
+
* `Path`), and leaves out a variable with no value.
|
|
41
|
+
*/
|
|
42
|
+
export function windowsPathValue(env: Readonly<Record<string, string | undefined>>): string | undefined {
|
|
43
|
+
const key = Object.keys(env).sort().find((name) => name.toUpperCase() === "PATH");
|
|
44
|
+
return key === undefined ? undefined : env[key];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The folders of a `PATH` value on Windows, in order, as libuv reads them: a `;` separates two
|
|
49
|
+
* folders, and quotes around a folder are not part of it. The list leaves out an empty folder and
|
|
50
|
+
* a folder that is not fully qualified, because both lead to the working folder.
|
|
51
|
+
*/
|
|
52
|
+
export function windowsPathFolders(value: string): string[] {
|
|
53
|
+
const folders: string[] = [];
|
|
54
|
+
let start = 0;
|
|
55
|
+
while (start <= value.length) {
|
|
56
|
+
const quote = value[start];
|
|
57
|
+
let from = start;
|
|
58
|
+
if (quote === '"' || quote === "'") {
|
|
59
|
+
const end = value.indexOf(quote, start + 1);
|
|
60
|
+
from = end === -1 ? value.length : end;
|
|
61
|
+
}
|
|
62
|
+
const semicolon = value.indexOf(";", from);
|
|
63
|
+
const end = semicolon === -1 ? value.length : semicolon;
|
|
64
|
+
const folder = value.slice(start, end).replace(/^["']/, "").replace(/["']$/, "");
|
|
65
|
+
if (fullyQualified(folder)) folders.push(folder);
|
|
66
|
+
start = end + 1;
|
|
67
|
+
}
|
|
68
|
+
return folders;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when a command has a folder part, for example `.\x`, `bin/x` or `C:x`. */
|
|
72
|
+
function hasFolder(command: string): boolean {
|
|
73
|
+
return /[\\/:]/.test(command);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* This function gives the file that `command` starts, for `spawn`:
|
|
78
|
+
* - On POSIX, and for a command with a folder part, it gives `command`. The system finds it as before.
|
|
79
|
+
* - On Windows, for a name with no folder, it gives the absolute path of the first match in the
|
|
80
|
+
* folders of the `PATH` of `env`, the environment of the child. It never looks in the working
|
|
81
|
+
* folder. It tries the names that libuv tries: the exact name when it has an extension, then
|
|
82
|
+
* the name with `.com`, then the name with `.exe`.
|
|
83
|
+
* - It gives `undefined` when no folder of `PATH` has the program.
|
|
84
|
+
*
|
|
85
|
+
* When `env` has no `PATH`, libuv uses the `PATH` of the host. This function does the same.
|
|
86
|
+
*/
|
|
87
|
+
export function programPath(
|
|
88
|
+
command: string,
|
|
89
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
90
|
+
options: ProgramPathOptions = {},
|
|
91
|
+
): string | undefined {
|
|
92
|
+
if ((options.platform ?? process.platform) !== "win32") return command;
|
|
93
|
+
// An empty name stays as it is: spawn gives its own error for it
|
|
94
|
+
if (command === "" || hasFolder(command)) return command;
|
|
95
|
+
if (command === ".") return undefined;
|
|
96
|
+
const dot = command.indexOf(".");
|
|
97
|
+
const names = [...(dot !== -1 && dot < command.length - 1 ? [command] : []), `${command}.com`, `${command}.exe`];
|
|
98
|
+
const value = windowsPathValue(env) ?? windowsPathValue(options.hostEnv ?? process.env) ?? "";
|
|
99
|
+
const test = options.isFile ?? isFile;
|
|
100
|
+
for (const folder of windowsPathFolders(value)) {
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
const path = win32.join(folder, name);
|
|
103
|
+
if (test(path)) return path;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A folder name that no file system of Windows allows: NTFS refuses `<` and `>` in a name. A path
|
|
111
|
+
* through it does not exist, so a start of it fails with ENOENT and searches no other folder.
|
|
112
|
+
*/
|
|
113
|
+
const NO_FOLDER = "<not in PATH>";
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* This function gives the file that `startChild` gives to `spawn`. It is the result of
|
|
117
|
+
* `programPath()` when there is one. When no folder of `PATH` has the program, it gives a fully
|
|
118
|
+
* qualified path that does not exist, in the system folder. So the start fails with ENOENT, as for
|
|
119
|
+
* a missing program, and the system does not look in the working folder.
|
|
120
|
+
*/
|
|
121
|
+
export function startFile(
|
|
122
|
+
command: string,
|
|
123
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
124
|
+
options: ProgramPathOptions = {},
|
|
125
|
+
): string {
|
|
126
|
+
// No join: a join would remove a name such as `..`, and the path would be a folder that exists
|
|
127
|
+
return programPath(command, env, options) ?? `${systemProgram(NO_FOLDER, options.hostEnv ?? process.env)}\\${command}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The part of an error of Node that names the file of a start. */
|
|
131
|
+
interface StartError {
|
|
132
|
+
message: string;
|
|
133
|
+
stack?: string | undefined;
|
|
134
|
+
syscall?: string | undefined;
|
|
135
|
+
path?: string | undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* When `file` is not `command`, this function changes each start error of `child` to name
|
|
140
|
+
* `command`, as Node names it for a start by name: the `message` (`spawn git ENOENT`), the
|
|
141
|
+
* `stack`, the `syscall` and the `path`. So the error of a call is the same as before the lookup.
|
|
142
|
+
* A child with no other listener of `error` still throws the error, as Node does.
|
|
143
|
+
*/
|
|
144
|
+
export function nameStartErrors(child: NodeJS.EventEmitter, command: string, file: string): void {
|
|
145
|
+
if (file === command) return;
|
|
146
|
+
const fileCall = `spawn ${file}`;
|
|
147
|
+
const commandCall = `spawn ${command}`;
|
|
148
|
+
child.prependListener("error", (error: StartError) => {
|
|
149
|
+
if (error.syscall === fileCall) {
|
|
150
|
+
error.syscall = commandCall;
|
|
151
|
+
if (error.path === file) error.path = command;
|
|
152
|
+
if (error.message.startsWith(fileCall)) error.message = commandCall + error.message.slice(fileCall.length);
|
|
153
|
+
if (error.stack !== undefined) error.stack = error.stack.replace(fileCall, commandCall);
|
|
154
|
+
}
|
|
155
|
+
if (child.listenerCount("error") === 1) throw error;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The program and the arguments of `shell.which` for a command. On Windows, `where name` gives a
|
|
161
|
+
* match in the working folder first. `where $PATH:name` looks only in the folders of `PATH`, as a
|
|
162
|
+
* start of a program by its name does. A command with a folder part goes to `where` as it is.
|
|
163
|
+
*/
|
|
164
|
+
export function whichCall(command: string, platform: NodeJS.Platform = process.platform): { program: string; args: string[] } {
|
|
165
|
+
if (platform !== "win32") return { program: "which", args: [command] };
|
|
166
|
+
return { program: "where", args: [hasFolder(command) ? command : `$PATH:${command}`] };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The path of a program of the system folder of Windows, for example `taskkill.exe`. The folder
|
|
171
|
+
* comes from `SystemRoot`, then `windir`, then `C:\Windows`. A value that is not fully qualified
|
|
172
|
+
* is not used.
|
|
173
|
+
*/
|
|
174
|
+
export function systemProgram(name: string, env: Readonly<Record<string, string | undefined>> = process.env): string {
|
|
175
|
+
const root = [env["SystemRoot"], env["windir"]].find((value): value is string => value !== undefined && fullyQualified(value));
|
|
176
|
+
return win32.join(root ?? "C:\\Windows", "System32", name);
|
|
177
|
+
}
|
package/src/command/spawn.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Start a child process the one way the helpers allow: an argument list, no shell, no window.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
5
|
+
import { spawn, type ChildProcess, type SpawnOptions as NodeSpawnOptions } from "node:child_process";
|
|
6
|
+
import { nameStartErrors, startFile } from "./program.js";
|
|
6
7
|
import { OWN_PROCESS_GROUP, track } from "./tree-kill.js";
|
|
7
8
|
|
|
8
9
|
export interface SpawnOptions {
|
|
@@ -23,17 +24,28 @@ export interface SpawnOptions {
|
|
|
23
24
|
* This function starts `command` with `shell: false` and `windowsHide`. On POSIX the child gets
|
|
24
25
|
* its own process group, so `killTree` reaches its descendants. The child is recorded until it
|
|
25
26
|
* ends, so the end of the host kills it.
|
|
27
|
+
*
|
|
28
|
+
* On Windows, a command with no folder starts from a folder of `PATH` only (`startFile`), never
|
|
29
|
+
* from the working folder. When no folder of `PATH` has it, the start fails with ENOENT, as for
|
|
30
|
+
* a missing program. The error names the command, as before.
|
|
26
31
|
*/
|
|
27
32
|
export function startChild(command: string, options: SpawnOptions, stdin: "pipe" | "ignore"): ChildProcess {
|
|
28
|
-
const
|
|
33
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
34
|
+
const file = startFile(command, env);
|
|
35
|
+
const spawnOptions: NodeSpawnOptions = {
|
|
29
36
|
cwd: options.cwd,
|
|
30
|
-
env
|
|
37
|
+
env,
|
|
31
38
|
shell: false,
|
|
32
39
|
windowsHide: true,
|
|
33
40
|
detached: OWN_PROCESS_GROUP,
|
|
34
41
|
stdio: [stdin, "pipe", "pipe"],
|
|
42
|
+
// The program gets the name of the call as its first argument, as before
|
|
43
|
+
argv0: command,
|
|
35
44
|
...(options.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
36
|
-
}
|
|
45
|
+
};
|
|
46
|
+
const args = [...(options.args ?? [])];
|
|
47
|
+
const child = spawn(file, args, spawnOptions);
|
|
48
|
+
nameStartErrors(child, command, file);
|
|
37
49
|
track(child);
|
|
38
50
|
return child;
|
|
39
51
|
}
|
package/src/command/tree-kill.ts
CHANGED
|
@@ -17,9 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { execFile, spawnSync, type ChildProcess } from "node:child_process";
|
|
20
|
+
import { systemProgram } from "./program.js";
|
|
20
21
|
|
|
21
22
|
const WINDOWS = process.platform === "win32";
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The full path of `taskkill.exe` in the system folder. A bare name started a `taskkill.exe` of
|
|
26
|
+
* the working folder of the host first.
|
|
27
|
+
*/
|
|
28
|
+
const TASKKILL = WINDOWS ? systemProgram("taskkill.exe") : "taskkill";
|
|
29
|
+
|
|
23
30
|
/**
|
|
24
31
|
* True when a child must start with `detached: true`. On POSIX, `detached` puts the child in a
|
|
25
32
|
* new process group, which `killTree` needs. On Windows, `detached` opens a new console.
|
|
@@ -35,7 +42,7 @@ export function killTree(child: ChildProcess, signal: NodeJS.Signals = "SIGTERM"
|
|
|
35
42
|
if (!running(child)) return;
|
|
36
43
|
const pid = child.pid!;
|
|
37
44
|
if (WINDOWS) {
|
|
38
|
-
execFile(
|
|
45
|
+
execFile(TASKKILL, ["/T", "/F", "/PID", String(pid)], { windowsHide: true }, (error) => {
|
|
39
46
|
// taskkill fails when the process ended in the meantime, or when taskkill is missing
|
|
40
47
|
if (error && running(child)) child.kill(signal);
|
|
41
48
|
});
|
|
@@ -55,7 +62,7 @@ function killTreeSync(child: ChildProcess): void {
|
|
|
55
62
|
const pid = child.pid!;
|
|
56
63
|
try {
|
|
57
64
|
if (WINDOWS) {
|
|
58
|
-
spawnSync(
|
|
65
|
+
spawnSync(TASKKILL, ["/T", "/F", "/PID", String(pid)], { windowsHide: true, stdio: "ignore" });
|
|
59
66
|
} else {
|
|
60
67
|
process.kill(-pid, "SIGKILL");
|
|
61
68
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Find the full path to a command.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { whichCall } from "../../command/program.js";
|
|
7
8
|
import { runCommand } from "../../command/run.js";
|
|
8
9
|
import type { ShellWhichInput, ShellWhichOutput } from "../../types.js";
|
|
9
10
|
|
|
@@ -16,8 +17,9 @@ export async function shellWhich(
|
|
|
16
17
|
if (input.command.startsWith("-")) {
|
|
17
18
|
return { path: null, found: false };
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
const
|
|
20
|
+
// On Windows, only the folders of PATH, as the start of a program by its name
|
|
21
|
+
const { program, args } = whichCall(input.command);
|
|
22
|
+
const result = await runCommand(program, { args, signal: ctx?.signal, timeout: 30_000 });
|
|
21
23
|
if (!result.success) {
|
|
22
24
|
return { path: null, found: false };
|
|
23
25
|
}
|