codehost 0.7.0 → 0.7.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 +8 -0
- package/package.json +1 -1
- package/src/cli/commands/list.ts +5 -1
- package/src/cli/oxmgr.ts +36 -3
- package/src/cli/proc.ts +57 -0
- package/src/cli/vscode.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.7.1](https://github.com/snomiao/codehost/compare/v0.7.0...v0.7.1) (2026-06-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* ch list shows only codehost daemons, not all oxmgr processes ([e9cf4fc](https://github.com/snomiao/codehost/commit/e9cf4fca40b653c430c26eb29ba47ef9a154f089))
|
|
7
|
+
* kill the VS Code serve-web process tree on stop (no orphans) ([d6337f6](https://github.com/snomiao/codehost/commit/d6337f6d45bf41fb57f2d0cd28c60a70f55f62d0))
|
|
8
|
+
|
|
1
9
|
# [0.7.0](https://github.com/snomiao/codehost/compare/v0.6.0...v0.7.0) (2026-06-08)
|
|
2
10
|
|
|
3
11
|
|
package/package.json
CHANGED
package/src/cli/commands/list.ts
CHANGED
|
@@ -18,7 +18,11 @@ export const listCommand: CommandModule = {
|
|
|
18
18
|
// Only hit oxmgr if it's actually runnable — `hasOxmgr` doesn't self-heal,
|
|
19
19
|
// so a broken install won't re-download its binary on every `list`.
|
|
20
20
|
if (await hasOxmgr()) {
|
|
21
|
-
|
|
21
|
+
const shown = await listDaemons();
|
|
22
|
+
// listDaemons returns the count of codehost daemons (>=0) or -1 if oxmgr
|
|
23
|
+
// is unusable; only the latter is an error exit. It prints its own
|
|
24
|
+
// "No codehost daemons running." message when the count is 0.
|
|
25
|
+
process.exit(shown < 0 ? 1 : 0);
|
|
22
26
|
}
|
|
23
27
|
if (!detached.length) console.log("No codehost daemons running.");
|
|
24
28
|
process.exit(0);
|
package/src/cli/oxmgr.ts
CHANGED
|
@@ -117,10 +117,43 @@ function enableStartup(): void {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* `codehost list` -> oxmgr's process table, filtered to codehost-owned daemons.
|
|
122
|
+
*
|
|
123
|
+
* oxmgr is a shared process manager (other tools register their own services
|
|
124
|
+
* with it), so a raw `oxmgr list` leaks unrelated processes. oxmgr has no JSON
|
|
125
|
+
* or name-filter flag, so we capture its ASCII table and drop data rows whose
|
|
126
|
+
* NAME column isn't one of ours (the `codehost-` prefix from `daemonName`).
|
|
127
|
+
* Returns the number of codehost daemons shown (-1 if oxmgr is unusable).
|
|
128
|
+
*/
|
|
121
129
|
export async function listDaemons(): Promise<number> {
|
|
122
|
-
if (!(await ensureOxmgr())) return 1;
|
|
123
|
-
|
|
130
|
+
if (!(await ensureOxmgr())) return -1;
|
|
131
|
+
const entry = oxmgrEntry();
|
|
132
|
+
if (!entry) return -1;
|
|
133
|
+
const r = spawnSync(process.execPath, [entry, "list"], { encoding: "utf8" });
|
|
134
|
+
if (r.status !== 0) {
|
|
135
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
136
|
+
return -1;
|
|
137
|
+
}
|
|
138
|
+
const lines = (r.stdout ?? "").split("\n");
|
|
139
|
+
let shown = 0;
|
|
140
|
+
const kept = lines.filter((line) => {
|
|
141
|
+
// Border lines (+----+) and blank lines pass through unchanged.
|
|
142
|
+
if (!line.startsWith("|")) return true;
|
|
143
|
+
const name = line.split("|")[2]?.trim() ?? "";
|
|
144
|
+
if (name === "NAME") return true; // header row
|
|
145
|
+
if (name.startsWith("codehost-")) {
|
|
146
|
+
shown++;
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
});
|
|
151
|
+
if (shown === 0) {
|
|
152
|
+
console.log("No codehost daemons running.");
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
process.stdout.write(kept.join("\n"));
|
|
156
|
+
return shown;
|
|
124
157
|
}
|
|
125
158
|
|
|
126
159
|
/** `codehost stop <name>` -> stop + delete the oxmgr process. */
|
package/src/cli/proc.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kill a process and all of its descendants. VS Code's `serve-web` launcher
|
|
5
|
+
* double-forks (code -> code-tunnel -> code-server -> node server-main.js), so
|
|
6
|
+
* killing only the spawned launcher leaves the real server running as orphans.
|
|
7
|
+
* Killing the whole tree avoids that leak. Best-effort and synchronous so it can
|
|
8
|
+
* run inside a shutdown handler right before the process exits.
|
|
9
|
+
*/
|
|
10
|
+
export function killProcessTree(pid: number, signal: NodeJS.Signals = "SIGTERM"): void {
|
|
11
|
+
if (!pid || pid <= 1) return;
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
// /T kills the process and its child tree; /F forces it.
|
|
14
|
+
spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// POSIX: signal descendants (leaves first) then the root itself.
|
|
18
|
+
for (const p of [...descendants(pid), pid]) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(p, signal);
|
|
21
|
+
} catch {
|
|
22
|
+
// already gone
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** PIDs descended from `root`, deepest first (so they can be signalled before
|
|
28
|
+
* their parents). Uses `ps`; returns [] if it's unavailable. */
|
|
29
|
+
function descendants(root: number): number[] {
|
|
30
|
+
let out = "";
|
|
31
|
+
try {
|
|
32
|
+
out = spawnSync("ps", ["-A", "-o", "pid=,ppid="], { encoding: "utf8" }).stdout ?? "";
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const childrenOf = new Map<number, number[]>();
|
|
37
|
+
for (const line of out.split("\n")) {
|
|
38
|
+
const cols = line.trim().split(/\s+/);
|
|
39
|
+
if (cols.length < 2) continue;
|
|
40
|
+
const pid = Number(cols[0]);
|
|
41
|
+
const ppid = Number(cols[1]);
|
|
42
|
+
if (!Number.isFinite(pid) || !Number.isFinite(ppid)) continue;
|
|
43
|
+
const arr = childrenOf.get(ppid);
|
|
44
|
+
if (arr) arr.push(pid);
|
|
45
|
+
else childrenOf.set(ppid, [pid]);
|
|
46
|
+
}
|
|
47
|
+
const result: number[] = [];
|
|
48
|
+
const stack = [root];
|
|
49
|
+
while (stack.length) {
|
|
50
|
+
const p = stack.pop()!;
|
|
51
|
+
for (const c of childrenOf.get(p) ?? []) {
|
|
52
|
+
result.push(c);
|
|
53
|
+
stack.push(c);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result.reverse();
|
|
57
|
+
}
|
package/src/cli/vscode.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, type Subprocess } from "bun";
|
|
2
2
|
import { resolveCodeBinary } from "./vscode-install";
|
|
3
|
+
import { killProcessTree } from "./proc";
|
|
3
4
|
|
|
4
5
|
// How long to wait for `code serve-web` to answer. The default is generous
|
|
5
6
|
// because the FIRST run downloads the server component, which can take minutes
|
|
@@ -61,7 +62,10 @@ export async function launchVscode(opts: LaunchOptions): Promise<VscodeServer> {
|
|
|
61
62
|
|
|
62
63
|
const stop = () => {
|
|
63
64
|
try {
|
|
64
|
-
|
|
65
|
+
// serve-web double-forks; kill the whole tree so the real VS Code server
|
|
66
|
+
// doesn't linger as an orphan after the daemon stops.
|
|
67
|
+
if (proc.pid) killProcessTree(proc.pid);
|
|
68
|
+
else proc.kill();
|
|
65
69
|
} catch {
|
|
66
70
|
// ignore
|
|
67
71
|
}
|