@jobshimo/browser-link 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/dist/bridge/dispatch.js +17 -7
- package/dist/bridge/dispatch.js.map +1 -1
- package/dist/bridge/ws-bridge.d.ts +28 -3
- package/dist/bridge/ws-bridge.js +51 -7
- package/dist/bridge/ws-bridge.js.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +62 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/free-port.d.ts +40 -1
- package/dist/commands/free-port.js +72 -7
- package/dist/commands/free-port.js.map +1 -1
- package/dist/extension/background.js +100 -8
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/popup.js +76 -1
- package/dist/extension/popup.js.map +1 -1
- package/dist/extension/repair-policy.d.ts +60 -0
- package/dist/extension/repair-policy.js +79 -0
- package/dist/extension/repair-policy.js.map +1 -0
- package/dist/extension/version-status.d.ts +44 -0
- package/dist/extension/version-status.js +60 -0
- package/dist/extension/version-status.js.map +1 -0
- package/dist/lifecycle/election.d.ts +98 -0
- package/dist/lifecycle/election.js +187 -0
- package/dist/lifecycle/election.js.map +1 -0
- package/dist/lifecycle/processes.d.ts +67 -0
- package/dist/lifecycle/processes.js +239 -0
- package/dist/lifecycle/processes.js.map +1 -0
- package/dist/lifecycle/repair.d.ts +48 -0
- package/dist/lifecycle/repair.js +67 -0
- package/dist/lifecycle/repair.js.map +1 -0
- package/dist/lifecycle/version-endpoint.d.ts +16 -0
- package/dist/lifecycle/version-endpoint.js +50 -0
- package/dist/lifecycle/version-endpoint.js.map +1 -0
- package/dist/manual/topics.js +1 -1
- package/dist/manual/topics.js.map +1 -1
- package/dist/messages.d.ts +31 -2
- package/dist/server.js +90 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +8 -0
- package/dist/tools/browser-dispatch.js +15 -1
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/ui/screens/free-port.js +4 -2
- package/dist/ui/screens/free-port.js.map +1 -1
- package/dist/version.d.ts +8 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { request } from 'node:http';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
import { compareSemver } from '../utils/semver.js';
|
|
4
|
+
import { defaultExecFile } from './processes.js';
|
|
5
|
+
/** The pure decision table (a)/(b)/(c) above. */
|
|
6
|
+
export function decideElection(input) {
|
|
7
|
+
const { probe } = input;
|
|
8
|
+
if (probe.kind === 'version') {
|
|
9
|
+
const remote = probe.info.version;
|
|
10
|
+
if (compareSemver(remote, input.local) < 0) {
|
|
11
|
+
return {
|
|
12
|
+
decision: 'take-over',
|
|
13
|
+
pid: probe.info.pid,
|
|
14
|
+
reason: `running primary ${remote} is older than ${input.local}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
decision: 'defer',
|
|
19
|
+
pid: probe.info.pid,
|
|
20
|
+
reason: `running primary ${remote} is not older than ${input.local}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (probe.kind === 'unreachable') {
|
|
24
|
+
return { decision: 'defer', pid: input.ownerPid, reason: 'port holder does not answer HTTP' };
|
|
25
|
+
}
|
|
26
|
+
const { ownerPid, ownerStartedAt, installedCodeMtime } = input;
|
|
27
|
+
if (ownerPid === null || ownerStartedAt === null || installedCodeMtime === null) {
|
|
28
|
+
return {
|
|
29
|
+
decision: 'defer',
|
|
30
|
+
pid: ownerPid,
|
|
31
|
+
reason: 'primary has no /version and its start time cannot be compared',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (ownerStartedAt < installedCodeMtime) {
|
|
35
|
+
return {
|
|
36
|
+
decision: 'take-over',
|
|
37
|
+
pid: ownerPid,
|
|
38
|
+
reason: 'primary has no /version and started before the installed code was written',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
decision: 'defer',
|
|
43
|
+
pid: ownerPid,
|
|
44
|
+
reason: 'primary has no /version but started after the installed code was written',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Probe, decide and, on take-over, step the owner down. The caller binds
|
|
48
|
+
* afterwards (see `retryBind`). A step-down that fails turns into `defer`. */
|
|
49
|
+
export async function runElection(deps) {
|
|
50
|
+
const probe = await deps.probe();
|
|
51
|
+
let ownerPid = null;
|
|
52
|
+
let ownerStartedAt = null;
|
|
53
|
+
let installedCodeMtime = null;
|
|
54
|
+
if (probe.kind === 'no-endpoint') {
|
|
55
|
+
ownerPid = deps.findOwnerPid();
|
|
56
|
+
ownerStartedAt = ownerPid === null ? null : deps.processStartTime(ownerPid);
|
|
57
|
+
installedCodeMtime = deps.installedCodeMtime();
|
|
58
|
+
}
|
|
59
|
+
const outcome = decideElection({
|
|
60
|
+
local: deps.localVersion,
|
|
61
|
+
probe,
|
|
62
|
+
ownerPid,
|
|
63
|
+
ownerStartedAt,
|
|
64
|
+
installedCodeMtime,
|
|
65
|
+
});
|
|
66
|
+
if (outcome.decision !== 'take-over' || outcome.pid === null)
|
|
67
|
+
return outcome;
|
|
68
|
+
try {
|
|
69
|
+
await deps.stepDown(outcome.pid);
|
|
70
|
+
return outcome;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
decision: 'defer',
|
|
75
|
+
pid: outcome.pid,
|
|
76
|
+
reason: `step-down of PID ${outcome.pid} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isVersionInfo(v) {
|
|
81
|
+
if (typeof v !== 'object' || v === null)
|
|
82
|
+
return false;
|
|
83
|
+
const o = v;
|
|
84
|
+
return (typeof o.name === 'string' &&
|
|
85
|
+
typeof o.version === 'string' &&
|
|
86
|
+
typeof o.pid === 'number' &&
|
|
87
|
+
Number.isInteger(o.pid) &&
|
|
88
|
+
o.pid > 0 &&
|
|
89
|
+
typeof o.startedAt === 'string' &&
|
|
90
|
+
(o.installedVersion === null || typeof o.installedVersion === 'string'));
|
|
91
|
+
}
|
|
92
|
+
/** `GET http://<host>:<port>/version`. Never rejects. */
|
|
93
|
+
export function probeVersion(port, timeoutMs = 1000, host = '127.0.0.1') {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
let settled = false;
|
|
96
|
+
const done = (probe) => {
|
|
97
|
+
if (settled)
|
|
98
|
+
return;
|
|
99
|
+
settled = true;
|
|
100
|
+
resolve(probe);
|
|
101
|
+
};
|
|
102
|
+
const req = request({ host, port, path: '/version', method: 'GET', timeout: timeoutMs }, (res) => {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
res.on('data', (c) => chunks.push(c));
|
|
105
|
+
res.on('end', () => {
|
|
106
|
+
if (res.statusCode !== 200) {
|
|
107
|
+
done({ kind: 'no-endpoint' });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
112
|
+
done(isVersionInfo(body) ? { kind: 'version', info: body } : { kind: 'no-endpoint' });
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
done({ kind: 'no-endpoint' });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
res.on('error', () => {
|
|
119
|
+
done({ kind: 'no-endpoint' });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
req.on('timeout', () => {
|
|
123
|
+
req.destroy();
|
|
124
|
+
done({ kind: 'unreachable' });
|
|
125
|
+
});
|
|
126
|
+
req.on('error', () => {
|
|
127
|
+
done({ kind: 'unreachable' });
|
|
128
|
+
});
|
|
129
|
+
req.end();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const sleepReal = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
133
|
+
/** Run `bind` until it succeeds, retrying only errors `isRetryable` accepts
|
|
134
|
+
* (a busy port), at most `attempts` times with `delayMs` between tries. */
|
|
135
|
+
export async function retryBind(bind, opts) {
|
|
136
|
+
const sleep = opts.sleep ?? sleepReal;
|
|
137
|
+
for (let attempt = 1;; attempt++) {
|
|
138
|
+
try {
|
|
139
|
+
return await bind();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (!opts.isRetryable(err) || attempt >= opts.attempts)
|
|
143
|
+
throw err;
|
|
144
|
+
await sleep(opts.delayMs);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Unix grace period between SIGTERM and SIGKILL. */
|
|
149
|
+
export const STEP_DOWN_GRACE_MS = 3000;
|
|
150
|
+
const STEP_DOWN_POLL_MS = 100;
|
|
151
|
+
function isAliveReal(pid) {
|
|
152
|
+
try {
|
|
153
|
+
process.kill(pid, 0);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
// EPERM: it exists but belongs to someone else.
|
|
158
|
+
return err.code === 'EPERM';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Stop a process: Windows `taskkill /PID n /F`; Unix `SIGTERM`, wait up to
|
|
163
|
+
* 3 s for it to exit, then `SIGKILL`. Throws when the kill itself fails.
|
|
164
|
+
*/
|
|
165
|
+
export async function stepDownProcess(pid, deps = {}) {
|
|
166
|
+
const os = deps.os ?? platform();
|
|
167
|
+
if (os === 'win32') {
|
|
168
|
+
(deps.execFile ?? defaultExecFile)('taskkill', ['/PID', String(pid), '/F'], {
|
|
169
|
+
timeout: 5000,
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const kill = deps.kill ?? ((p, s) => void process.kill(p, s));
|
|
174
|
+
const isAlive = deps.isAlive ?? isAliveReal;
|
|
175
|
+
const sleep = deps.sleep ?? sleepReal;
|
|
176
|
+
const now = deps.now ?? Date.now;
|
|
177
|
+
kill(pid, 'SIGTERM');
|
|
178
|
+
const deadline = now() + STEP_DOWN_GRACE_MS;
|
|
179
|
+
while (isAlive(pid)) {
|
|
180
|
+
if (now() >= deadline) {
|
|
181
|
+
kill(pid, 'SIGKILL');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
await sleep(STEP_DOWN_POLL_MS);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=election.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"election.js","sourceRoot":"","sources":["../../src/lifecycle/election.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process enumeration for the lifecycle features: `browser-link stop`
|
|
3
|
+
* (kill every live bridge, not only the port owner), `doctor` (list them)
|
|
4
|
+
* and the primary election (when did the port owner start?).
|
|
5
|
+
*
|
|
6
|
+
* Every OS call goes through an injectable `ExecFileLike`, the same
|
|
7
|
+
* dependency-injection pattern `auth/silent-debugger.ts` uses, but
|
|
8
|
+
* synchronous: `runFreePort` is synchronous and consumed as such by the CLI,
|
|
9
|
+
* the TUI and `self-update`. Nothing in this module throws: a failed or
|
|
10
|
+
* unparseable listing degrades to "no processes" / `null`.
|
|
11
|
+
*
|
|
12
|
+
* Windows uses `Get-CimInstance Win32_Process` (never `wmic`: deprecated and
|
|
13
|
+
* absent on newer Windows 11 installs). Unix uses BSD-style `ps -axo`,
|
|
14
|
+
* accepted by both macOS and procps.
|
|
15
|
+
*/
|
|
16
|
+
/** `execFileSync`-shaped primitive: argv array, no shell, stdout as text. */
|
|
17
|
+
export type ExecFileLike = (file: string, args: readonly string[], options: {
|
|
18
|
+
timeout: number;
|
|
19
|
+
}) => string;
|
|
20
|
+
/** One row of the OS process table. `startedAt` is epoch milliseconds, or
|
|
21
|
+
* `null` when the OS did not report a usable start time. */
|
|
22
|
+
export interface ProcessEntry {
|
|
23
|
+
pid: number;
|
|
24
|
+
ppid: number;
|
|
25
|
+
startedAt: number | null;
|
|
26
|
+
command: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const defaultExecFile: ExecFileLike;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a process start time in any of the forms the OS tools print:
|
|
31
|
+
* PowerShell 5.1's JSON `/Date(ms)/`, ISO 8601 (PowerShell 7 JSON,
|
|
32
|
+
* `ToString('o')`), a WMI datetime `yyyyMMddHHmmss.ffffff±UUU` (offset in
|
|
33
|
+
* minutes) and `ps`'s `lstart` (`Mon Nov 9 02:17:44 2026`, local time).
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseProcessTime(raw: string): number | null;
|
|
36
|
+
/** Parse `Get-CimInstance … | ConvertTo-Json -Compress`. One process comes
|
|
37
|
+
* out as a bare object, several as an array; both are accepted. */
|
|
38
|
+
export declare function parseWindowsProcessList(stdout: string): ProcessEntry[];
|
|
39
|
+
/** Parse `ps -axo pid=,ppid=,lstart=,command=` (C locale). */
|
|
40
|
+
export declare function parseUnixProcessList(stdout: string): ProcessEntry[];
|
|
41
|
+
/** The whole process table, or `[]` when it cannot be read. */
|
|
42
|
+
export declare function listProcesses(os?: NodeJS.Platform, exec?: ExecFileLike): ProcessEntry[];
|
|
43
|
+
/** Start time of one process (epoch ms), or `null` when unknown. */
|
|
44
|
+
export declare function readProcessStartTime(pid: number, opts?: {
|
|
45
|
+
os?: NodeJS.Platform;
|
|
46
|
+
exec?: ExecFileLike;
|
|
47
|
+
}): number | null;
|
|
48
|
+
/**
|
|
49
|
+
* Whether a command line runs this package, and with which subcommand
|
|
50
|
+
* (`null` = none, i.e. the MCP server or the TUI). Only the script argument
|
|
51
|
+
* counts (the first non-flag argument after the runtime, or the package
|
|
52
|
+
* spec after npm's `npx-cli.js`), so a process that merely mentions a
|
|
53
|
+
* browser-link path as an option value is never matched.
|
|
54
|
+
*/
|
|
55
|
+
export declare function matchBrowserLinkCommand(command: string): {
|
|
56
|
+
subcommand: string | null;
|
|
57
|
+
} | null;
|
|
58
|
+
/** Live browser-link bridges in `list`, excluding `selfPid`, its ancestor
|
|
59
|
+
* chain (the shell or client that launched it) and non-bridge commands. */
|
|
60
|
+
export declare function findBridgeProcesses(list: readonly ProcessEntry[], selfPid: number): ProcessEntry[];
|
|
61
|
+
export declare function listBridgeProcesses(opts?: {
|
|
62
|
+
os?: NodeJS.Platform;
|
|
63
|
+
exec?: ExecFileLike;
|
|
64
|
+
selfPid?: number;
|
|
65
|
+
}): ProcessEntry[];
|
|
66
|
+
/** Short, display-only form: runtime name, `browser-link`, then arguments. */
|
|
67
|
+
export declare function shortCommand(command: string, max?: number): string;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
/** WMI cold starts are slow: same budget `silent-debugger.ts` documents. */
|
|
4
|
+
const LIST_TIMEOUT_MS = 5000;
|
|
5
|
+
export const defaultExecFile = (file, args, options) => execFileSync(file, args, {
|
|
6
|
+
encoding: 'utf8',
|
|
7
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
8
|
+
windowsHide: true,
|
|
9
|
+
timeout: options.timeout,
|
|
10
|
+
// `ps` prints lstart in the C locale only when told so.
|
|
11
|
+
env: { ...process.env, LC_ALL: 'C' },
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Parse a process start time in any of the forms the OS tools print:
|
|
15
|
+
* PowerShell 5.1's JSON `/Date(ms)/`, ISO 8601 (PowerShell 7 JSON,
|
|
16
|
+
* `ToString('o')`), a WMI datetime `yyyyMMddHHmmss.ffffff±UUU` (offset in
|
|
17
|
+
* minutes) and `ps`'s `lstart` (`Mon Nov 9 02:17:44 2026`, local time).
|
|
18
|
+
*/
|
|
19
|
+
export function parseProcessTime(raw) {
|
|
20
|
+
const text = raw.trim();
|
|
21
|
+
if (text.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const msDate = /^\/Date\((-?\d+)\)\/$/.exec(text);
|
|
24
|
+
if (msDate)
|
|
25
|
+
return Number(msDate[1]);
|
|
26
|
+
const wmi = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{3})\d*([+-])(\d{3})$/.exec(text);
|
|
27
|
+
if (wmi) {
|
|
28
|
+
const [, y, mo, d, h, mi, s, ms, sign, off] = wmi;
|
|
29
|
+
const utc = Date.UTC(+y, +mo - 1, +d, +h, +mi, +s, +ms);
|
|
30
|
+
const offsetMs = Number(off) * 60_000 * (sign === '-' ? -1 : 1);
|
|
31
|
+
return utc - offsetMs;
|
|
32
|
+
}
|
|
33
|
+
const parsed = Date.parse(text);
|
|
34
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
35
|
+
}
|
|
36
|
+
/** Parse `Get-CimInstance … | ConvertTo-Json -Compress`. One process comes
|
|
37
|
+
* out as a bare object, several as an array; both are accepted. */
|
|
38
|
+
export function parseWindowsProcessList(stdout) {
|
|
39
|
+
let data;
|
|
40
|
+
try {
|
|
41
|
+
data = JSON.parse(stdout);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const rows = (Array.isArray(data) ? data : [data]);
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
if (typeof row !== 'object' || row === null)
|
|
50
|
+
continue;
|
|
51
|
+
const { ProcessId, ParentProcessId, CreationDate, CommandLine } = row;
|
|
52
|
+
const pid = Number(ProcessId);
|
|
53
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
54
|
+
continue;
|
|
55
|
+
const ppid = Number(ParentProcessId);
|
|
56
|
+
out.push({
|
|
57
|
+
pid,
|
|
58
|
+
ppid: Number.isInteger(ppid) ? ppid : 0,
|
|
59
|
+
startedAt: typeof CreationDate === 'string' ? parseProcessTime(CreationDate) : null,
|
|
60
|
+
command: typeof CommandLine === 'string' ? CommandLine.trim() : '',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const UNIX_ROW = /^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d{1,2}\s+\d{1,2}:\d{2}:\d{2}\s+\d{4})\s+(.*)$/;
|
|
66
|
+
/** Parse `ps -axo pid=,ppid=,lstart=,command=` (C locale). */
|
|
67
|
+
export function parseUnixProcessList(stdout) {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
70
|
+
const m = UNIX_ROW.exec(line);
|
|
71
|
+
if (!m)
|
|
72
|
+
continue;
|
|
73
|
+
out.push({
|
|
74
|
+
pid: Number(m[1]),
|
|
75
|
+
ppid: Number(m[2]),
|
|
76
|
+
startedAt: parseProcessTime(m[3]),
|
|
77
|
+
command: m[4].trim(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
const WINDOWS_LIST_SCRIPT = 'Get-CimInstance Win32_Process | ' +
|
|
83
|
+
'Select-Object ProcessId, ParentProcessId, CreationDate, CommandLine | ' +
|
|
84
|
+
'ConvertTo-Json -Compress';
|
|
85
|
+
function powershell(script) {
|
|
86
|
+
return ['powershell', ['-NoProfile', '-NonInteractive', '-Command', script]];
|
|
87
|
+
}
|
|
88
|
+
/** The whole process table, or `[]` when it cannot be read. */
|
|
89
|
+
export function listProcesses(os = platform(), exec = defaultExecFile) {
|
|
90
|
+
try {
|
|
91
|
+
if (os === 'win32') {
|
|
92
|
+
const [file, args] = powershell(WINDOWS_LIST_SCRIPT);
|
|
93
|
+
return parseWindowsProcessList(exec(file, args, { timeout: LIST_TIMEOUT_MS }));
|
|
94
|
+
}
|
|
95
|
+
return parseUnixProcessList(exec('ps', ['-axo', 'pid=,ppid=,lstart=,command='], { timeout: LIST_TIMEOUT_MS }));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Start time of one process (epoch ms), or `null` when unknown. */
|
|
102
|
+
export function readProcessStartTime(pid, opts = {}) {
|
|
103
|
+
const os = opts.os ?? platform();
|
|
104
|
+
const exec = opts.exec ?? defaultExecFile;
|
|
105
|
+
try {
|
|
106
|
+
if (os === 'win32') {
|
|
107
|
+
const [file, args] = powershell(`Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' | ` +
|
|
108
|
+
`Select-Object -ExpandProperty CreationDate | ` +
|
|
109
|
+
`ForEach-Object { $_.ToUniversalTime().ToString('o') }`);
|
|
110
|
+
return parseProcessTime(exec(file, args, { timeout: LIST_TIMEOUT_MS }));
|
|
111
|
+
}
|
|
112
|
+
return parseProcessTime(exec('ps', ['-o', 'lstart=', '-p', String(pid)], { timeout: LIST_TIMEOUT_MS }));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Split a command line into arguments. Double quotes group (Windows
|
|
119
|
+
* command lines quote paths with spaces); backslashes stay literal. */
|
|
120
|
+
function tokenize(command) {
|
|
121
|
+
const tokens = [];
|
|
122
|
+
let current = '';
|
|
123
|
+
let inQuotes = false;
|
|
124
|
+
let hasToken = false;
|
|
125
|
+
for (const ch of command) {
|
|
126
|
+
if (ch === '"') {
|
|
127
|
+
inQuotes = !inQuotes;
|
|
128
|
+
hasToken = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!inQuotes && /\s/.test(ch)) {
|
|
132
|
+
if (hasToken)
|
|
133
|
+
tokens.push(current);
|
|
134
|
+
current = '';
|
|
135
|
+
hasToken = false;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
current += ch;
|
|
139
|
+
hasToken = true;
|
|
140
|
+
}
|
|
141
|
+
if (hasToken)
|
|
142
|
+
tokens.push(current);
|
|
143
|
+
return tokens;
|
|
144
|
+
}
|
|
145
|
+
function normalise(token) {
|
|
146
|
+
return token.replace(/\\+/g, '/').toLowerCase();
|
|
147
|
+
}
|
|
148
|
+
function basename(token) {
|
|
149
|
+
const n = normalise(token);
|
|
150
|
+
return n.slice(n.lastIndexOf('/') + 1);
|
|
151
|
+
}
|
|
152
|
+
/** Runtimes a browser-link process runs under. */
|
|
153
|
+
const RUNTIMES = new Set(['node', 'node.exe', 'nodejs', 'bun', 'bun.exe']);
|
|
154
|
+
/** npm's own entry scripts: the package spec is a later argument. */
|
|
155
|
+
const NPM_ENTRIES = new Set(['npx-cli.js', 'npm-cli.js']);
|
|
156
|
+
function isBrowserLinkEntry(token) {
|
|
157
|
+
const n = normalise(token);
|
|
158
|
+
if (n.includes('@jobshimo/browser-link'))
|
|
159
|
+
return true;
|
|
160
|
+
if (n.endsWith('browser-link/dist/cli.js'))
|
|
161
|
+
return true;
|
|
162
|
+
// The global bin shim (a symlink on Unix): `node /usr/local/bin/browser-link`.
|
|
163
|
+
const base = basename(token);
|
|
164
|
+
return n.includes('/') && (base === 'browser-link' || base === 'browser-link.js');
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Whether a command line runs this package, and with which subcommand
|
|
168
|
+
* (`null` = none, i.e. the MCP server or the TUI). Only the script argument
|
|
169
|
+
* counts (the first non-flag argument after the runtime, or the package
|
|
170
|
+
* spec after npm's `npx-cli.js`), so a process that merely mentions a
|
|
171
|
+
* browser-link path as an option value is never matched.
|
|
172
|
+
*/
|
|
173
|
+
export function matchBrowserLinkCommand(command) {
|
|
174
|
+
const tokens = tokenize(command);
|
|
175
|
+
if (tokens.length < 2 || !RUNTIMES.has(basename(tokens[0])))
|
|
176
|
+
return null;
|
|
177
|
+
let i = 1;
|
|
178
|
+
while (i < tokens.length && tokens[i].startsWith('-'))
|
|
179
|
+
i += 1;
|
|
180
|
+
if (i >= tokens.length)
|
|
181
|
+
return null;
|
|
182
|
+
if (NPM_ENTRIES.has(basename(tokens[i]))) {
|
|
183
|
+
i += 1;
|
|
184
|
+
while (i < tokens.length && (tokens[i].startsWith('-') || tokens[i] === 'exec'))
|
|
185
|
+
i += 1;
|
|
186
|
+
if (i >= tokens.length)
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (!isBrowserLinkEntry(tokens[i]))
|
|
190
|
+
return null;
|
|
191
|
+
return { subcommand: tokens[i + 1] ?? null };
|
|
192
|
+
}
|
|
193
|
+
/** Subcommands that run the bridge (see `cli.ts`'s dispatch switch: only a
|
|
194
|
+
* bare invocation and `start` call `runServer`). Every other subcommand
|
|
195
|
+
* (`stop`, `doctor`, `version`, `help`, `about`, `config`, `map`,
|
|
196
|
+
* `instructions`, `activity`, `self-update`, …) is a short-lived command
|
|
197
|
+
* that `stop` must never kill. */
|
|
198
|
+
function isBridgeSubcommand(subcommand) {
|
|
199
|
+
return subcommand === null || subcommand === 'start';
|
|
200
|
+
}
|
|
201
|
+
/** `selfPid` and every ancestor reachable through `ppid` in `list`. */
|
|
202
|
+
function selfAndAncestors(list, selfPid) {
|
|
203
|
+
const byPid = new Map(list.map((p) => [p.pid, p]));
|
|
204
|
+
const out = new Set();
|
|
205
|
+
let pid = selfPid;
|
|
206
|
+
while (pid !== undefined && pid > 0 && !out.has(pid)) {
|
|
207
|
+
out.add(pid);
|
|
208
|
+
pid = byPid.get(pid)?.ppid;
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
/** Live browser-link bridges in `list`, excluding `selfPid`, its ancestor
|
|
213
|
+
* chain (the shell or client that launched it) and non-bridge commands. */
|
|
214
|
+
export function findBridgeProcesses(list, selfPid) {
|
|
215
|
+
const excluded = selfAndAncestors(list, selfPid);
|
|
216
|
+
return list.filter((p) => {
|
|
217
|
+
if (excluded.has(p.pid))
|
|
218
|
+
return false;
|
|
219
|
+
const match = matchBrowserLinkCommand(p.command);
|
|
220
|
+
return match !== null && isBridgeSubcommand(match.subcommand);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
export function listBridgeProcesses(opts = {}) {
|
|
224
|
+
return findBridgeProcesses(listProcesses(opts.os, opts.exec), opts.selfPid ?? process.pid);
|
|
225
|
+
}
|
|
226
|
+
/** Short, display-only form: runtime name, `browser-link`, then arguments. */
|
|
227
|
+
export function shortCommand(command, max = 80) {
|
|
228
|
+
const tokens = tokenize(command);
|
|
229
|
+
if (tokens.length === 0)
|
|
230
|
+
return '';
|
|
231
|
+
const parts = tokens.map((t, i) => {
|
|
232
|
+
if (i === 0)
|
|
233
|
+
return basename(t).replace(/\.exe$/, '');
|
|
234
|
+
return isBrowserLinkEntry(t) ? 'browser-link' : t;
|
|
235
|
+
});
|
|
236
|
+
const text = parts.join(' ');
|
|
237
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=processes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processes.js","sourceRoot":"","sources":["../../src/lifecycle/processes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BridgeRepairMessage, ServerToExtension } from '../messages.js';
|
|
2
|
+
/**
|
|
3
|
+
* Popup "Repair connection", server side (S4).
|
|
4
|
+
*
|
|
5
|
+
* On `bridge.repair` the bridge answers first, then this process restarts
|
|
6
|
+
* itself on the installed code: spawn the installed CLI detached, terminate
|
|
7
|
+
* every extension socket uncleanly (`ws.terminate()`, which the extension's
|
|
8
|
+
* auto-reconnect treats as involuntary), stop IPC (proxies get
|
|
9
|
+
* `primary-closing`) and exit 0. The child wins the port through the
|
|
10
|
+
* election's bind retries, and the extension reconnects to it by itself.
|
|
11
|
+
*
|
|
12
|
+
* The policy is pure; `server.ts` injects the real spawn/terminate/exit.
|
|
13
|
+
*/
|
|
14
|
+
export interface RepairDeps {
|
|
15
|
+
/** `process.execPath`. */
|
|
16
|
+
execPath: string;
|
|
17
|
+
/** The installed `dist/cli.js` (same file the process started from). */
|
|
18
|
+
cliPath: string;
|
|
19
|
+
spawn: (command: string, args: string[], options: {
|
|
20
|
+
detached: true;
|
|
21
|
+
stdio: 'ignore';
|
|
22
|
+
windowsHide: true;
|
|
23
|
+
}) => {
|
|
24
|
+
unref(): void;
|
|
25
|
+
};
|
|
26
|
+
terminateSockets: () => void;
|
|
27
|
+
stopIpc: () => Promise<void>;
|
|
28
|
+
exit: (code: number) => void;
|
|
29
|
+
/** Waits long enough for the ack frame to be flushed to the socket. */
|
|
30
|
+
delay: (ms: number) => Promise<void>;
|
|
31
|
+
log?: (msg: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/** Time given to the `bridge.repair.result` frame before sockets die. */
|
|
34
|
+
export declare const REPAIR_FLUSH_DELAY_MS = 150;
|
|
35
|
+
export interface RepairController {
|
|
36
|
+
/** Start the restart (once). Returns whether it is (now) in progress. */
|
|
37
|
+
start(): boolean;
|
|
38
|
+
/** Settles when the current restart attempt finished (tests). */
|
|
39
|
+
readonly done: Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function createRepairController(deps: RepairDeps): RepairController;
|
|
42
|
+
/**
|
|
43
|
+
* Answer one `bridge.repair` frame: the ack goes out BEFORE the repair
|
|
44
|
+
* starts, so the popup learns the outcome even though its socket is about to
|
|
45
|
+
* be terminated. `startRepair` is undefined in a process that cannot repair
|
|
46
|
+
* itself (tests, a bridge started without the wiring).
|
|
47
|
+
*/
|
|
48
|
+
export declare function answerRepairRequest(msg: BridgeRepairMessage, send: (reply: ServerToExtension) => void, startRepair: (() => boolean) | undefined): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Time given to the `bridge.repair.result` frame before sockets die. */
|
|
2
|
+
export const REPAIR_FLUSH_DELAY_MS = 150;
|
|
3
|
+
export function createRepairController(deps) {
|
|
4
|
+
let inProgress = false;
|
|
5
|
+
let done = Promise.resolve();
|
|
6
|
+
const run = async () => {
|
|
7
|
+
await deps.delay(REPAIR_FLUSH_DELAY_MS);
|
|
8
|
+
try {
|
|
9
|
+
deps
|
|
10
|
+
.spawn(deps.execPath, [deps.cliPath], {
|
|
11
|
+
detached: true,
|
|
12
|
+
stdio: 'ignore',
|
|
13
|
+
windowsHide: true,
|
|
14
|
+
})
|
|
15
|
+
.unref();
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
// Without a successor, exiting would leave no server at all: keep
|
|
19
|
+
// serving and let the user fall back to `browser-link stop`.
|
|
20
|
+
inProgress = false;
|
|
21
|
+
deps.log?.(`Repair: could not start the installed server (${err instanceof Error ? err.message : String(err)}); still serving.`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
deps.log?.('Repair: installed server started; handing over the port.');
|
|
25
|
+
deps.terminateSockets();
|
|
26
|
+
try {
|
|
27
|
+
await deps.stopIpc();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* already closed; exiting anyway */
|
|
31
|
+
}
|
|
32
|
+
deps.exit(0);
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
start() {
|
|
36
|
+
if (inProgress)
|
|
37
|
+
return true;
|
|
38
|
+
inProgress = true;
|
|
39
|
+
done = run();
|
|
40
|
+
return true;
|
|
41
|
+
},
|
|
42
|
+
get done() {
|
|
43
|
+
return done;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Answer one `bridge.repair` frame: the ack goes out BEFORE the repair
|
|
49
|
+
* starts, so the popup learns the outcome even though its socket is about to
|
|
50
|
+
* be terminated. `startRepair` is undefined in a process that cannot repair
|
|
51
|
+
* itself (tests, a bridge started without the wiring).
|
|
52
|
+
*/
|
|
53
|
+
export function answerRepairRequest(msg, send, startRepair) {
|
|
54
|
+
const requestId = typeof msg.request_id === 'string' ? msg.request_id : '';
|
|
55
|
+
if (!startRepair) {
|
|
56
|
+
send({
|
|
57
|
+
kind: 'bridge.repair.result',
|
|
58
|
+
request_id: requestId,
|
|
59
|
+
ok: false,
|
|
60
|
+
error: 'repair is not available in this server process',
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
send({ kind: 'bridge.repair.result', request_id: requestId, ok: true, action: 'restarting' });
|
|
65
|
+
startRepair();
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=repair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/lifecycle/repair.ts"],"names":[],"mappings":"AAiCA,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AASzC,MAAM,UAAU,sBAAsB,CAAC,IAAgB;IACrD,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,IAAI,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAE5C,MAAM,GAAG,GAAG,KAAK,IAAmB,EAAE;QACpC,MAAM,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,IAAI;iBACD,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;gBACpC,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,QAAQ;gBACf,WAAW,EAAE,IAAI;aAClB,CAAC;iBACD,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kEAAkE;YAClE,6DAA6D;YAC7D,UAAU,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,GAAG,EAAE,CACR,iDAAiD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,CACrH,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,GAAG,EAAE,CAAC,0DAA0D,CAAC,CAAC;QACvE,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,CAAC,CAAC;IAEF,OAAO;QACL,KAAK;YACH,IAAI,UAAU;gBAAE,OAAO,IAAI,CAAC;YAC5B,UAAU,GAAG,IAAI,CAAC;YAClB,IAAI,GAAG,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI;YACN,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAwB,EACxB,IAAwC,EACxC,WAAwC;IAExC,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,EAAE,sBAAsB;YAC5B,UAAU,EAAE,SAAS;YACrB,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,gDAAgD;SACxD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,IAAI,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAC9F,WAAW,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { VersionInfo } from './election.js';
|
|
3
|
+
/**
|
|
4
|
+
* The installed CLI entry point: `<package>/dist/cli.js`. This module lives
|
|
5
|
+
* in `dist/lifecycle/` (installed) or `src/lifecycle/` (dev), two levels
|
|
6
|
+
* below the package root either way. After `npm install -g` this is the
|
|
7
|
+
* same path the running process started from, now holding the new code.
|
|
8
|
+
*/
|
|
9
|
+
export declare const INSTALLED_CLI_PATH: string;
|
|
10
|
+
/** Last-modified time of the installed CLI (epoch ms), or `null`. */
|
|
11
|
+
export declare function readInstalledCodeMtime(path?: string): number | null;
|
|
12
|
+
/** Approximate wall-clock start of this process, epoch ms. */
|
|
13
|
+
export declare const PROCESS_STARTED_AT: number;
|
|
14
|
+
export declare function buildVersionInfo(version: string, pid?: number, startedAt?: number, installed?: () => string | null): VersionInfo;
|
|
15
|
+
/** The bridge's plain-HTTP handler: `/version` or 404. */
|
|
16
|
+
export declare function handleBridgeHttpRequest(req: IncomingMessage, res: ServerResponse, version: string): void;
|