stim 1.8.0 → 1.10.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 +23 -155
- package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
- package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
- package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
- package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-CrJ6t_bv.mjs +819 -0
- package/dist/budget-BP4jZZL4.mjs +620 -0
- package/dist/build-progress-BXx-ZBe4.mjs +512 -0
- package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +22 -19
- package/dist/collector-run.d.mts +7 -1
- package/dist/collector-run.mjs +10 -9
- package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
- package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
- package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
- package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
- package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
- package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
- package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
- package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
- package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
- package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
- package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
- package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
- package/dist/exec-DK8KaVHK.mjs +133 -0
- package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
- package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
- package/dist/guide-status-CWvti2Lg.mjs +125 -0
- package/dist/idle-DpRLp5lj.mjs +157 -0
- package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
- package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
- package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
- package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
- package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
- package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
- package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
- package/dist/metro-BWLOJ_Wr.mjs +409 -0
- package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
- package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
- package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
- package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
- package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
- package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
- package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
- package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
- package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
- package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
- package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
- package/dist/server-expo-DBVTdult.mjs +2 -0
- package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
- package/dist/settings-EWYNwe72.mjs +378 -0
- package/dist/settings.schema.json +1064 -0
- package/dist/simslim-Ca3487H6.mjs +436 -0
- package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
- package/dist/spawn-claims-CUleC2cM.mjs +49 -0
- package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
- package/dist/start-VoFsPSes.mjs +2 -0
- package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
- package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
- package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
- package/dist/status-DLCD-o3s.mjs +764 -0
- package/dist/status-Dr4fRkym.mjs +216 -0
- package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
- package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
- package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
- package/dist/supervisor-run.d.mts +11 -2
- package/dist/supervisor-run.mjs +228 -42
- package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
- package/dist/workspaces-BTUyFfkv.mjs +1003 -0
- package/dist/worktree-4aZg2NnI.mjs +2 -0
- package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
- package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
- package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
- package/package.json +6 -6
- package/skill/SKILL.md +2 -0
- package/dist/app-install-BDbsSgCl.mjs +0 -1175
- package/dist/dev-client-BmT-ioFI.mjs +0 -797
- package/dist/device-lease-Ffz_M5t0.mjs +0 -359
- package/dist/exec-B29XNuhP.mjs +0 -77
- package/dist/logs-query-BGL-Pl12.mjs +0 -290
- package/dist/metro-C7bcJ0uo.mjs +0 -233
- package/dist/ownership-wolE-0Do.mjs +0 -470
- package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
- package/dist/reclaim-KJ-0uWEF.mjs +0 -361
- package/dist/server-expo-YDDG3eP7.mjs +0 -2
- package/dist/spawn-entry-nCE6m770.mjs +0 -14
- package/dist/status-DdkLki47.mjs +0 -426
- package/dist/workspace-state-BT5E3SA2.mjs +0 -146
- package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-B29XNuhP.mjs";
|
|
2
|
-
import { u as getConfigDir, w as process_identity_exports } from "./config-B7iyKWkL.mjs";
|
|
3
|
-
import { t as createLineReader } from "./process-output-Dl5uKE03.mjs";
|
|
4
|
-
import { i as pidExists, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
|
|
5
|
-
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
|
|
6
|
-
import { t as probePublicHttp } from "./public-http-probe-CyMDrQ3c.mjs";
|
|
7
|
-
import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync } from "node:fs";
|
|
8
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
-
import { basename, join, resolve, sep } from "node:path";
|
|
10
|
-
//#region src/engine/tunnel.ts
|
|
11
|
-
function tunnelArgv(provider, port, ngrokUrl, logFile) {
|
|
12
|
-
if (provider === "cloudflared") return {
|
|
13
|
-
bin: "cloudflared",
|
|
14
|
-
args: [
|
|
15
|
-
"tunnel",
|
|
16
|
-
"--url",
|
|
17
|
-
`http://127.0.0.1:${port}`,
|
|
18
|
-
...logFile ? ["--logfile", logFile] : []
|
|
19
|
-
]
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
bin: "ngrok",
|
|
23
|
-
args: [
|
|
24
|
-
"http",
|
|
25
|
-
String(port),
|
|
26
|
-
`--log=${logFile || "stdout"}`,
|
|
27
|
-
"--log-format=json",
|
|
28
|
-
...ngrokUrl ? ["--url", ngrokUrl] : []
|
|
29
|
-
]
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
const CLOUDFLARED_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
33
|
-
function parseCloudflaredLine(line) {
|
|
34
|
-
const match = line.match(CLOUDFLARED_URL_RE);
|
|
35
|
-
return match ? match[0] : null;
|
|
36
|
-
}
|
|
37
|
-
function parseNgrokLine(line) {
|
|
38
|
-
let data;
|
|
39
|
-
try {
|
|
40
|
-
data = JSON.parse(line);
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
if (typeof data !== "object" || data === null) return null;
|
|
45
|
-
const url = data.url;
|
|
46
|
-
return typeof url === "string" && /^https?:\/\//i.test(url) ? url : null;
|
|
47
|
-
}
|
|
48
|
-
function parserFor(provider) {
|
|
49
|
-
return provider === "cloudflared" ? parseCloudflaredLine : parseNgrokLine;
|
|
50
|
-
}
|
|
51
|
-
const URL_TIMEOUT_MS = 15e3;
|
|
52
|
-
const REACHABLE_TIMEOUT_MS = 24e4;
|
|
53
|
-
const REACHABLE_POLL_MS = 2e3;
|
|
54
|
-
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
55
|
-
async function withManagedTunnelLock(root, fn, options = {}) {
|
|
56
|
-
return withWorkspaceProcessLock(managedTunnelLockRoot(root), "metro-tunnel", fn, {
|
|
57
|
-
...options,
|
|
58
|
-
rejectOwnerPurposes: ["workspace removal"]
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async function withManagedTunnelRemovalLock(root, fn, options = {}) {
|
|
62
|
-
return withWorkspaceProcessLock(managedTunnelLockRoot(root), "metro-tunnel", fn, {
|
|
63
|
-
...options,
|
|
64
|
-
ownerPurpose: "workspace removal"
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
async function withManagedRemoteWorktreeLock(worktreeRoot, fn, options = {}) {
|
|
68
|
-
return withWorkspaceProcessLock(managedRemoteWorktreeLockRoot(worktreeRoot), "managed-remote", fn, {
|
|
69
|
-
...options,
|
|
70
|
-
external: true,
|
|
71
|
-
rejectOwnerPurposes: ["worktree removal"]
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
async function withManagedRemoteWorktreeRemovalLock(worktreeRoot, fn, options = {}) {
|
|
75
|
-
return withWorkspaceProcessLock(managedRemoteWorktreeLockRoot(worktreeRoot), "managed-remote", fn, {
|
|
76
|
-
...options,
|
|
77
|
-
external: true,
|
|
78
|
-
ownerPurpose: "worktree removal"
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
function managedTunnelLockRoot(root) {
|
|
82
|
-
const key = createHash("sha256").update(resolve(root)).digest("hex");
|
|
83
|
-
return join(getConfigDir(), "process-locks", key);
|
|
84
|
-
}
|
|
85
|
-
function managedRemoteWorktreeLockRoot(worktreeRoot) {
|
|
86
|
-
const key = createHash("sha256").update(resolve(worktreeRoot)).digest("hex");
|
|
87
|
-
return join(getConfigDir(), "process-locks", "worktrees", key);
|
|
88
|
-
}
|
|
89
|
-
async function defaultProbeReachable(url, signal) {
|
|
90
|
-
const status = await probePublicHttp(url, signal);
|
|
91
|
-
return status !== null && status > 0;
|
|
92
|
-
}
|
|
93
|
-
function waitForUrl(child, parseLine, timeoutMs) {
|
|
94
|
-
return new Promise((resolve) => {
|
|
95
|
-
let settled = false;
|
|
96
|
-
let timer;
|
|
97
|
-
let onOut;
|
|
98
|
-
let onErr;
|
|
99
|
-
let onError;
|
|
100
|
-
let onExit;
|
|
101
|
-
const finish = (url, exited = false) => {
|
|
102
|
-
if (settled) return;
|
|
103
|
-
settled = true;
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
child.stdout?.removeListener("data", onOut);
|
|
106
|
-
child.stderr?.removeListener("data", onErr);
|
|
107
|
-
child.removeListener("error", onError);
|
|
108
|
-
child.removeListener("exit", onExit);
|
|
109
|
-
resolve({
|
|
110
|
-
url,
|
|
111
|
-
exited
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
const onLine = (line) => {
|
|
115
|
-
if (settled) return;
|
|
116
|
-
const url = parseLine(line);
|
|
117
|
-
if (url) finish(url);
|
|
118
|
-
};
|
|
119
|
-
const outReader = createLineReader(onLine);
|
|
120
|
-
const errReader = createLineReader(onLine);
|
|
121
|
-
onOut = (chunk) => outReader.push(chunk);
|
|
122
|
-
onErr = (chunk) => errReader.push(chunk);
|
|
123
|
-
onError = () => finish(null);
|
|
124
|
-
onExit = () => finish(null, true);
|
|
125
|
-
child.stdout?.setEncoding?.("utf-8");
|
|
126
|
-
child.stderr?.setEncoding?.("utf-8");
|
|
127
|
-
child.stdout?.on("data", onOut);
|
|
128
|
-
child.stderr?.on("data", onErr);
|
|
129
|
-
child.on("error", onError);
|
|
130
|
-
child.on("exit", onExit);
|
|
131
|
-
timer = setTimeout(() => finish(null), timeoutMs);
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
|
|
135
|
-
return new Promise((resolve) => {
|
|
136
|
-
let settled = false;
|
|
137
|
-
const finish = (url, exited = false) => {
|
|
138
|
-
if (settled) return;
|
|
139
|
-
settled = true;
|
|
140
|
-
clearInterval(interval);
|
|
141
|
-
clearTimeout(timer);
|
|
142
|
-
child.removeListener("error", onError);
|
|
143
|
-
child.removeListener("exit", onExit);
|
|
144
|
-
resolve({
|
|
145
|
-
url,
|
|
146
|
-
exited
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
const probe = () => {
|
|
150
|
-
try {
|
|
151
|
-
for (const line of readFileSync(logFile, "utf-8").split(/\r?\n/)) {
|
|
152
|
-
const url = parseLine(line);
|
|
153
|
-
if (url) return finish(url);
|
|
154
|
-
}
|
|
155
|
-
} catch {}
|
|
156
|
-
};
|
|
157
|
-
const onError = () => finish(null);
|
|
158
|
-
const onExit = () => finish(null, true);
|
|
159
|
-
child.on("error", onError);
|
|
160
|
-
child.on("exit", onExit);
|
|
161
|
-
const interval = setInterval(probe, 25);
|
|
162
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
163
|
-
probe();
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
function createTunnelLogFile(provider) {
|
|
167
|
-
const dir = join(getConfigDir(), "tunnel-logs");
|
|
168
|
-
mkdirSync(dir, { recursive: true });
|
|
169
|
-
const path = join(dir, `${provider}-${randomUUID()}.log`);
|
|
170
|
-
closeSync(openSync(path, "wx", 384));
|
|
171
|
-
return path;
|
|
172
|
-
}
|
|
173
|
-
function removeTunnelLogFile(path) {
|
|
174
|
-
if (!path) return;
|
|
175
|
-
try {
|
|
176
|
-
unlinkSync(path);
|
|
177
|
-
} catch {}
|
|
178
|
-
}
|
|
179
|
-
function removeRecordedTunnelLogFile(path) {
|
|
180
|
-
if (!path) return;
|
|
181
|
-
const dir = resolve(join(getConfigDir(), "tunnel-logs"));
|
|
182
|
-
const resolved = resolve(path);
|
|
183
|
-
if (!resolved.startsWith(`${dir}${sep}`) || resolve(join(dir, basename(resolved))) !== resolved) return;
|
|
184
|
-
removeTunnelLogFile(resolved);
|
|
185
|
-
}
|
|
186
|
-
async function waitUntilReachable({ url, now, sleep, probe, timeoutMs }) {
|
|
187
|
-
const deadline = now() + timeoutMs;
|
|
188
|
-
for (;;) {
|
|
189
|
-
if (await probe(url, new AbortController().signal).catch(() => false)) return true;
|
|
190
|
-
if (now() >= deadline) return false;
|
|
191
|
-
await sleep(REACHABLE_POLL_MS);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const CLEANUP_TIMEOUT_MS = 1e3;
|
|
195
|
-
const CLEANUP_POLL_MS = 25;
|
|
196
|
-
async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
|
|
197
|
-
const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
|
|
198
|
-
const outputFile = logFile || (spawnFn ? null : createTunnelLogFile(provider));
|
|
199
|
-
const { bin, args } = tunnelArgv(provider, port, ngrokUrl, outputFile);
|
|
200
|
-
let child;
|
|
201
|
-
try {
|
|
202
|
-
child = spawn(bin, args, {
|
|
203
|
-
stdio: outputFile ? [
|
|
204
|
-
"ignore",
|
|
205
|
-
"ignore",
|
|
206
|
-
"ignore"
|
|
207
|
-
] : [
|
|
208
|
-
"ignore",
|
|
209
|
-
"pipe",
|
|
210
|
-
"pipe"
|
|
211
|
-
],
|
|
212
|
-
detached: true
|
|
213
|
-
});
|
|
214
|
-
} catch (err) {
|
|
215
|
-
removeTunnelLogFile(outputFile);
|
|
216
|
-
return {
|
|
217
|
-
failed: true,
|
|
218
|
-
reason: `Could not start ${provider}: ${describe(err)}`
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
let childExited = false;
|
|
222
|
-
child.once("exit", () => {
|
|
223
|
-
childExited = true;
|
|
224
|
-
});
|
|
225
|
-
const cleanup = async (alreadyExited = childExited) => {
|
|
226
|
-
const stopped = await terminateChild(child, {
|
|
227
|
-
alreadyExited,
|
|
228
|
-
timeoutMs: cleanupTimeoutMs,
|
|
229
|
-
now,
|
|
230
|
-
sleep,
|
|
231
|
-
isAlive: isChildAlive
|
|
232
|
-
});
|
|
233
|
-
if (stopped) removeTunnelLogFile(outputFile);
|
|
234
|
-
return stopped ? { status: "stopped" } : {
|
|
235
|
-
status: "failed",
|
|
236
|
-
reason: `Sent SIGKILL but could not confirm that pid ${child.pid ?? "unknown"} exited.`
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
const failAfterCleanup = async (reason, alreadyExited = childExited) => {
|
|
240
|
-
const stopped = await cleanup(alreadyExited);
|
|
241
|
-
return stopped.status === "stopped" ? {
|
|
242
|
-
failed: true,
|
|
243
|
-
reason
|
|
244
|
-
} : {
|
|
245
|
-
failed: true,
|
|
246
|
-
reason: `${reason} ${stopped.reason}`,
|
|
247
|
-
cleanupFailed: true
|
|
248
|
-
};
|
|
249
|
-
};
|
|
250
|
-
const pid = child.pid;
|
|
251
|
-
if (!pid) return failAfterCleanup(`${provider} started but reported no pid.`);
|
|
252
|
-
const captureChildIdentity = () => {
|
|
253
|
-
try {
|
|
254
|
-
return readProcessToken(pid);
|
|
255
|
-
} catch {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
const initialProcessToken = captureChildIdentity();
|
|
260
|
-
if (!initialProcessToken) return failAfterCleanup(`${provider} started but its process identity token could not be read.`);
|
|
261
|
-
const { url, exited } = outputFile ? await waitForFileUrl(child, outputFile, parserFor(provider), urlTimeoutMs) : await waitForUrl(child, parserFor(provider), urlTimeoutMs);
|
|
262
|
-
if (!url) return failAfterCleanup(exited ? `${provider} exited before printing a tunnel URL.` : `${provider} did not print a tunnel URL within ${urlTimeoutMs}ms.`, exited || childExited);
|
|
263
|
-
if (!outputFile) resumeChildPipes(child);
|
|
264
|
-
if (requireReachable) {
|
|
265
|
-
if (!await waitUntilReachable({
|
|
266
|
-
url,
|
|
267
|
-
now,
|
|
268
|
-
sleep,
|
|
269
|
-
probe: probeReachable,
|
|
270
|
-
timeoutMs: reachableTimeoutMs
|
|
271
|
-
})) return failAfterCleanup(`${url} did not become reachable within ${reachableTimeoutMs}ms; ${provider} may still be registering, or the network path is blocked.`);
|
|
272
|
-
}
|
|
273
|
-
const finalProcessToken = captureChildIdentity();
|
|
274
|
-
if (!finalProcessToken || finalProcessToken !== initialProcessToken) return failAfterCleanup(`${provider} process identity changed before its tunnel could be recorded.`);
|
|
275
|
-
if (!outputFile) unrefChildPipes(child);
|
|
276
|
-
child.unref?.();
|
|
277
|
-
if (childExited) return failAfterCleanup(`${provider} exited before its tunnel could be recorded.`, true);
|
|
278
|
-
return {
|
|
279
|
-
url,
|
|
280
|
-
pid,
|
|
281
|
-
processToken: initialProcessToken,
|
|
282
|
-
logFile: outputFile,
|
|
283
|
-
cleanup: () => cleanup()
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
async function startTunnelSequence({ providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
|
|
287
|
-
const failures = [];
|
|
288
|
-
for (const provider of providers) {
|
|
289
|
-
const result = await start({
|
|
290
|
-
provider,
|
|
291
|
-
port,
|
|
292
|
-
ngrokUrl: provider === "ngrok" ? ngrokUrl : null,
|
|
293
|
-
requireReachable
|
|
294
|
-
});
|
|
295
|
-
if (!("failed" in result)) return {
|
|
296
|
-
provider,
|
|
297
|
-
...result
|
|
298
|
-
};
|
|
299
|
-
failures.push(`${provider}: ${result.reason}`);
|
|
300
|
-
if (result.cleanupFailed) return {
|
|
301
|
-
failed: true,
|
|
302
|
-
reason: failures.join(" ")
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
failed: true,
|
|
307
|
-
reason: failures.join(" ") || "No managed tunnel provider was selected."
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function closeChildPipes(child) {
|
|
311
|
-
child.stdout?.destroy?.();
|
|
312
|
-
child.stderr?.destroy?.();
|
|
313
|
-
}
|
|
314
|
-
function unrefChildPipes(child) {
|
|
315
|
-
resumeChildPipes(child);
|
|
316
|
-
for (const stream of [child.stdout, child.stderr]) stream?.unref?.();
|
|
317
|
-
}
|
|
318
|
-
function resumeChildPipes(child) {
|
|
319
|
-
child.stdout?.resume?.();
|
|
320
|
-
child.stderr?.resume?.();
|
|
321
|
-
}
|
|
322
|
-
async function signalAndWaitForExit(child, signal, { timeoutMs, now, sleep, isAlive, platform }) {
|
|
323
|
-
let exited = false;
|
|
324
|
-
const onExit = () => {
|
|
325
|
-
exited = true;
|
|
326
|
-
};
|
|
327
|
-
child.once("exit", onExit);
|
|
328
|
-
try {
|
|
329
|
-
if (platform === "win32" && child.pid) signalProcessTree(child.pid, signal, { platform });
|
|
330
|
-
child.kill(signal);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
child.removeListener("exit", onExit);
|
|
333
|
-
return isEsrch(err);
|
|
334
|
-
}
|
|
335
|
-
const hasExited = () => exited;
|
|
336
|
-
const deadline = now() + timeoutMs;
|
|
337
|
-
while (!hasExited() && now() < deadline) await sleep(CLEANUP_POLL_MS);
|
|
338
|
-
if (!hasExited() && child.pid && !isAlive(child.pid)) exited = true;
|
|
339
|
-
child.removeListener("exit", onExit);
|
|
340
|
-
return hasExited();
|
|
341
|
-
}
|
|
342
|
-
async function terminateChild(child, { alreadyExited, timeoutMs, now, sleep, isAlive, platform = process.platform }) {
|
|
343
|
-
if (alreadyExited) {
|
|
344
|
-
closeChildPipes(child);
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
if (await signalAndWaitForExit(child, "SIGTERM", {
|
|
348
|
-
timeoutMs,
|
|
349
|
-
now,
|
|
350
|
-
sleep,
|
|
351
|
-
isAlive,
|
|
352
|
-
platform
|
|
353
|
-
})) {
|
|
354
|
-
closeChildPipes(child);
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
const killed = await signalAndWaitForExit(child, "SIGKILL", {
|
|
358
|
-
timeoutMs,
|
|
359
|
-
now,
|
|
360
|
-
sleep,
|
|
361
|
-
isAlive,
|
|
362
|
-
platform
|
|
363
|
-
});
|
|
364
|
-
closeChildPipes(child);
|
|
365
|
-
return killed;
|
|
366
|
-
}
|
|
367
|
-
function describe(err) {
|
|
368
|
-
return err?.message || String(err);
|
|
369
|
-
}
|
|
370
|
-
const STOP_TIMEOUT_MS = 5e3;
|
|
371
|
-
const STOP_POLL_MS = 100;
|
|
372
|
-
function isEsrch(err) {
|
|
373
|
-
return err?.code === "ESRCH";
|
|
374
|
-
}
|
|
375
|
-
async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, kill = (pid) => signalProcessTree(pid, "SIGTERM", { platform }), now = Date.now, sleep = defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
|
|
376
|
-
const missing = () => {
|
|
377
|
-
removeRecordedTunnelLogFile(record?.logFile);
|
|
378
|
-
return { status: "missing" };
|
|
379
|
-
};
|
|
380
|
-
const pid = record?.pid;
|
|
381
|
-
if (!pid || !isAlive(pid)) return missing();
|
|
382
|
-
if (!record.processToken) return {
|
|
383
|
-
status: "failed",
|
|
384
|
-
reason: `tunnel pid ${pid} has no process identity token, so Stim cannot verify ownership. Keep the record. Inspect the process, stop it with the provider tooling, remove the stale metroTunnel state, and retry.`
|
|
385
|
-
};
|
|
386
|
-
const identity = inspectIdentity(record);
|
|
387
|
-
if (identity === "gone" || identity === "different") return missing();
|
|
388
|
-
if (identity !== "same") return {
|
|
389
|
-
status: "failed",
|
|
390
|
-
reason: `could not verify the process identity for tunnel pid ${pid}; refusing to signal it.`
|
|
391
|
-
};
|
|
392
|
-
try {
|
|
393
|
-
kill(pid);
|
|
394
|
-
} catch (err) {
|
|
395
|
-
return isEsrch(err) ? missing() : {
|
|
396
|
-
status: "failed",
|
|
397
|
-
reason: describe(err)
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
const deadline = now() + timeoutMs;
|
|
401
|
-
const exited = () => {
|
|
402
|
-
if (!isAlive(pid)) return true;
|
|
403
|
-
const current = inspectIdentity(record);
|
|
404
|
-
return current === "gone" || current === "different";
|
|
405
|
-
};
|
|
406
|
-
while (now() < deadline && !exited()) await sleep(STOP_POLL_MS);
|
|
407
|
-
if (!exited()) return {
|
|
408
|
-
status: "failed",
|
|
409
|
-
reason: `pid ${pid} did not exit within ${timeoutMs}ms.`
|
|
410
|
-
};
|
|
411
|
-
removeRecordedTunnelLogFile(record.logFile);
|
|
412
|
-
return { status: "stopped" };
|
|
413
|
-
}
|
|
414
|
-
//#endregion
|
|
415
|
-
//#region src/supervisor/ownership.ts
|
|
416
|
-
function resolveSupervisorTarget({ state, record, reservedPort, isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity } = {}) {
|
|
417
|
-
const statePid = numberOrNull(state?.pid);
|
|
418
|
-
const recordPid = numberOrNull(record?.pid);
|
|
419
|
-
const pid = statePid ?? recordPid;
|
|
420
|
-
if (!pid) return { status: "none" };
|
|
421
|
-
if (statePid && recordPid && statePid !== recordPid) return {
|
|
422
|
-
status: "unverified",
|
|
423
|
-
pid,
|
|
424
|
-
reason: `workspace state.json records supervisor pid ${statePid} but the registry records pid ${recordPid}`
|
|
425
|
-
};
|
|
426
|
-
if (!isAlive(pid)) return {
|
|
427
|
-
status: "stale",
|
|
428
|
-
pid
|
|
429
|
-
};
|
|
430
|
-
const port = numberOrNull(state?.port) ?? numberOrNull(record?.port);
|
|
431
|
-
if (reservedPort !== null && reservedPort !== void 0 && port !== null && port !== reservedPort) return {
|
|
432
|
-
status: "unverified",
|
|
433
|
-
pid,
|
|
434
|
-
reason: `supervisor pid ${pid} records port ${port}, but this project reserved port ${reservedPort}`
|
|
435
|
-
};
|
|
436
|
-
if (reservedPort !== null && reservedPort !== void 0 && port === null) return {
|
|
437
|
-
status: "unverified",
|
|
438
|
-
pid,
|
|
439
|
-
reason: `supervisor pid ${pid} has no recorded port, so it cannot be matched against reserved port ${reservedPort}`
|
|
440
|
-
};
|
|
441
|
-
const identityRecord = statePid ? state : record;
|
|
442
|
-
if (statePid && recordPid && state?.processToken !== record?.processToken) return {
|
|
443
|
-
status: "unverified",
|
|
444
|
-
pid,
|
|
445
|
-
reason: "workspace state and registry process identities disagree"
|
|
446
|
-
};
|
|
447
|
-
const identity = inspectIdentity(identityRecord);
|
|
448
|
-
if (identity === "gone" || identity === "different") return {
|
|
449
|
-
status: "stale",
|
|
450
|
-
pid
|
|
451
|
-
};
|
|
452
|
-
if (identity !== "same") return {
|
|
453
|
-
status: "unverified",
|
|
454
|
-
pid,
|
|
455
|
-
reason: "the recorded process identity could not be verified"
|
|
456
|
-
};
|
|
457
|
-
return {
|
|
458
|
-
status: "ours",
|
|
459
|
-
processToken: identityRecord.processToken,
|
|
460
|
-
pid,
|
|
461
|
-
port,
|
|
462
|
-
mode: state?.mode ?? record?.mode ?? null,
|
|
463
|
-
startedAt: state?.startedAt ?? record?.startedAt ?? null
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function numberOrNull(v) {
|
|
467
|
-
return typeof v === "number" && Number.isSafeInteger(v) && v > 0 ? v : null;
|
|
468
|
-
}
|
|
469
|
-
//#endregion
|
|
470
|
-
export { withManagedRemoteWorktreeLock as a, withManagedTunnelRemovalLock as c, terminateChild as i, startTunnelSequence as n, withManagedRemoteWorktreeRemovalLock as o, stopTunnel as r, withManagedTunnelLock as s, resolveSupervisorTarget as t };
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { resolve4 } from "node:dns/promises";
|
|
2
|
-
import { request } from "node:https";
|
|
3
|
-
//#region src/engine/public-http-probe.ts
|
|
4
|
-
const DNS_TIMEOUT_MS = 5e3;
|
|
5
|
-
async function resolveAddresses(hostname, signal) {
|
|
6
|
-
if (signal.aborted) throw signal.reason;
|
|
7
|
-
let timer;
|
|
8
|
-
let onAbort;
|
|
9
|
-
const unavailable = new Promise((_resolve, reject) => {
|
|
10
|
-
onAbort = () => reject(signal.reason);
|
|
11
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
12
|
-
if (signal.aborted) onAbort();
|
|
13
|
-
else timer = setTimeout(() => reject(/* @__PURE__ */ new Error("DNS lookup timed out")), DNS_TIMEOUT_MS);
|
|
14
|
-
});
|
|
15
|
-
try {
|
|
16
|
-
return await Promise.race([resolve4(hostname), unavailable]);
|
|
17
|
-
} finally {
|
|
18
|
-
clearTimeout(timer);
|
|
19
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
async function probePublicHttp(url, signal) {
|
|
23
|
-
try {
|
|
24
|
-
return (await fetch(url, {
|
|
25
|
-
signal,
|
|
26
|
-
redirect: "follow"
|
|
27
|
-
})).status;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
const cause = error?.cause;
|
|
30
|
-
if (cause?.code !== "ENOTFOUND" && cause?.code !== "EAI_AGAIN") return null;
|
|
31
|
-
}
|
|
32
|
-
const hostname = new URL(url).hostname;
|
|
33
|
-
if (!hostname.endsWith(".trycloudflare.com")) return null;
|
|
34
|
-
let addresses;
|
|
35
|
-
try {
|
|
36
|
-
addresses = await resolveAddresses(hostname, signal);
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
const firstAddress = addresses[0];
|
|
41
|
-
if (!firstAddress) return null;
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const request$1 = request(url, {
|
|
44
|
-
signal,
|
|
45
|
-
lookup: (_hostname, options, callback) => {
|
|
46
|
-
if (options.all) callback(null, addresses.map((address) => ({
|
|
47
|
-
address,
|
|
48
|
-
family: 4
|
|
49
|
-
})));
|
|
50
|
-
else callback(null, firstAddress, 4);
|
|
51
|
-
}
|
|
52
|
-
}, (response) => {
|
|
53
|
-
response.resume();
|
|
54
|
-
resolve(response.statusCode ?? null);
|
|
55
|
-
});
|
|
56
|
-
request$1.on("error", () => resolve(null));
|
|
57
|
-
request$1.end();
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
export { probePublicHttp as t };
|