@oh-my-pi/pi-utils 17.1.3 → 17.1.4
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 +9 -0
- package/dist/types/postmortem.d.ts +3 -6
- package/dist/types/procmgr.d.ts +32 -3
- package/dist/types/worker-host.d.ts +4 -0
- package/package.json +2 -2
- package/src/postmortem.ts +29 -23
- package/src/procmgr.ts +84 -49
- package/src/ptree.ts +1 -0
- package/src/worker-host.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.4] - 2026-07-26
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed postmortem signal and fatal shutdown exits being intercepted by temporary `process.exit` guards during extension startup ([#6488](https://github.com/can1357/oh-my-pi/issues/6488)).
|
|
10
|
+
- Corrected Windows shell resolution errors to identify the active global, project, overlay, or runtime source for `shellPath` instead of directing every user to the retired `settings.json` file ([#6579](https://github.com/can1357/oh-my-pi/issues/6579)).
|
|
11
|
+
- Contained timed-out child lifecycle rejections so `ptree` callers cannot leak an unhandled `TimeoutError` after settling ([#6635](https://github.com/can1357/oh-my-pi/issues/6635)).
|
|
12
|
+
- Fixed an invalid configured `shellPath` being silently masked whenever an earlier caller had already resolved a shell in the same process; the guidance error now surfaces regardless of cache state.
|
|
13
|
+
|
|
5
14
|
## [17.0.9] - 2026-07-23
|
|
6
15
|
|
|
7
16
|
### Breaking Changes
|
|
@@ -49,10 +49,7 @@ export declare function markExpectedCleanupError<T extends object>(reason: T): T
|
|
|
49
49
|
export declare function isExpectedCleanupError(reason: unknown): boolean;
|
|
50
50
|
/**
|
|
51
51
|
* Register an interceptor consulted before an unhandled rejection tears the
|
|
52
|
-
* process down.
|
|
53
|
-
* reporting and the process continues. Used by embedded script runtimes (JS
|
|
54
|
-
* eval cells) whose user code can float rejections the host must not die for.
|
|
55
|
-
* Returns an unregister function.
|
|
52
|
+
* process down. A consuming interceptor owns reporting and keeps the process alive.
|
|
56
53
|
*/
|
|
57
54
|
export declare function interceptUnhandledRejections(interceptor: (reason: unknown) => boolean): () => void;
|
|
58
55
|
/**
|
|
@@ -68,9 +65,9 @@ export declare function register(id: string, callback: (reason: Reason) => void
|
|
|
68
65
|
*/
|
|
69
66
|
export declare function cleanup(): Promise<void>;
|
|
70
67
|
/**
|
|
71
|
-
* Runs all cleanup callbacks and exits.
|
|
68
|
+
* Runs all cleanup callbacks and exits through the current `process.exit`.
|
|
72
69
|
*
|
|
73
|
-
* In main thread: waits for stdout drain, then calls process.exit()
|
|
70
|
+
* In main thread: waits for stdout drain, then calls `process.exit()`.
|
|
74
71
|
* In workers: runs cleanup only (process.exit would kill entire process).
|
|
75
72
|
*/
|
|
76
73
|
export declare function quit(code?: number): Promise<void>;
|
package/dist/types/procmgr.d.ts
CHANGED
|
@@ -5,23 +5,52 @@ export interface ShellConfig {
|
|
|
5
5
|
env: Record<string, string>;
|
|
6
6
|
prefix: string | undefined;
|
|
7
7
|
}
|
|
8
|
+
/** Identifies the settings source users should edit when shell resolution fails. */
|
|
9
|
+
export interface ShellConfigOptions {
|
|
10
|
+
/** File path or runtime layer that supplied the active shell setting. */
|
|
11
|
+
configSource?: string;
|
|
12
|
+
}
|
|
8
13
|
/**
|
|
9
14
|
* Check if a shell binary is executable.
|
|
10
15
|
*/
|
|
11
16
|
export declare function isExecutable(path: string): boolean;
|
|
17
|
+
/** Whether the shell is Windows cmd.exe (spawn paths must use `/c`, not `-c`). */
|
|
18
|
+
export declare function isCmdShell(shell: string): boolean;
|
|
12
19
|
/**
|
|
13
20
|
* Resolve a basic shell (bash or sh) as fallback.
|
|
14
21
|
*/
|
|
15
22
|
export declare function resolveBasicShell(): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the external shell to advertise on Windows.
|
|
25
|
+
*
|
|
26
|
+
* A host bash is OPTIONAL: bash tool commands always execute in the embedded
|
|
27
|
+
* brush-core shell. The resolved binary only serves the spawn-a-shell paths
|
|
28
|
+
* (interactive PTY sessions, ACP client terminals, SHELL env), so this
|
|
29
|
+
* prefers a real Git Bash when one exists and otherwise falls back to
|
|
30
|
+
* cmd.exe — it never fails.
|
|
31
|
+
*
|
|
32
|
+
* Search order:
|
|
33
|
+
* 1. Git for Windows install roots (machine + per-user installers)
|
|
34
|
+
* 2. scoop installs — scoop's git manifest sets GIT_INSTALL_ROOT and shims
|
|
35
|
+
* sh.exe/git.exe but never bash.exe, so PATH lookup alone misses it
|
|
36
|
+
* 3. bash.exe on PATH (Cygwin, MSYS2, ...)
|
|
37
|
+
* 4. sh.exe on PATH (Git for Windows' sh.exe is bash; prefer a sibling
|
|
38
|
+
* bash.exe when present)
|
|
39
|
+
* 5. cmd.exe from ComSpec
|
|
40
|
+
*
|
|
41
|
+
* Exported for tests; `env` overrides Bun.env-based discovery.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveWindowsShell(env?: Record<string, string | undefined>): string;
|
|
16
44
|
/**
|
|
17
45
|
* Get shell configuration based on platform.
|
|
18
46
|
* Resolution order:
|
|
19
|
-
* 1. User-specified shellPath
|
|
20
|
-
* 2. On Windows: Git Bash
|
|
47
|
+
* 1. User-specified shellPath from the active settings source
|
|
48
|
+
* 2. On Windows: Git Bash / bash / sh discovery, then cmd.exe (see
|
|
49
|
+
* {@link resolveWindowsShell}) — never fails
|
|
21
50
|
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
22
51
|
* 4. Fallback: sh
|
|
23
52
|
*/
|
|
24
|
-
export declare function getShellConfig(customShellPath?: string): ShellConfig;
|
|
53
|
+
export declare function getShellConfig(customShellPath?: string, options?: ShellConfigOptions): ShellConfig;
|
|
25
54
|
/**
|
|
26
55
|
* Check if a process is running.
|
|
27
56
|
*/
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** Prefix reserved for argv selectors dispatched by the shared CLI worker host. */
|
|
2
|
+
export declare const WORKER_HOST_SELECTOR_PREFIX = "__omp_worker_";
|
|
3
|
+
/** Whether an argv value selects a worker hosted by the shared CLI entrypoint. */
|
|
4
|
+
export declare function isWorkerHostSelector(value: string | undefined): value is string;
|
|
1
5
|
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
2
6
|
export declare function declareWorkerHostEntry(): void;
|
|
3
7
|
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-utils",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.4",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
34
|
+
"@oh-my-pi/pi-natives": "17.1.4",
|
|
35
35
|
"handlebars": "^4.7.9",
|
|
36
36
|
"winston": "^3.19.0",
|
|
37
37
|
"winston-daily-rotate-file": "^5.0.0"
|
package/src/postmortem.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import inspector from "node:inspector";
|
|
11
11
|
import { isMainThread } from "node:worker_threads";
|
|
12
|
-
import
|
|
12
|
+
import * as logger from "./logger";
|
|
13
13
|
import { restoreTerminalStderr } from "./stderr-guard";
|
|
14
14
|
|
|
15
15
|
// Cleanup reasons, in order of priority/meaning.
|
|
@@ -29,6 +29,8 @@ const callbackList: ((reason: Reason) => Promise<void> | void)[] = [];
|
|
|
29
29
|
// Tracks cleanup run state (to prevent recursion/reentry issues)
|
|
30
30
|
let cleanupStage: "idle" | "running" | "complete" = "idle";
|
|
31
31
|
const CLEANUP_DEADLINE_MS = 10_000;
|
|
32
|
+
const exitProcess =
|
|
33
|
+
typeof process.reallyExit === "function" ? process.reallyExit.bind(process) : process.exit.bind(process);
|
|
32
34
|
let cleanupPromise: Promise<void> | undefined;
|
|
33
35
|
let stdioDisconnectRegistrations = 0;
|
|
34
36
|
|
|
@@ -149,18 +151,12 @@ export function isExpectedCleanupError(reason: unknown): boolean {
|
|
|
149
151
|
return false;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
/**
|
|
153
|
-
* Interceptors consulted by the global `unhandledRejection` handler before the
|
|
154
|
-
* fatal path. See {@link interceptUnhandledRejections}.
|
|
155
|
-
*/
|
|
154
|
+
/** Interceptors consulted by the global `unhandledRejection` handler before the fatal path. */
|
|
156
155
|
const rejectionInterceptors = new Set<(reason: unknown) => boolean>();
|
|
157
156
|
|
|
158
157
|
/**
|
|
159
158
|
* Register an interceptor consulted before an unhandled rejection tears the
|
|
160
|
-
* process down.
|
|
161
|
-
* reporting and the process continues. Used by embedded script runtimes (JS
|
|
162
|
-
* eval cells) whose user code can float rejections the host must not die for.
|
|
163
|
-
* Returns an unregister function.
|
|
159
|
+
* process down. A consuming interceptor owns reporting and keeps the process alive.
|
|
164
160
|
*/
|
|
165
161
|
export function interceptUnhandledRejections(interceptor: (reason: unknown) => boolean): () => void {
|
|
166
162
|
rejectionInterceptors.add(interceptor);
|
|
@@ -177,7 +173,7 @@ function formatFatalError(label: string, err: Error): string {
|
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
async function exitAfterFatal(label: string, logMessage: string, err: Error, reason: Reason): Promise<void> {
|
|
180
|
-
const forcedExit = setTimeout(() =>
|
|
176
|
+
const forcedExit = setTimeout(() => exitProcess(1), CLEANUP_DEADLINE_MS);
|
|
181
177
|
try {
|
|
182
178
|
restoreTerminalStderr();
|
|
183
179
|
// A revoked terminal can make stream writes raise another fatal error. Use
|
|
@@ -189,7 +185,7 @@ async function exitAfterFatal(label: string, logMessage: string, err: Error, rea
|
|
|
189
185
|
await runCleanup(reason);
|
|
190
186
|
} finally {
|
|
191
187
|
clearTimeout(forcedExit);
|
|
192
|
-
|
|
188
|
+
exitProcess(1);
|
|
193
189
|
}
|
|
194
190
|
}
|
|
195
191
|
|
|
@@ -197,7 +193,7 @@ if (isMainThread) {
|
|
|
197
193
|
process
|
|
198
194
|
.on("SIGINT", async () => {
|
|
199
195
|
await runCleanup(Reason.SIGINT);
|
|
200
|
-
|
|
196
|
+
exitProcess(130); // 128 + SIGINT (2)
|
|
201
197
|
})
|
|
202
198
|
.on("SIGUSR1", () => {
|
|
203
199
|
if (inspectorOpened) return;
|
|
@@ -231,7 +227,7 @@ if (isMainThread) {
|
|
|
231
227
|
}
|
|
232
228
|
if (brokenPipeSource === "stdio-write" && stdioDisconnectRegistrations > 0) {
|
|
233
229
|
logger.warn("Stdio peer disconnected; shutting down gracefully", { err });
|
|
234
|
-
await
|
|
230
|
+
await runQuit(0, "native");
|
|
235
231
|
return;
|
|
236
232
|
}
|
|
237
233
|
if (isExpectedCleanupError(reason)) {
|
|
@@ -254,11 +250,11 @@ if (isMainThread) {
|
|
|
254
250
|
})
|
|
255
251
|
.on("SIGTERM", async () => {
|
|
256
252
|
await runCleanup(Reason.SIGTERM);
|
|
257
|
-
|
|
253
|
+
exitProcess(143); // 128 + SIGTERM (15)
|
|
258
254
|
})
|
|
259
255
|
.on("SIGHUP", async () => {
|
|
260
256
|
await runCleanup(Reason.SIGHUP);
|
|
261
|
-
|
|
257
|
+
exitProcess(129); // 128 + SIGHUP (1)
|
|
262
258
|
});
|
|
263
259
|
} else {
|
|
264
260
|
// Worker thread: only register exit handler for cleanup.
|
|
@@ -323,13 +319,7 @@ export function cleanup(): Promise<void> {
|
|
|
323
319
|
return runCleanup(Reason.MANUAL);
|
|
324
320
|
}
|
|
325
321
|
|
|
326
|
-
|
|
327
|
-
* Runs all cleanup callbacks and exits.
|
|
328
|
-
*
|
|
329
|
-
* In main thread: waits for stdout drain, then calls process.exit().
|
|
330
|
-
* In workers: runs cleanup only (process.exit would kill entire process).
|
|
331
|
-
*/
|
|
332
|
-
export async function quit(code: number = 0): Promise<void> {
|
|
322
|
+
async function runQuit(code: number, exitMode: "guarded" | "native"): Promise<void> {
|
|
333
323
|
await runCleanup(Reason.MANUAL);
|
|
334
324
|
|
|
335
325
|
if (!isMainThread) {
|
|
@@ -341,5 +331,21 @@ export async function quit(code: number = 0): Promise<void> {
|
|
|
341
331
|
process.stdout.once("drain", resolve);
|
|
342
332
|
await Promise.race([promise, Bun.sleep(5000)]);
|
|
343
333
|
}
|
|
344
|
-
|
|
334
|
+
|
|
335
|
+
switch (exitMode) {
|
|
336
|
+
case "guarded":
|
|
337
|
+
return process.exit(code);
|
|
338
|
+
case "native":
|
|
339
|
+
return exitProcess(code);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Runs all cleanup callbacks and exits through the current `process.exit`.
|
|
345
|
+
*
|
|
346
|
+
* In main thread: waits for stdout drain, then calls `process.exit()`.
|
|
347
|
+
* In workers: runs cleanup only (process.exit would kill entire process).
|
|
348
|
+
*/
|
|
349
|
+
export function quit(code: number = 0): Promise<void> {
|
|
350
|
+
return runQuit(code, "guarded");
|
|
345
351
|
}
|
package/src/procmgr.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { Process, ProcessStatus } from "@oh-my-pi/pi-natives";
|
|
4
4
|
import type { Subprocess } from "bun";
|
|
5
|
+
import { getAgentDir, MAIN_CONFIG_FILENAMES } from "./dirs";
|
|
5
6
|
import { $env, filterChildShellEnv } from "./env";
|
|
6
7
|
import { $which } from "./which";
|
|
7
8
|
|
|
@@ -11,6 +12,12 @@ export interface ShellConfig {
|
|
|
11
12
|
env: Record<string, string>;
|
|
12
13
|
prefix: string | undefined;
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
/** Identifies the settings source users should edit when shell resolution fails. */
|
|
17
|
+
export interface ShellConfigOptions {
|
|
18
|
+
/** File path or runtime layer that supplied the active shell setting. */
|
|
19
|
+
configSource?: string;
|
|
20
|
+
}
|
|
14
21
|
let cachedShellConfig: ShellConfig | null = null;
|
|
15
22
|
|
|
16
23
|
/**
|
|
@@ -42,14 +49,22 @@ function buildSpawnEnv(shell: string): Record<string, string> {
|
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
|
-
* Get shell args
|
|
46
|
-
*
|
|
52
|
+
* Get shell args for the resolved shell.
|
|
53
|
+
* cmd.exe takes `/c`; POSIX shells take `-c`, with `-l` unless
|
|
54
|
+
* PI_BASH_NO_LOGIN / CLAUDE_BASH_NO_LOGIN is set.
|
|
47
55
|
*/
|
|
48
|
-
function getShellArgs(): string[] {
|
|
56
|
+
function getShellArgs(shell: string): string[] {
|
|
57
|
+
if (isCmdShell(shell)) return ["/c"];
|
|
49
58
|
const noLogin = $env.PI_BASH_NO_LOGIN || $env.CLAUDE_BASH_NO_LOGIN;
|
|
50
59
|
return noLogin ? ["-c"] : ["-l", "-c"];
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
/** Whether the shell is Windows cmd.exe (spawn paths must use `/c`, not `-c`). */
|
|
63
|
+
export function isCmdShell(shell: string): boolean {
|
|
64
|
+
const basename = shell.replace(/\\/g, "/").split("/").pop()?.toLowerCase();
|
|
65
|
+
return basename === "cmd.exe" || basename === "cmd";
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
/**
|
|
54
69
|
* Get shell prefix for wrapping commands (profilers, strace, etc.).
|
|
55
70
|
*/
|
|
@@ -63,7 +78,7 @@ function getShellPrefix(): string | undefined {
|
|
|
63
78
|
function buildConfig(shell: string): ShellConfig {
|
|
64
79
|
return {
|
|
65
80
|
shell,
|
|
66
|
-
args: getShellArgs(),
|
|
81
|
+
args: getShellArgs(shell),
|
|
67
82
|
env: buildSpawnEnv(shell),
|
|
68
83
|
prefix: getShellPrefix(),
|
|
69
84
|
};
|
|
@@ -93,63 +108,83 @@ export function resolveBasicShell(): string | undefined {
|
|
|
93
108
|
return undefined;
|
|
94
109
|
}
|
|
95
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the external shell to advertise on Windows.
|
|
113
|
+
*
|
|
114
|
+
* A host bash is OPTIONAL: bash tool commands always execute in the embedded
|
|
115
|
+
* brush-core shell. The resolved binary only serves the spawn-a-shell paths
|
|
116
|
+
* (interactive PTY sessions, ACP client terminals, SHELL env), so this
|
|
117
|
+
* prefers a real Git Bash when one exists and otherwise falls back to
|
|
118
|
+
* cmd.exe — it never fails.
|
|
119
|
+
*
|
|
120
|
+
* Search order:
|
|
121
|
+
* 1. Git for Windows install roots (machine + per-user installers)
|
|
122
|
+
* 2. scoop installs — scoop's git manifest sets GIT_INSTALL_ROOT and shims
|
|
123
|
+
* sh.exe/git.exe but never bash.exe, so PATH lookup alone misses it
|
|
124
|
+
* 3. bash.exe on PATH (Cygwin, MSYS2, ...)
|
|
125
|
+
* 4. sh.exe on PATH (Git for Windows' sh.exe is bash; prefer a sibling
|
|
126
|
+
* bash.exe when present)
|
|
127
|
+
* 5. cmd.exe from ComSpec
|
|
128
|
+
*
|
|
129
|
+
* Exported for tests; `env` overrides Bun.env-based discovery.
|
|
130
|
+
*/
|
|
131
|
+
export function resolveWindowsShell(env: Record<string, string | undefined> = Bun.env): string {
|
|
132
|
+
const gitRoots = [
|
|
133
|
+
env.ProgramFiles && path.join(env.ProgramFiles, "Git"),
|
|
134
|
+
env["ProgramFiles(x86)"] && path.join(env["ProgramFiles(x86)"], "Git"),
|
|
135
|
+
env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, "Programs", "Git"),
|
|
136
|
+
env.GIT_INSTALL_ROOT,
|
|
137
|
+
env.SCOOP && path.join(env.SCOOP, "apps", "git", "current"),
|
|
138
|
+
env.USERPROFILE && path.join(env.USERPROFILE, "scoop", "apps", "git", "current"),
|
|
139
|
+
];
|
|
140
|
+
for (const root of gitRoots) {
|
|
141
|
+
if (!root) continue;
|
|
142
|
+
const candidate = path.join(root, "bin", "bash.exe");
|
|
143
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const bashOnPath = $which("bash.exe");
|
|
147
|
+
if (bashOnPath) return bashOnPath;
|
|
148
|
+
|
|
149
|
+
const shOnPath = $which("sh.exe");
|
|
150
|
+
if (shOnPath) {
|
|
151
|
+
const siblingBash = path.join(path.dirname(shOnPath), "bash.exe");
|
|
152
|
+
return fs.existsSync(siblingBash) ? siblingBash : shOnPath;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return env.ComSpec || env.COMSPEC || "C:\\Windows\\System32\\cmd.exe";
|
|
156
|
+
}
|
|
157
|
+
|
|
96
158
|
/**
|
|
97
159
|
* Get shell configuration based on platform.
|
|
98
160
|
* Resolution order:
|
|
99
|
-
* 1. User-specified shellPath
|
|
100
|
-
* 2. On Windows: Git Bash
|
|
161
|
+
* 1. User-specified shellPath from the active settings source
|
|
162
|
+
* 2. On Windows: Git Bash / bash / sh discovery, then cmd.exe (see
|
|
163
|
+
* {@link resolveWindowsShell}) — never fails
|
|
101
164
|
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
102
165
|
* 4. Fallback: sh
|
|
103
166
|
*/
|
|
104
|
-
export function getShellConfig(customShellPath?: string): ShellConfig {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// 1. Check user-specified shell path
|
|
167
|
+
export function getShellConfig(customShellPath?: string, options: ShellConfigOptions = {}): ShellConfig {
|
|
168
|
+
const configSource = options.configSource ?? path.join(getAgentDir(), MAIN_CONFIG_FILENAMES[0]);
|
|
169
|
+
// 1. Check user-specified shell path. Validated even on the cached path so a
|
|
170
|
+
// broken shellPath surfaces its guidance error instead of being masked by an
|
|
171
|
+
// earlier successful resolution in the same process.
|
|
110
172
|
if (customShellPath) {
|
|
111
|
-
if (fs.existsSync(customShellPath)) {
|
|
173
|
+
if (!fs.existsSync(customShellPath)) {
|
|
174
|
+
throw new Error(`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ${configSource}`);
|
|
175
|
+
}
|
|
176
|
+
if (cachedShellConfig?.shell !== customShellPath) {
|
|
112
177
|
cachedShellConfig = buildConfig(customShellPath);
|
|
113
|
-
return cachedShellConfig;
|
|
114
178
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
179
|
+
return cachedShellConfig;
|
|
180
|
+
}
|
|
181
|
+
if (cachedShellConfig) {
|
|
182
|
+
return cachedShellConfig;
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
if (process.platform === "win32") {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const programFiles = Bun.env.ProgramFiles;
|
|
124
|
-
if (programFiles) {
|
|
125
|
-
paths.push(`${programFiles}\\Git\\bin\\bash.exe`);
|
|
126
|
-
}
|
|
127
|
-
const programFilesX86 = Bun.env["ProgramFiles(x86)"];
|
|
128
|
-
if (programFilesX86) {
|
|
129
|
-
paths.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const path of paths) {
|
|
133
|
-
if (fs.existsSync(path)) {
|
|
134
|
-
cachedShellConfig = buildConfig(path);
|
|
135
|
-
return cachedShellConfig;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)
|
|
140
|
-
const bashOnPath = $which("bash.exe");
|
|
141
|
-
if (bashOnPath) {
|
|
142
|
-
cachedShellConfig = buildConfig(bashOnPath);
|
|
143
|
-
return cachedShellConfig;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
throw new Error(
|
|
147
|
-
`No bash shell found. Options:\n` +
|
|
148
|
-
` 1. Install Git for Windows: https://git-scm.com/download/win\n` +
|
|
149
|
-
` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\n` +
|
|
150
|
-
` 3. Set shellPath in ~/.omp/agent/settings.json\n\n` +
|
|
151
|
-
`Searched Git Bash in:\n${paths.map(p => ` ${p}`).join("\n")}`,
|
|
152
|
-
);
|
|
186
|
+
cachedShellConfig = buildConfig(resolveWindowsShell());
|
|
187
|
+
return cachedShellConfig;
|
|
153
188
|
}
|
|
154
189
|
|
|
155
190
|
// Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
|
package/src/ptree.ts
CHANGED
|
@@ -309,6 +309,7 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
309
309
|
|
|
310
310
|
attachTimeout(ms: number): void {
|
|
311
311
|
if (ms <= 0 || this.proc.killed) return;
|
|
312
|
+
this.#exited.catch(() => {});
|
|
312
313
|
Promise.race([
|
|
313
314
|
Bun.sleep(ms).then(() => true),
|
|
314
315
|
this.proc.exited.then(
|
package/src/worker-host.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { stripWindowsExtendedLengthPathPrefix } from "./path";
|
|
2
2
|
|
|
3
|
+
/** Prefix reserved for argv selectors dispatched by the shared CLI worker host. */
|
|
4
|
+
export const WORKER_HOST_SELECTOR_PREFIX = "__omp_worker_";
|
|
5
|
+
|
|
6
|
+
/** Whether an argv value selects a worker hosted by the shared CLI entrypoint. */
|
|
7
|
+
export function isWorkerHostSelector(value: string | undefined): value is string {
|
|
8
|
+
return value?.startsWith(WORKER_HOST_SELECTOR_PREFIX) ?? false;
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
12
|
* Main-module path declared by self-dispatching CLI entrypoints — entries
|
|
5
13
|
* whose top-level argv handling routes hidden `__omp_*` worker selectors.
|