@wlv-zedd/dsh-chatgpt-web 1.0.0 → 1.0.2
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/LICENSE +21 -21
- package/README.md +140 -140
- package/cordis.patch.yml +4 -4
- package/lib/cli.js +13269 -11860
- package/lib/plugin.js +3 -50
- package/package.json +89 -88
- package/src/adapters/base.ts +16 -16
- package/src/adapters/chatgpt-web/adapter-error.ts +59 -59
- package/src/adapters/chatgpt-web/browser-helper-main.ts +513 -513
- package/src/adapters/chatgpt-web/browser-helper-prompt-selection.ts +27 -27
- package/src/adapters/chatgpt-web/browser-worker.ts +4944 -4944
- package/src/adapters/chatgpt-web/codex-rollout-environment.ts +628 -628
- package/src/adapters/chatgpt-web/compaction-handoff.ts +533 -533
- package/src/adapters/chatgpt-web/compaction-transaction.ts +142 -142
- package/src/adapters/chatgpt-web/concurrency.ts +6 -6
- package/src/adapters/chatgpt-web/conversation-key.ts +58 -58
- package/src/adapters/chatgpt-web/environment.ts +669 -669
- package/src/adapters/chatgpt-web/index.ts +1544 -1544
- package/src/adapters/chatgpt-web/input-tokens.ts +74 -74
- package/src/adapters/chatgpt-web/launcher-helper-client.ts +695 -695
- package/src/adapters/chatgpt-web/markdown.ts +418 -418
- package/src/adapters/chatgpt-web/mcp-main.ts +25 -25
- package/src/adapters/chatgpt-web/mcp-server.ts +933 -933
- package/src/adapters/chatgpt-web/model.ts +70 -70
- package/src/adapters/chatgpt-web/native-compaction-control.ts +74 -74
- package/src/adapters/chatgpt-web/output-validation.ts +62 -62
- package/src/adapters/chatgpt-web/process-line-writer.ts +46 -46
- package/src/adapters/chatgpt-web/prompt.ts +702 -702
- package/src/adapters/chatgpt-web/retry-policy.ts +73 -73
- package/src/adapters/chatgpt-web/rolling-checkpoint.ts +384 -384
- package/src/adapters/chatgpt-web/thread-environment.ts +238 -238
- package/src/adapters/chatgpt-web/tool-stream-parser.ts +1 -1
- package/src/adapters/chatgpt-web/turn-broker.ts +1481 -1481
- package/src/adapters/chatgpt-web/turn-execution.ts +816 -816
- package/src/adapters/chatgpt-web/turn-progress.ts +292 -292
- package/src/adapters/chatgpt-web/usage.ts +121 -121
- package/src/adapters/image.ts +9 -9
- package/src/bridge.ts +1083 -1083
- package/src/browser-login.ts +521 -521
- package/src/chatgpt-session.ts +240 -240
- package/src/chatgpt-web-models.ts +400 -400
- package/src/cli.ts +568 -568
- package/src/codex-integration-document.ts +824 -824
- package/src/codex-integration-journal.ts +212 -212
- package/src/codex-integration-route.ts +515 -515
- package/src/codex-integration-shared.ts +332 -332
- package/src/codex-integration.ts +529 -529
- package/src/codex-interrupt-hook.ts +158 -158
- package/src/config.ts +616 -616
- package/src/dev-chat/cli.ts +432 -432
- package/src/dev-chat/constants.ts +3 -3
- package/src/dev-chat/driver.ts +655 -655
- package/src/dev-chat/profile.ts +223 -223
- package/src/dev-chat/session.ts +287 -287
- package/src/dev-chat/transport.ts +54 -54
- package/src/doctor.ts +237 -237
- package/src/event-queue.ts +45 -45
- package/src/http-body.ts +30 -30
- package/src/launcher-browser-host.ts +695 -695
- package/src/lib/errors.ts +281 -281
- package/src/lib/token-estimate.ts +42 -42
- package/src/login-helper.cjs +1 -1
- package/src/model-catalog.ts +197 -197
- package/src/native-passthrough.ts +261 -261
- package/src/plugin.ts +191 -191
- package/src/process.ts +45 -45
- package/src/responses/compaction.ts +199 -199
- package/src/responses/parser.ts +633 -633
- package/src/responses/reasoning-envelope.ts +49 -49
- package/src/responses/schema.ts +172 -172
- package/src/responses/state.ts +230 -230
- package/src/server.ts +1111 -1111
- package/src/service.ts +315 -315
- package/src/setup.ts +671 -671
- package/src/stall-timeout.ts +23 -23
- package/src/tunnel-service.ts +160 -160
- package/src/tunnel.ts +417 -417
- package/src/turndown-plugin-gfm.d.ts +5 -5
- package/src/types.ts +307 -307
- package/src/usage/totals.ts +12 -12
- package/src/version.ts +1 -1
package/src/plugin.ts
CHANGED
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
export interface CordisContext {
|
|
8
|
-
effect?: (cb: () => void | Promise<void> | (() => void) | (() => Promise<void>)) => void;
|
|
9
|
-
logger?: (name: string) => {
|
|
10
|
-
info(msg: string): void;
|
|
11
|
-
warn(msg: string): void;
|
|
12
|
-
error(msg: string): void;
|
|
13
|
-
debug(msg: string): void;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const name = "dsh-chatgpt-web";
|
|
18
|
-
export const inject = [];
|
|
19
|
-
|
|
20
|
-
export interface ChatGPTWebPluginConfig {
|
|
21
|
-
/** Host to bind or check for health (default: 127.0.0.1). */
|
|
22
|
-
host?: string;
|
|
23
|
-
/** Port the sidecar listens on (default: 17841). */
|
|
24
|
-
port?: number;
|
|
25
|
-
/** Automatically start the sidecar daemon if not running (default: true). */
|
|
26
|
-
autoStart?: boolean;
|
|
27
|
-
/** Maximum milliseconds to wait for the sidecar to report ready (default: 30000). */
|
|
28
|
-
readyTimeoutMs?: number;
|
|
29
|
-
/** Explicit path to the bun executable (optional). */
|
|
30
|
-
bunPath?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
import { pathToFileURL } from "node:url";
|
|
34
|
-
|
|
35
|
-
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
36
|
-
|
|
37
|
-
function resolveLauncher(customBunPath?: string): { cmd: string; args: string[] } {
|
|
38
|
-
const libCliPath = resolve(ROOT_DIR, "lib", "cli.js");
|
|
39
|
-
if (existsSync(libCliPath)) {
|
|
40
|
-
return {
|
|
41
|
-
cmd: process.execPath,
|
|
42
|
-
args: [libCliPath, "serve"],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Development fallback: check for bun or tsx
|
|
47
|
-
if (customBunPath && existsSync(customBunPath)) {
|
|
48
|
-
return { cmd: customBunPath, args: ["run", "src/cli.ts", "serve"] };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const winBun = join(homedir(), ".bun", "bin", "bun.exe");
|
|
52
|
-
if (existsSync(winBun)) {
|
|
53
|
-
return { cmd: winBun, args: ["run", "src/cli.ts", "serve"] };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const tsxPath = resolve(ROOT_DIR, "../deepseek-harness/node_modules/tsx/dist/esm/index.mjs");
|
|
57
|
-
if (existsSync(tsxPath)) {
|
|
58
|
-
return {
|
|
59
|
-
cmd: process.execPath,
|
|
60
|
-
args: ["--import", pathToFileURL(tsxPath).href, "src/cli.ts", "serve"],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return { cmd: process.execPath, args: ["src/cli.ts", "serve"] };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function isSidecarHealthy(host: string, port: number): Promise<boolean> {
|
|
68
|
-
try {
|
|
69
|
-
const controller = new AbortController();
|
|
70
|
-
const timer = setTimeout(() => controller.abort(), 1500);
|
|
71
|
-
const res = await fetch(`http://${host}:${port}/healthz`, {
|
|
72
|
-
signal: controller.signal,
|
|
73
|
-
});
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
if (!res.ok) return false;
|
|
76
|
-
const body = (await res.json()) as { status?: string };
|
|
77
|
-
return body.status === "ok";
|
|
78
|
-
} catch {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function apply(ctx: CordisContext, config: ChatGPTWebPluginConfig = {}): void {
|
|
84
|
-
const host = config.host || "127.0.0.1";
|
|
85
|
-
const port = config.port || 17841;
|
|
86
|
-
const autoStart = config.autoStart !== false;
|
|
87
|
-
const readyTimeoutMs = config.readyTimeoutMs || 30_000;
|
|
88
|
-
const logger = typeof ctx.logger === "function" ? ctx.logger("chatgpt-web") : console;
|
|
89
|
-
|
|
90
|
-
let spawnedProcess: ChildProcess | undefined;
|
|
91
|
-
|
|
92
|
-
const startDaemon = async () => {
|
|
93
|
-
const alreadyHealthy = await isSidecarHealthy(host, port);
|
|
94
|
-
if (alreadyHealthy) {
|
|
95
|
-
logger.info(`[dsh-chatgpt-web] Sidecar already running and healthy at http://${host}:${port}/v1`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!autoStart) {
|
|
100
|
-
logger.warn(`[dsh-chatgpt-web] Sidecar is offline and autoStart is false. Start it manually with 'bun run src/cli.ts serve'`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const launcher = resolveLauncher(config.bunPath);
|
|
105
|
-
logger.info(`[dsh-chatgpt-web] Starting dsh-chatgpt-web daemon via ${launcher.cmd} at http://${host}:${port}/v1...`);
|
|
106
|
-
|
|
107
|
-
const child = spawn(launcher.cmd, launcher.args, {
|
|
108
|
-
cwd: ROOT_DIR,
|
|
109
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
110
|
-
windowsHide: true,
|
|
111
|
-
env: {
|
|
112
|
-
...process.env,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
spawnedProcess = child;
|
|
117
|
-
|
|
118
|
-
child.stdout?.on("data", (chunk: Buffer) => {
|
|
119
|
-
const text = chunk.toString().trim();
|
|
120
|
-
if (text && typeof logger.debug === "function") logger.debug(`[sidecar] ${text}`);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
child.stderr?.on("data", (chunk: Buffer) => {
|
|
124
|
-
const text = chunk.toString().trim();
|
|
125
|
-
if (text && typeof logger.debug === "function") logger.debug(`[sidecar:err] ${text}`);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
child.on("error", (err) => {
|
|
129
|
-
logger.error(`[dsh-chatgpt-web] Failed to launch sidecar process: ${err.message}`);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
child.on("exit", (code, signal) => {
|
|
133
|
-
if (code !== 0 && code !== null) {
|
|
134
|
-
logger.warn(`[dsh-chatgpt-web] Sidecar process exited with code ${code} (signal: ${signal})`);
|
|
135
|
-
}
|
|
136
|
-
spawnedProcess = undefined;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Wait for healthcheck
|
|
140
|
-
const deadline = Date.now() + readyTimeoutMs;
|
|
141
|
-
while (Date.now() < deadline) {
|
|
142
|
-
if (await isSidecarHealthy(host, port)) {
|
|
143
|
-
logger.info(`[dsh-chatgpt-web] Sidecar ready and accepting turns at http://${host}:${port}/v1`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
logger.error(`[dsh-chatgpt-web] Sidecar did not become healthy within ${readyTimeoutMs}ms`);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const stopDaemon = async () => {
|
|
153
|
-
if (!spawnedProcess) return;
|
|
154
|
-
|
|
155
|
-
logger.info("[dsh-chatgpt-web] Stopping sidecar daemon...");
|
|
156
|
-
try {
|
|
157
|
-
const controller = new AbortController();
|
|
158
|
-
const timer = setTimeout(() => controller.abort(), 2000);
|
|
159
|
-
await fetch(`http://${host}:${port}/admin/shutdown`, {
|
|
160
|
-
method: "POST",
|
|
161
|
-
signal: controller.signal,
|
|
162
|
-
}).catch(() => {});
|
|
163
|
-
clearTimeout(timer);
|
|
164
|
-
} catch {
|
|
165
|
-
// ignore
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (spawnedProcess && !spawnedProcess.killed) {
|
|
169
|
-
spawnedProcess.kill("SIGTERM");
|
|
170
|
-
}
|
|
171
|
-
spawnedProcess = undefined;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
if (typeof ctx.effect === "function") {
|
|
175
|
-
ctx.effect(() => {
|
|
176
|
-
void startDaemon();
|
|
177
|
-
return () => {
|
|
178
|
-
void stopDaemon();
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
} else {
|
|
182
|
-
void startDaemon();
|
|
183
|
-
process.once("beforeExit", () => void stopDaemon());
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export default {
|
|
188
|
-
name,
|
|
189
|
-
inject,
|
|
190
|
-
apply,
|
|
191
|
-
};
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
export interface CordisContext {
|
|
8
|
+
effect?: (cb: () => void | Promise<void> | (() => void) | (() => Promise<void>)) => void;
|
|
9
|
+
logger?: (name: string) => {
|
|
10
|
+
info(msg: string): void;
|
|
11
|
+
warn(msg: string): void;
|
|
12
|
+
error(msg: string): void;
|
|
13
|
+
debug(msg: string): void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const name = "dsh-chatgpt-web";
|
|
18
|
+
export const inject = [];
|
|
19
|
+
|
|
20
|
+
export interface ChatGPTWebPluginConfig {
|
|
21
|
+
/** Host to bind or check for health (default: 127.0.0.1). */
|
|
22
|
+
host?: string;
|
|
23
|
+
/** Port the sidecar listens on (default: 17841). */
|
|
24
|
+
port?: number;
|
|
25
|
+
/** Automatically start the sidecar daemon if not running (default: true). */
|
|
26
|
+
autoStart?: boolean;
|
|
27
|
+
/** Maximum milliseconds to wait for the sidecar to report ready (default: 30000). */
|
|
28
|
+
readyTimeoutMs?: number;
|
|
29
|
+
/** Explicit path to the bun executable (optional). */
|
|
30
|
+
bunPath?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
|
|
35
|
+
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
36
|
+
|
|
37
|
+
function resolveLauncher(customBunPath?: string): { cmd: string; args: string[] } {
|
|
38
|
+
const libCliPath = resolve(ROOT_DIR, "lib", "cli.js");
|
|
39
|
+
if (existsSync(libCliPath)) {
|
|
40
|
+
return {
|
|
41
|
+
cmd: process.execPath,
|
|
42
|
+
args: [libCliPath, "serve"],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Development fallback: check for bun or tsx
|
|
47
|
+
if (customBunPath && existsSync(customBunPath)) {
|
|
48
|
+
return { cmd: customBunPath, args: ["run", "src/cli.ts", "serve"] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const winBun = join(homedir(), ".bun", "bin", "bun.exe");
|
|
52
|
+
if (existsSync(winBun)) {
|
|
53
|
+
return { cmd: winBun, args: ["run", "src/cli.ts", "serve"] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const tsxPath = resolve(ROOT_DIR, "../deepseek-harness/node_modules/tsx/dist/esm/index.mjs");
|
|
57
|
+
if (existsSync(tsxPath)) {
|
|
58
|
+
return {
|
|
59
|
+
cmd: process.execPath,
|
|
60
|
+
args: ["--import", pathToFileURL(tsxPath).href, "src/cli.ts", "serve"],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { cmd: process.execPath, args: ["src/cli.ts", "serve"] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function isSidecarHealthy(host: string, port: number): Promise<boolean> {
|
|
68
|
+
try {
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timer = setTimeout(() => controller.abort(), 1500);
|
|
71
|
+
const res = await fetch(`http://${host}:${port}/healthz`, {
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
if (!res.ok) return false;
|
|
76
|
+
const body = (await res.json()) as { status?: string };
|
|
77
|
+
return body.status === "ok";
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function apply(ctx: CordisContext, config: ChatGPTWebPluginConfig = {}): void {
|
|
84
|
+
const host = config.host || "127.0.0.1";
|
|
85
|
+
const port = config.port || 17841;
|
|
86
|
+
const autoStart = config.autoStart !== false;
|
|
87
|
+
const readyTimeoutMs = config.readyTimeoutMs || 30_000;
|
|
88
|
+
const logger = typeof ctx.logger === "function" ? ctx.logger("chatgpt-web") : console;
|
|
89
|
+
|
|
90
|
+
let spawnedProcess: ChildProcess | undefined;
|
|
91
|
+
|
|
92
|
+
const startDaemon = async () => {
|
|
93
|
+
const alreadyHealthy = await isSidecarHealthy(host, port);
|
|
94
|
+
if (alreadyHealthy) {
|
|
95
|
+
logger.info(`[dsh-chatgpt-web] Sidecar already running and healthy at http://${host}:${port}/v1`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!autoStart) {
|
|
100
|
+
logger.warn(`[dsh-chatgpt-web] Sidecar is offline and autoStart is false. Start it manually with 'bun run src/cli.ts serve'`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const launcher = resolveLauncher(config.bunPath);
|
|
105
|
+
logger.info(`[dsh-chatgpt-web] Starting dsh-chatgpt-web daemon via ${launcher.cmd} at http://${host}:${port}/v1...`);
|
|
106
|
+
|
|
107
|
+
const child = spawn(launcher.cmd, launcher.args, {
|
|
108
|
+
cwd: ROOT_DIR,
|
|
109
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
110
|
+
windowsHide: true,
|
|
111
|
+
env: {
|
|
112
|
+
...process.env,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
spawnedProcess = child;
|
|
117
|
+
|
|
118
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
119
|
+
const text = chunk.toString().trim();
|
|
120
|
+
if (text && typeof logger.debug === "function") logger.debug(`[sidecar] ${text}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
124
|
+
const text = chunk.toString().trim();
|
|
125
|
+
if (text && typeof logger.debug === "function") logger.debug(`[sidecar:err] ${text}`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
child.on("error", (err) => {
|
|
129
|
+
logger.error(`[dsh-chatgpt-web] Failed to launch sidecar process: ${err.message}`);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
child.on("exit", (code, signal) => {
|
|
133
|
+
if (code !== 0 && code !== null) {
|
|
134
|
+
logger.warn(`[dsh-chatgpt-web] Sidecar process exited with code ${code} (signal: ${signal})`);
|
|
135
|
+
}
|
|
136
|
+
spawnedProcess = undefined;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Wait for healthcheck
|
|
140
|
+
const deadline = Date.now() + readyTimeoutMs;
|
|
141
|
+
while (Date.now() < deadline) {
|
|
142
|
+
if (await isSidecarHealthy(host, port)) {
|
|
143
|
+
logger.info(`[dsh-chatgpt-web] Sidecar ready and accepting turns at http://${host}:${port}/v1`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
logger.error(`[dsh-chatgpt-web] Sidecar did not become healthy within ${readyTimeoutMs}ms`);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const stopDaemon = async () => {
|
|
153
|
+
if (!spawnedProcess) return;
|
|
154
|
+
|
|
155
|
+
logger.info("[dsh-chatgpt-web] Stopping sidecar daemon...");
|
|
156
|
+
try {
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
159
|
+
await fetch(`http://${host}:${port}/admin/shutdown`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
signal: controller.signal,
|
|
162
|
+
}).catch(() => {});
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
} catch {
|
|
165
|
+
// ignore
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (spawnedProcess && !spawnedProcess.killed) {
|
|
169
|
+
spawnedProcess.kill("SIGTERM");
|
|
170
|
+
}
|
|
171
|
+
spawnedProcess = undefined;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (typeof ctx.effect === "function") {
|
|
175
|
+
ctx.effect(() => {
|
|
176
|
+
void startDaemon();
|
|
177
|
+
return () => {
|
|
178
|
+
void stopDaemon();
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
void startDaemon();
|
|
183
|
+
process.once("beforeExit", () => void stopDaemon());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default {
|
|
188
|
+
name,
|
|
189
|
+
inject,
|
|
190
|
+
apply,
|
|
191
|
+
};
|
package/src/process.ts
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { spawnSync, type SpawnSyncOptions } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export interface CommandResult {
|
|
4
|
-
status: number;
|
|
5
|
-
stdout: string;
|
|
6
|
-
stderr: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function processRunning(
|
|
10
|
-
pid: unknown,
|
|
11
|
-
probe: (pid: number, signal: 0) => void = process.kill,
|
|
12
|
-
): boolean {
|
|
13
|
-
if (!Number.isInteger(pid) || (pid as number) < 1) return false;
|
|
14
|
-
try {
|
|
15
|
-
probe(pid as number, 0);
|
|
16
|
-
return true;
|
|
17
|
-
} catch (error) {
|
|
18
|
-
// Windows and hardened Unix environments can deny signalling an existing process. EPERM is
|
|
19
|
-
// existence evidence, not proof that the launcher/browser/tunnel owner disappeared.
|
|
20
|
-
return (error as NodeJS.ErrnoException)?.code === "EPERM";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function runCommand(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
|
|
25
|
-
const result = spawnSync(command, args, {
|
|
26
|
-
encoding: "utf8",
|
|
27
|
-
stdio: "pipe",
|
|
28
|
-
...options,
|
|
29
|
-
});
|
|
30
|
-
if (result.error) throw result.error;
|
|
31
|
-
return {
|
|
32
|
-
status: result.status ?? 1,
|
|
33
|
-
stdout: typeof result.stdout === "string" ? result.stdout : result.stdout?.toString("utf8") ?? "",
|
|
34
|
-
stderr: typeof result.stderr === "string" ? result.stderr : result.stderr?.toString("utf8") ?? "",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
|
|
39
|
-
const result = runCommand(command, args, options);
|
|
40
|
-
if (result.status !== 0) {
|
|
41
|
-
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
|
|
42
|
-
throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
|
|
43
|
-
}
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
1
|
+
import { spawnSync, type SpawnSyncOptions } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export interface CommandResult {
|
|
4
|
+
status: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function processRunning(
|
|
10
|
+
pid: unknown,
|
|
11
|
+
probe: (pid: number, signal: 0) => void = process.kill,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (!Number.isInteger(pid) || (pid as number) < 1) return false;
|
|
14
|
+
try {
|
|
15
|
+
probe(pid as number, 0);
|
|
16
|
+
return true;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// Windows and hardened Unix environments can deny signalling an existing process. EPERM is
|
|
19
|
+
// existence evidence, not proof that the launcher/browser/tunnel owner disappeared.
|
|
20
|
+
return (error as NodeJS.ErrnoException)?.code === "EPERM";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runCommand(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
|
|
25
|
+
const result = spawnSync(command, args, {
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
stdio: "pipe",
|
|
28
|
+
...options,
|
|
29
|
+
});
|
|
30
|
+
if (result.error) throw result.error;
|
|
31
|
+
return {
|
|
32
|
+
status: result.status ?? 1,
|
|
33
|
+
stdout: typeof result.stdout === "string" ? result.stdout : result.stdout?.toString("utf8") ?? "",
|
|
34
|
+
stderr: typeof result.stderr === "string" ? result.stderr : result.stderr?.toString("utf8") ?? "",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
|
|
39
|
+
const result = runCommand(command, args, options);
|
|
40
|
+
if (result.status !== 0) {
|
|
41
|
+
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
|
|
42
|
+
throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|