@saptools/cf-debugger 0.1.0 → 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dongtran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.js CHANGED
@@ -202,6 +202,43 @@ import { execFile as execFile2 } from "child_process";
202
202
  import { createConnection, createServer } from "net";
203
203
  import { promisify as promisify2 } from "util";
204
204
  var execFileAsync2 = promisify2(execFile2);
205
+ async function findListeningPidsWithNetstat(port) {
206
+ try {
207
+ const { stdout } = await execFileAsync2("netstat", ["-ano"]);
208
+ const pids = /* @__PURE__ */ new Set();
209
+ for (const line of stdout.split("\n")) {
210
+ if (!line.includes(`:${port.toString()}`) || !line.includes("LISTENING")) {
211
+ continue;
212
+ }
213
+ const parts = line.trim().split(/\s+/);
214
+ const last = parts[parts.length - 1];
215
+ if (last === void 0) {
216
+ continue;
217
+ }
218
+ const pid = Number.parseInt(last, 10);
219
+ if (!Number.isNaN(pid)) {
220
+ pids.add(pid);
221
+ }
222
+ }
223
+ return [...pids];
224
+ } catch {
225
+ return [];
226
+ }
227
+ }
228
+ async function findListeningPidsWithLsof(port) {
229
+ try {
230
+ const { stdout } = await execFileAsync2("lsof", ["-nP", "-t", "-i", `tcp:${port.toString()}`, "-sTCP:LISTEN"]);
231
+ return stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => Number.parseInt(line, 10)).filter((pid) => !Number.isNaN(pid));
232
+ } catch {
233
+ return [];
234
+ }
235
+ }
236
+ async function findListeningPids(port) {
237
+ if (process.platform === "win32") {
238
+ return await findListeningPidsWithNetstat(port);
239
+ }
240
+ return await findListeningPidsWithLsof(port);
241
+ }
205
242
  async function isPortFree(port) {
206
243
  return await new Promise((resolve) => {
207
244
  const server = createServer();
@@ -245,24 +282,15 @@ async function probeTunnelReady(port, timeoutMs) {
245
282
  }
246
283
  return false;
247
284
  }
285
+ async function findListeningProcessId(port) {
286
+ const pids = await findListeningPids(port);
287
+ return pids[0];
288
+ }
248
289
  async function killProcessOnPort(port) {
249
290
  const portStr = port.toString();
250
291
  if (process.platform === "win32") {
251
292
  try {
252
- const { stdout } = await execFileAsync2("netstat", ["-ano"]);
253
- const pids = /* @__PURE__ */ new Set();
254
- for (const line of stdout.split("\n")) {
255
- if (line.includes(`:${portStr}`) && line.includes("LISTENING")) {
256
- const parts = line.trim().split(/\s+/);
257
- const last = parts[parts.length - 1];
258
- if (last !== void 0) {
259
- const pid = Number.parseInt(last, 10);
260
- if (!Number.isNaN(pid)) {
261
- pids.add(pid);
262
- }
263
- }
264
- }
265
- }
293
+ const pids = await findListeningPidsWithNetstat(port);
266
294
  for (const pid of pids) {
267
295
  try {
268
296
  await execFileAsync2("taskkill", ["/F", "/PID", pid.toString()]);
@@ -275,14 +303,15 @@ async function killProcessOnPort(port) {
275
303
  }
276
304
  try {
277
305
  const { stdout } = await execFileAsync2("lsof", ["-t", "-i", `tcp:${portStr}`]);
278
- const lines = stdout.trim().split("\n").filter((l) => l.length > 0);
279
- for (const pidStr of lines) {
280
- const pid = Number.parseInt(pidStr, 10);
281
- if (!Number.isNaN(pid)) {
282
- try {
283
- process.kill(pid, "SIGKILL");
284
- } catch {
285
- }
306
+ const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
307
+ for (const line of lines) {
308
+ const pid = Number.parseInt(line, 10);
309
+ if (Number.isNaN(pid)) {
310
+ continue;
311
+ }
312
+ try {
313
+ process.kill(pid, "SIGKILL");
314
+ } catch {
286
315
  }
287
316
  }
288
317
  } catch {
@@ -578,6 +607,39 @@ async function updateSessionStatus(sessionId, status, message) {
578
607
  return updated;
579
608
  });
580
609
  }
610
+ async function updateSessionPid(sessionId, pid) {
611
+ return await withFileLock(stateLockPath(), async () => {
612
+ const raw = await readStateRaw();
613
+ let updated;
614
+ const nextSessions = raw.sessions.map((session) => {
615
+ if (session.sessionId !== sessionId) {
616
+ return session;
617
+ }
618
+ const next = {
619
+ sessionId: session.sessionId,
620
+ pid,
621
+ hostname: session.hostname,
622
+ region: session.region,
623
+ org: session.org,
624
+ space: session.space,
625
+ app: session.app,
626
+ apiEndpoint: session.apiEndpoint,
627
+ localPort: session.localPort,
628
+ remotePort: session.remotePort,
629
+ cfHomeDir: session.cfHomeDir,
630
+ startedAt: session.startedAt,
631
+ status: session.status,
632
+ ...session.message === void 0 ? {} : { message: session.message }
633
+ };
634
+ updated = next;
635
+ return next;
636
+ });
637
+ if (updated !== void 0) {
638
+ await writeState({ version: "1", sessions: nextSessions });
639
+ }
640
+ return updated;
641
+ });
642
+ }
581
643
  async function removeSession(sessionId) {
582
644
  return await withFileLock(stateLockPath(), async () => {
583
645
  const raw = await readStateRaw();
@@ -597,41 +659,45 @@ var POST_USR1_DELAY_MS = 300;
597
659
  var PORT_CLEANUP_DELAY_MS = 600;
598
660
  var CHILD_SIGTERM_GRACE_MS = 2e3;
599
661
  var PORT_RECLAIM_DELAY_MS = 250;
600
- async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
601
- if (child.pid === void 0) {
602
- return;
603
- }
604
- if (child.exitCode !== null || child.signalCode !== null) {
605
- return;
606
- }
662
+ var PID_TERMINATION_POLL_MS = 100;
663
+ function signalPidOrGroup(pid, signal) {
607
664
  const isWindows = process3.platform === "win32";
608
- const send = (sig) => {
665
+ if (!isWindows) {
609
666
  try {
610
- if (!isWindows && child.pid !== void 0) {
611
- process3.kill(-child.pid, sig);
612
- } else {
613
- child.kill(sig);
614
- }
667
+ process3.kill(-pid, signal);
668
+ return;
615
669
  } catch {
616
670
  }
617
- };
618
- send("SIGTERM");
619
- const closed = await new Promise((resolve) => {
620
- if (child.exitCode !== null || child.signalCode !== null) {
621
- resolve(true);
671
+ }
672
+ try {
673
+ process3.kill(pid, signal);
674
+ } catch {
675
+ }
676
+ }
677
+ async function terminatePidOrGroup(pid, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
678
+ if (!isPidAlive(pid)) {
679
+ return;
680
+ }
681
+ signalPidOrGroup(pid, "SIGTERM");
682
+ const startedAt = Date.now();
683
+ while (Date.now() - startedAt < timeoutMs) {
684
+ if (!isPidAlive(pid)) {
622
685
  return;
623
686
  }
624
- const t = setTimeout(() => {
625
- resolve(false);
626
- }, timeoutMs);
627
- child.once("close", () => {
628
- clearTimeout(t);
629
- resolve(true);
687
+ await new Promise((resolve) => {
688
+ setTimeout(resolve, PID_TERMINATION_POLL_MS);
630
689
  });
631
- });
632
- if (!closed) {
633
- send("SIGKILL");
634
690
  }
691
+ signalPidOrGroup(pid, "SIGKILL");
692
+ }
693
+ async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
694
+ if (child.pid === void 0) {
695
+ return;
696
+ }
697
+ if (child.exitCode !== null || child.signalCode !== null) {
698
+ return;
699
+ }
700
+ await terminatePidOrGroup(child.pid, timeoutMs);
635
701
  }
636
702
  async function pruneAndCleanupOrphans() {
637
703
  const result = await readAndPruneActiveSessions();
@@ -792,6 +858,9 @@ async function startDebugger(options) {
792
858
  }
793
859
  }
794
860
  child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);
861
+ if (child.pid !== void 0) {
862
+ await updateSessionPid(session.sessionId, child.pid);
863
+ }
795
864
  child.on("close", (code) => {
796
865
  tunnelClosed = true;
797
866
  exitResolve?.(code);
@@ -807,9 +876,14 @@ async function startDebugger(options) {
807
876
  `SSH tunnel on port ${session.localPort.toString()} did not become ready within ${Math.round(tunnelReadyTimeoutMs / 1e3).toString()}s.`
808
877
  );
809
878
  }
879
+ const listeningPid = await findListeningProcessId(session.localPort);
880
+ const activePid = listeningPid ?? child.pid ?? session.pid;
881
+ if (activePid !== session.pid) {
882
+ await updateSessionPid(session.sessionId, activePid);
883
+ }
810
884
  emit("ready");
811
885
  const readySession = await updateSessionStatus(session.sessionId, "ready");
812
- const activeSession = readySession ?? { ...session, status: "ready" };
886
+ const activeSession = readySession ?? { ...session, pid: activePid, status: "ready" };
813
887
  let disposePromise;
814
888
  const handle = {
815
889
  session: activeSession,
@@ -847,7 +921,7 @@ async function stopDebugger(options) {
847
921
  }
848
922
  if (target.pid !== process3.pid) {
849
923
  try {
850
- process3.kill(target.pid, "SIGTERM");
924
+ await terminatePidOrGroup(target.pid);
851
925
  } catch {
852
926
  }
853
927
  }
@@ -859,7 +933,7 @@ async function stopDebugger(options) {
859
933
  await rm(target.cfHomeDir, { recursive: true, force: true });
860
934
  } catch {
861
935
  }
862
- return removed;
936
+ return removed ?? target;
863
937
  }
864
938
  async function stopAllDebuggers() {
865
939
  const sessions = await pruneAndCleanupOrphans();
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/debugger.ts","../src/cf.ts","../src/types.ts","../src/paths.ts","../src/port.ts","../src/regions.ts","../src/state.ts","../src/lock.ts"],"sourcesContent":["import process from \"node:process\";\n\nimport { Command } from \"commander\";\n\nimport {\n getSession,\n listSessions,\n startDebugger,\n stopAllDebuggers,\n stopDebugger,\n} from \"./debugger.js\";\nimport type { SessionKey, SessionStatus } from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nfunction readRequiredOption(value: string | undefined, flag: string): string {\n if (value === undefined || value === \"\") {\n process.stderr.write(`Missing required option ${flag}\\n`);\n process.exit(1);\n }\n return value;\n}\n\nfunction parseOptionalPort(raw: string | undefined): number | undefined {\n if (raw === undefined) {\n return undefined;\n }\n const port = Number.parseInt(raw, 10);\n if (Number.isNaN(port) || port <= 0 || port > 65_535) {\n process.stderr.write(`Invalid port: ${raw}\\n`);\n process.exit(1);\n }\n return port;\n}\n\nfunction parseOptionalTimeout(raw: string | undefined): number | undefined {\n if (raw === undefined) {\n return undefined;\n }\n const seconds = Number.parseInt(raw, 10);\n if (Number.isNaN(seconds) || seconds <= 0) {\n process.stderr.write(`Invalid timeout: ${raw}\\n`);\n process.exit(1);\n }\n return seconds * 1000;\n}\n\ninterface StartCommandOptions {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n readonly port?: string;\n readonly timeout?: string;\n readonly verbose?: boolean;\n}\n\ninterface StopCommandOptions {\n readonly region?: string;\n readonly org?: string;\n readonly space?: string;\n readonly app?: string;\n readonly sessionId?: string;\n readonly all?: boolean;\n}\n\ninterface StatusCommandOptions {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nfunction logStatus(verbose: boolean, status: SessionStatus, message?: string): void {\n if (verbose) {\n const suffix = message === undefined ? \"\" : `: ${message}`;\n process.stdout.write(`[cf-debugger] ${status}${suffix}\\n`);\n }\n}\n\nasync function handleStart(opts: StartCommandOptions): Promise<void> {\n const region = readRequiredOption(opts.region, \"--region\");\n const org = readRequiredOption(opts.org, \"--org\");\n const space = readRequiredOption(opts.space, \"--space\");\n const app = readRequiredOption(opts.app, \"--app\");\n const verbose = opts.verbose ?? false;\n\n const preferredPort = parseOptionalPort(opts.port);\n const tunnelReadyTimeoutMs = parseOptionalTimeout(opts.timeout);\n\n const abortController = new AbortController();\n const onStartupSignal = (exitCode: number) => (): void => {\n abortController.abort();\n process.stderr.write(`\\nAborting startup for ${app}...\\n`);\n setTimeout(() => {\n process.exit(exitCode);\n }, 5_000).unref();\n };\n const startupSigint = onStartupSignal(130);\n const startupSigterm = onStartupSignal(143);\n process.on(\"SIGINT\", startupSigint);\n process.on(\"SIGTERM\", startupSigterm);\n\n let handle;\n try {\n handle = await startDebugger({\n region,\n org,\n space,\n app,\n verbose,\n signal: abortController.signal,\n ...(preferredPort === undefined ? {} : { preferredPort }),\n ...(tunnelReadyTimeoutMs === undefined ? {} : { tunnelReadyTimeoutMs }),\n onStatus: (status, message) => {\n logStatus(verbose, status, message);\n },\n });\n } finally {\n process.off(\"SIGINT\", startupSigint);\n process.off(\"SIGTERM\", startupSigterm);\n }\n\n process.stdout.write(\n `Debugger ready for ${app} (${region}/${org}/${space}).\\n` +\n ` Local port: ${handle.session.localPort.toString()}\\n` +\n ` Remote port: ${handle.session.remotePort.toString()}\\n` +\n ` Session id: ${handle.session.sessionId}\\n` +\n ` PID: ${handle.session.pid.toString()}\\n` +\n `Press Ctrl+C to stop.\\n`,\n );\n\n let disposePromise: Promise<void> | undefined;\n const dispose = async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n process.stdout.write(`\\nStopping debugger for ${app}...\\n`);\n try {\n await handle.dispose();\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`Error during stop: ${msg}\\n`);\n }\n })();\n await disposePromise;\n };\n\n process.on(\"SIGINT\", () => {\n void dispose().then(() => {\n process.exit(130);\n });\n });\n process.on(\"SIGTERM\", () => {\n void dispose().then(() => {\n process.exit(143);\n });\n });\n\n const code = await handle.waitForExit();\n await dispose();\n process.exit(code ?? 0);\n}\n\nfunction resolveKeyFromOpts(opts: StopCommandOptions): SessionKey | undefined {\n if (\n opts.region !== undefined &&\n opts.org !== undefined &&\n opts.space !== undefined &&\n opts.app !== undefined\n ) {\n return {\n region: opts.region,\n org: opts.org,\n space: opts.space,\n app: opts.app,\n };\n }\n return undefined;\n}\n\nasync function handleStop(opts: StopCommandOptions): Promise<void> {\n if (opts.all === true) {\n const count = await stopAllDebuggers();\n process.stdout.write(`Stopped ${count.toString()} session(s).\\n`);\n return;\n }\n const key = resolveKeyFromOpts(opts);\n const result = await stopDebugger({\n ...(opts.sessionId === undefined ? {} : { sessionId: opts.sessionId }),\n ...(key === undefined ? {} : { key }),\n });\n if (result === undefined) {\n process.stderr.write(\"No matching session found.\\n\");\n process.exit(1);\n }\n process.stdout.write(\n `Stopped session ${result.sessionId} (${result.app}, port ${result.localPort.toString()}).\\n`,\n );\n}\n\nasync function handleList(): Promise<void> {\n const sessions = await listSessions();\n process.stdout.write(`${JSON.stringify(sessions, null, 2)}\\n`);\n}\n\nasync function handleStatus(opts: StatusCommandOptions): Promise<void> {\n const session = await getSession({\n region: opts.region,\n org: opts.org,\n space: opts.space,\n app: opts.app,\n });\n process.stdout.write(`${JSON.stringify(session ?? null, null, 2)}\\n`);\n}\n\nexport async function main(argv: readonly string[]): Promise<void> {\n const program = new Command();\n\n program\n .name(\"cf-debugger\")\n .description(\"Open an SSH debug tunnel to a SAP BTP Cloud Foundry app's Node.js inspector\");\n\n program\n .command(\"start\")\n .description(\"Open a debug tunnel for one app\")\n .requiredOption(\"--region <key>\", \"CF region key (e.g. eu10)\")\n .requiredOption(\"--org <name>\", \"CF org name\")\n .requiredOption(\"--space <name>\", \"CF space name\")\n .requiredOption(\"--app <name>\", \"CF app name\")\n .option(\"--port <number>\", \"Preferred local port (auto-assigned if omitted)\")\n .option(\"--timeout <seconds>\", \"Tunnel-ready timeout in seconds (default: 30)\")\n .option(\"--verbose\", \"Print status transitions\", false)\n .action(async (opts: StartCommandOptions): Promise<void> => {\n await handleStart(opts);\n });\n\n program\n .command(\"stop\")\n .description(\"Stop one session (by key or id) or all sessions with --all\")\n .option(\"--region <key>\")\n .option(\"--org <name>\")\n .option(\"--space <name>\")\n .option(\"--app <name>\")\n .option(\"--session-id <id>\")\n .option(\"--all\", \"Stop every active session\", false)\n .action(async (opts: StopCommandOptions): Promise<void> => {\n await handleStop(opts);\n });\n\n program\n .command(\"list\")\n .description(\"Print every active debugger session as JSON\")\n .action(async (): Promise<void> => {\n await handleList();\n });\n\n program\n .command(\"status\")\n .description(\"Print one session by key as JSON (null if not active)\")\n .requiredOption(\"--region <key>\")\n .requiredOption(\"--org <name>\")\n .requiredOption(\"--space <name>\")\n .requiredOption(\"--app <name>\")\n .action(async (opts: StatusCommandOptions): Promise<void> => {\n await handleStatus(opts);\n });\n\n await program.parseAsync([...argv]);\n}\n\ntry {\n await main(process.argv);\n} catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n if (err.code === \"ABORTED\") {\n process.stderr.write(`Aborted: ${err.message}\\n`);\n process.exit(130);\n }\n process.stderr.write(`Error [${err.code}]: ${err.message}\\n`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`Error: ${msg}\\n`);\n }\n process.exit(1);\n}\n","import type { ChildProcess } from \"node:child_process\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport process from \"node:process\";\n\nimport type { CfExecContext } from \"./cf.js\";\nimport {\n cfEnableSsh,\n cfLogin,\n cfRestartApp,\n cfSshEnabled,\n cfSshOneShot,\n cfTarget,\n isSshDisabledError,\n spawnSshTunnel,\n} from \"./cf.js\";\nimport { sessionCfHomeDir } from \"./paths.js\";\nimport { isPortFree, killProcessOnPort, probeTunnelReady } from \"./port.js\";\nimport { resolveApiEndpoint } from \"./regions.js\";\nimport {\n matchesKey,\n readAndPruneActiveSessions,\n registerNewSession,\n removeSession,\n sessionKeyString,\n updateSessionStatus,\n} from \"./state.js\";\nimport type {\n ActiveSession,\n DebuggerHandle,\n SessionKey,\n SessionStatus,\n StartDebuggerOptions,\n} from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nconst DEFAULT_TUNNEL_READY_TIMEOUT_MS = 30_000;\nconst POST_USR1_DELAY_MS = 300;\nconst PORT_CLEANUP_DELAY_MS = 600;\nconst CHILD_SIGTERM_GRACE_MS = 2_000;\nconst PORT_RECLAIM_DELAY_MS = 250;\n\nasync function killProcessGroupOrProc(\n child: ChildProcess,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (child.pid === undefined) {\n return;\n }\n if (child.exitCode !== null || child.signalCode !== null) {\n return;\n }\n const isWindows = process.platform === \"win32\";\n const send = (sig: NodeJS.Signals): void => {\n try {\n if (!isWindows && child.pid !== undefined) {\n process.kill(-child.pid, sig);\n } else {\n child.kill(sig);\n }\n } catch {\n // already gone\n }\n };\n send(\"SIGTERM\");\n const closed = await new Promise<boolean>((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve(true);\n return;\n }\n const t = setTimeout(() => {\n resolve(false);\n }, timeoutMs);\n child.once(\"close\", () => {\n clearTimeout(t);\n resolve(true);\n });\n });\n if (!closed) {\n send(\"SIGKILL\");\n }\n}\n\nasync function pruneAndCleanupOrphans(): Promise<readonly ActiveSession[]> {\n const result = await readAndPruneActiveSessions();\n const host = getHostname();\n for (const removed of result.removed) {\n if (removed.hostname === host) {\n void killProcessOnPort(removed.localPort);\n }\n }\n return result.sessions;\n}\n\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CfDebuggerError(\"ABORTED\", \"Operation aborted by caller\");\n }\n}\n\nfunction requireCredentials(options: StartDebuggerOptions): {\n readonly email: string;\n readonly password: string;\n} {\n const email = options.email ?? process.env[\"SAP_EMAIL\"];\n const password = options.password ?? process.env[\"SAP_PASSWORD\"];\n if (email === undefined || email === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP email is required. Pass `email` or set SAP_EMAIL env var.\",\n );\n }\n if (password === undefined || password === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP password is required. Pass `password` or set SAP_PASSWORD env var.\",\n );\n }\n return { email, password };\n}\n\nexport async function startDebugger(options: StartDebuggerOptions): Promise<DebuggerHandle> {\n const { email, password } = requireCredentials(options);\n const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);\n const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;\n const emit = (status: SessionStatus, message?: string): void => {\n options.onStatus?.(status, message);\n };\n\n checkAbort(options.signal);\n\n await pruneAndCleanupOrphans();\n\n const registration = await registerNewSession({\n region: options.region,\n org: options.org,\n space: options.space,\n app: options.app,\n apiEndpoint,\n ...(options.preferredPort === undefined ? {} : { preferredPort: options.preferredPort }),\n portProbe: isPortFree,\n cfHomeForSession: sessionCfHomeDir,\n });\n\n if (registration.existing) {\n throw new CfDebuggerError(\n \"SESSION_ALREADY_RUNNING\",\n `A debugger session is already running for ${sessionKeyString(options)} ` +\n `on port ${registration.existing.localPort.toString()} ` +\n `(pid ${registration.existing.pid.toString()}, sessionId ${registration.existing.sessionId}). ` +\n `Stop it first with \\`cf-debugger stop\\`.`,\n );\n }\n\n const session = registration.session;\n const context: CfExecContext = { cfHome: session.cfHomeDir };\n\n let child: ChildProcess | undefined;\n let tunnelClosed = false;\n let exitResolve: ((code: number | null) => void) | undefined;\n const exitPromise = new Promise<number | null>((resolve) => {\n exitResolve = resolve;\n });\n\n const cleanupFilesystem = async (): Promise<void> => {\n try {\n await rm(session.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n };\n\n const finalize = async (): Promise<void> => {\n if (!tunnelClosed) {\n tunnelClosed = true;\n if (child) {\n await killProcessGroupOrProc(child);\n }\n setTimeout(() => {\n void killProcessOnPort(session.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n }\n await removeSession(session.sessionId);\n await cleanupFilesystem();\n emit(\"stopped\");\n };\n\n try {\n await mkdir(session.cfHomeDir, { recursive: true });\n\n emit(\"logging-in\");\n await updateSessionStatus(session.sessionId, \"logging-in\");\n await cfLogin(apiEndpoint, email, password, context);\n checkAbort(options.signal);\n\n emit(\"targeting\");\n await updateSessionStatus(session.sessionId, \"targeting\");\n await cfTarget(options.org, options.space, context);\n checkAbort(options.signal);\n\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 200);\n });\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const signalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n\n if (isSshDisabledError(signalResult.stderr)) {\n const alreadyEnabled = await cfSshEnabled(options.app, context);\n if (!alreadyEnabled) {\n emit(\"ssh-enabling\", \"Enabling SSH on the app\");\n await updateSessionStatus(session.sessionId, \"ssh-enabling\");\n await cfEnableSsh(options.app, context);\n }\n emit(\"ssh-restarting\", \"Restarting app so SSH becomes active\");\n await updateSessionStatus(session.sessionId, \"ssh-restarting\");\n await cfRestartApp(options.app, context);\n checkAbort(options.signal);\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const retrySignalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n if (retrySignalResult.exitCode !== 0) {\n const detail =\n retrySignalResult.stderr.trim().length > 0\n ? retrySignalResult.stderr.trim()\n : `exit code ${String(retrySignalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,\n retrySignalResult.stderr,\n );\n }\n } else if (signalResult.exitCode !== 0) {\n const detail =\n signalResult.stderr.trim().length > 0\n ? signalResult.stderr.trim()\n : `exit code ${String(signalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,\n signalResult.stderr,\n );\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, POST_USR1_DELAY_MS);\n });\n checkAbort(options.signal);\n\n emit(\"tunneling\");\n await updateSessionStatus(session.sessionId, \"tunneling\");\n\n if (!(await isPortFree(session.localPort))) {\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PORT_RECLAIM_DELAY_MS);\n });\n if (!(await isPortFree(session.localPort))) {\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `Local port ${session.localPort.toString()} is in use and could not be reclaimed for the tunnel.`,\n );\n }\n }\n\n child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);\n\n child.on(\"close\", (code) => {\n tunnelClosed = true;\n exitResolve?.(code);\n });\n\n child.on(\"error\", (err: Error) => {\n emit(\"error\", err.message);\n });\n\n const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);\n checkAbort(options.signal);\n if (!ready) {\n throw new CfDebuggerError(\n \"TUNNEL_NOT_READY\",\n `SSH tunnel on port ${session.localPort.toString()} did not become ready within ` +\n `${Math.round(tunnelReadyTimeoutMs / 1000).toString()}s.`,\n );\n }\n\n emit(\"ready\");\n const readySession = await updateSessionStatus(session.sessionId, \"ready\");\n const activeSession: ActiveSession = readySession ?? { ...session, status: \"ready\" };\n\n let disposePromise: Promise<void> | undefined;\n const handle: DebuggerHandle = {\n session: activeSession,\n dispose: async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n emit(\"stopping\");\n await updateSessionStatus(session.sessionId, \"stopping\");\n await finalize();\n })();\n await disposePromise;\n },\n waitForExit: async (): Promise<number | null> => {\n return await exitPromise;\n },\n };\n\n return handle;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n emit(\"error\", message);\n await finalize();\n throw err;\n }\n}\n\nexport interface StopOptions {\n readonly sessionId?: string;\n readonly key?: SessionKey;\n}\n\nexport async function stopDebugger(options: StopOptions): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n let target: ActiveSession | undefined;\n if (options.sessionId !== undefined) {\n target = sessions.find((s) => s.sessionId === options.sessionId);\n } else if (options.key !== undefined) {\n const key = options.key;\n target = sessions.find((s) => matchesKey(s, key));\n }\n if (target === undefined) {\n return undefined;\n }\n if (target.pid !== process.pid) {\n try {\n process.kill(target.pid, \"SIGTERM\");\n } catch {\n // process already gone — cleanup below\n }\n }\n setTimeout(() => {\n void killProcessOnPort(target.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n const removed = await removeSession(target.sessionId);\n try {\n await rm(target.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n return removed;\n}\n\nexport async function stopAllDebuggers(): Promise<number> {\n const sessions = await pruneAndCleanupOrphans();\n let stopped = 0;\n for (const session of sessions) {\n const result = await stopDebugger({ sessionId: session.sessionId });\n if (result) {\n stopped += 1;\n }\n }\n return stopped;\n}\n\nexport async function listSessions(): Promise<readonly ActiveSession[]> {\n return await pruneAndCleanupOrphans();\n}\n\nexport async function getSession(key: SessionKey): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n return sessions.find((s) => matchesKey(s, key));\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { CfDebuggerError } from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst MAX_BUFFER = 16 * 1024 * 1024;\nconst CF_CLI_TIMEOUT_MS = 30_000;\nconst CF_RESTART_TIMEOUT_MS = 120_000;\nconst CF_SSH_SIGNAL_TIMEOUT_MS = 15_000;\nconst CF_AUTH_MAX_ATTEMPTS = 3;\n\nexport interface CfExecContext {\n readonly cfHome: string;\n readonly command?: string;\n}\n\nfunction buildEnv(cfHome: string): NodeJS.ProcessEnv {\n return { ...process.env, CF_HOME: cfHome };\n}\n\nfunction resolveBin(context: CfExecContext): string {\n return context.command ?? process.env[\"CF_DEBUGGER_CF_BIN\"] ?? \"cf\";\n}\n\nasync function runCf(\n args: readonly string[],\n context: CfExecContext,\n timeoutMs: number = CF_CLI_TIMEOUT_MS,\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync(resolveBin(context), [...args], {\n env: buildEnv(context.cfHome),\n maxBuffer: MAX_BUFFER,\n timeout: timeoutMs,\n });\n return stdout;\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException & { stderr?: string; stdout?: string };\n const stderr = e.stderr?.trim() ?? \"\";\n throw new CfDebuggerError(\n \"CF_CLI_FAILED\",\n `cf ${args.join(\" \")} failed: ${stderr.length > 0 ? stderr : e.message}`,\n stderr,\n );\n }\n}\n\nexport async function cfApi(apiEndpoint: string, context: CfExecContext): Promise<void> {\n await runCf([\"api\", apiEndpoint], context);\n}\n\nexport async function cfAuth(\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n let lastError: unknown;\n for (let attempt = 0; attempt < CF_AUTH_MAX_ATTEMPTS; attempt++) {\n try {\n await runCf([\"auth\", email, password], context);\n return;\n } catch (err: unknown) {\n lastError = err;\n if (attempt < CF_AUTH_MAX_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 1000 * (attempt + 1));\n });\n }\n }\n }\n if (lastError instanceof Error) {\n throw lastError;\n }\n throw new CfDebuggerError(\"CF_AUTH_FAILED\", `cf auth failed: ${String(lastError)}`);\n}\n\nexport async function cfLogin(\n apiEndpoint: string,\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await cfApi(apiEndpoint, context);\n await cfAuth(email, password, context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_LOGIN_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfTarget(\n org: string,\n space: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await runCf([\"target\", \"-o\", org, \"-s\", space], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_TARGET_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfAppExists(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n await runCf([\"app\", appName], context);\n return true;\n } catch (err: unknown) {\n const stderr = (err as CfDebuggerError).stderr ?? \"\";\n if (stderr.toLowerCase().includes(\"not found\")) {\n return false;\n }\n throw err;\n }\n}\n\nexport async function cfSshEnabled(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n const stdout = await runCf([\"ssh-enabled\", appName], context);\n return stdout.toLowerCase().includes(\"ssh support is enabled\");\n } catch {\n return false;\n }\n}\n\nexport async function cfEnableSsh(appName: string, context: CfExecContext): Promise<void> {\n try {\n await runCf([\"enable-ssh\", appName], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"SSH_NOT_ENABLED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfRestartApp(appName: string, context: CfExecContext): Promise<void> {\n await runCf([\"restart\", appName], context, CF_RESTART_TIMEOUT_MS);\n}\n\nexport interface CfSshSignalResult {\n readonly exitCode: number | null;\n readonly stderr: string;\n}\n\nexport async function cfSshOneShot(\n appName: string,\n command: string,\n context: CfExecContext,\n): Promise<CfSshSignalResult> {\n return await new Promise<CfSshSignalResult>((resolve) => {\n const child = spawn(resolveBin(context), [\"ssh\", appName, \"-c\", command], {\n env: buildEnv(context.cfHome),\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stderrBuf = \"\";\n let settled = false;\n\n const timeout = setTimeout(() => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n child.kill();\n } catch {\n // already gone\n }\n resolve({ exitCode: null, stderr: stderrBuf });\n }, CF_SSH_SIGNAL_TIMEOUT_MS);\n\n child.stderr.on(\"data\", (data: Buffer | string) => {\n stderrBuf += data.toString();\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: code, stderr: stderrBuf });\n });\n\n child.on(\"error\", (err: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: null, stderr: err.message });\n });\n });\n}\n\nexport function isSshDisabledError(stderr: string): boolean {\n const lower = stderr.toLowerCase();\n return lower.includes(\"not authorized\") || lower.includes(\"ssh support is disabled\");\n}\n\nexport function spawnSshTunnel(\n appName: string,\n localPort: number,\n remotePort: number,\n context: CfExecContext,\n): ReturnType<typeof spawn> {\n const tunnelArg = `${localPort.toString()}:localhost:${remotePort.toString()}`;\n const isWindows = process.platform === \"win32\";\n return spawn(resolveBin(context), [\"ssh\", appName, \"-N\", \"-L\", tunnelArg], {\n env: buildEnv(context.cfHome),\n shell: isWindows,\n detached: !isWindows,\n });\n}\n\nexport function parseAppNames(stdout: string): readonly string[] {\n const apps: string[] = [];\n let pastHeader = false;\n for (const line of stdout.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!pastHeader) {\n if (trimmed.startsWith(\"name\")) {\n pastHeader = true;\n }\n continue;\n }\n if (trimmed.length === 0) {\n continue;\n }\n const first = trimmed.split(/\\s+/)[0];\n if (first !== undefined && first.length > 0) {\n apps.push(first);\n }\n }\n return apps;\n}\n\nexport async function cfApps(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"apps\"], context);\n return parseAppNames(stdout);\n}\n\nexport function parseNameTable(stdout: string): readonly string[] {\n const lines = stdout.split(\"\\n\");\n const headerIdx = lines.findIndex((l) => l.trim() === \"name\");\n if (headerIdx === -1) {\n return [];\n }\n return lines\n .slice(headerIdx + 1)\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function cfOrgs(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"orgs\"], context);\n return parseNameTable(stdout);\n}\n\nexport async function cfSpaces(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"spaces\"], context);\n return parseNameTable(stdout);\n}\n","export interface SessionKey {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nexport type SessionStatus =\n | \"starting\"\n | \"logging-in\"\n | \"targeting\"\n | \"ssh-enabling\"\n | \"ssh-restarting\"\n | \"signaling\"\n | \"tunneling\"\n | \"ready\"\n | \"stopping\"\n | \"stopped\"\n | \"error\";\n\nexport interface ActiveSession extends SessionKey {\n readonly sessionId: string;\n readonly pid: number;\n readonly hostname: string;\n readonly localPort: number;\n readonly remotePort: number;\n readonly apiEndpoint: string;\n readonly cfHomeDir: string;\n readonly startedAt: string;\n readonly status: SessionStatus;\n readonly message?: string;\n}\n\nexport interface StartDebuggerOptions extends SessionKey {\n readonly email?: string;\n readonly password?: string;\n readonly apiEndpoint?: string;\n readonly preferredPort?: number;\n readonly tunnelReadyTimeoutMs?: number;\n readonly verbose?: boolean;\n readonly onStatus?: (status: SessionStatus, message?: string) => void;\n readonly signal?: AbortSignal;\n}\n\nexport interface DebuggerHandle {\n readonly session: ActiveSession;\n dispose(): Promise<void>;\n waitForExit(): Promise<number | null>;\n}\n\nexport interface StateFile {\n readonly version: \"1\";\n readonly sessions: readonly ActiveSession[];\n}\n\nexport class CfDebuggerError extends Error {\n public readonly code: string;\n public readonly stderr?: string;\n\n public constructor(code: string, message: string, stderr?: string) {\n super(message);\n this.name = \"CfDebuggerError\";\n this.code = code;\n if (stderr !== undefined) {\n this.stderr = stderr;\n }\n }\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const SAPTOOLS_DIR_NAME = \".saptools\";\nexport const CF_DEBUGGER_STATE_FILENAME = \"cf-debugger-state.json\";\nexport const CF_DEBUGGER_LOCK_FILENAME = \"cf-debugger-state.lock\";\nexport const CF_DEBUGGER_HOMES_DIRNAME = \"cf-debugger-homes\";\n\nexport function saptoolsDir(): string {\n return join(homedir(), SAPTOOLS_DIR_NAME);\n}\n\nexport function stateFilePath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_STATE_FILENAME);\n}\n\nexport function stateLockPath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_LOCK_FILENAME);\n}\n\nexport function sessionCfHomeDir(sessionId: string): string {\n return join(saptoolsDir(), CF_DEBUGGER_HOMES_DIRNAME, sessionId);\n}\n","import { execFile } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function isPortFree(port: number): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close(() => {\n resolve(true);\n });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport async function probeTunnelReady(port: number, timeoutMs: number): Promise<boolean> {\n const pollIntervalMs = 250;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const connected = await new Promise<boolean>((resolve) => {\n const socket = createConnection({ port, host: \"127.0.0.1\" });\n socket.setTimeout(200);\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(true);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.once(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n });\n\n if (connected) {\n return true;\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, pollIntervalMs);\n });\n }\n\n return false;\n}\n\nexport async function killProcessOnPort(port: number): Promise<void> {\n const portStr = port.toString();\n if (process.platform === \"win32\") {\n try {\n const { stdout } = await execFileAsync(\"netstat\", [\"-ano\"]);\n const pids = new Set<number>();\n for (const line of stdout.split(\"\\n\")) {\n if (line.includes(`:${portStr}`) && line.includes(\"LISTENING\")) {\n const parts = line.trim().split(/\\s+/);\n const last = parts[parts.length - 1];\n if (last !== undefined) {\n const pid = Number.parseInt(last, 10);\n if (!Number.isNaN(pid)) {\n pids.add(pid);\n }\n }\n }\n }\n for (const pid of pids) {\n try {\n // cspell:ignore taskkill\n await execFileAsync(\"taskkill\", [\"/F\", \"/PID\", pid.toString()]);\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore\n }\n return;\n }\n\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-t\", \"-i\", `tcp:${portStr}`]);\n const lines = stdout\n .trim()\n .split(\"\\n\")\n .filter((l) => l.length > 0);\n for (const pidStr of lines) {\n const pid = Number.parseInt(pidStr, 10);\n if (!Number.isNaN(pid)) {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already dead\n }\n }\n }\n } catch {\n // lsof missing or no match — ignore\n }\n}\n","export interface RegionInfo {\n readonly key: string;\n readonly apiEndpoint: string;\n}\n\nconst REGION_API_ENDPOINTS: Readonly<Record<string, string>> = {\n ae01: \"https://api.cf.ae01.hana.ondemand.com\",\n ap01: \"https://api.cf.ap01.hana.ondemand.com\",\n ap10: \"https://api.cf.ap10.hana.ondemand.com\",\n ap11: \"https://api.cf.ap11.hana.ondemand.com\",\n ap12: \"https://api.cf.ap12.hana.ondemand.com\",\n ap20: \"https://api.cf.ap20.hana.ondemand.com\",\n ap21: \"https://api.cf.ap21.hana.ondemand.com\",\n ap30: \"https://api.cf.ap30.hana.ondemand.com\",\n br10: \"https://api.cf.br10.hana.ondemand.com\",\n br20: \"https://api.cf.br20.hana.ondemand.com\",\n br30: \"https://api.cf.br30.hana.ondemand.com\",\n ca10: \"https://api.cf.ca10.hana.ondemand.com\",\n ca20: \"https://api.cf.ca20.hana.ondemand.com\",\n ch20: \"https://api.cf.ch20.hana.ondemand.com\",\n eu10: \"https://api.cf.eu10.hana.ondemand.com\",\n eu11: \"https://api.cf.eu11.hana.ondemand.com\",\n eu12: \"https://api.cf.eu12.hana.ondemand.com\",\n eu20: \"https://api.cf.eu20.hana.ondemand.com\",\n eu21: \"https://api.cf.eu21.hana.ondemand.com\",\n eu30: \"https://api.cf.eu30.hana.ondemand.com\",\n eu31: \"https://api.cf.eu31.hana.ondemand.com\",\n in30: \"https://api.cf.in30.hana.ondemand.com\",\n jp10: \"https://api.cf.jp10.hana.ondemand.com\",\n jp20: \"https://api.cf.jp20.hana.ondemand.com\",\n jp30: \"https://api.cf.jp30.hana.ondemand.com\",\n kr30: \"https://api.cf.kr30.hana.ondemand.com\",\n us10: \"https://api.cf.us10.hana.ondemand.com\",\n us11: \"https://api.cf.us11.hana.ondemand.com\",\n us20: \"https://api.cf.us20.hana.ondemand.com\",\n us21: \"https://api.cf.us21.hana.ondemand.com\",\n us30: \"https://api.cf.us30.hana.ondemand.com\",\n us31: \"https://api.cf.us31.hana.ondemand.com\",\n};\n\nexport function resolveApiEndpoint(regionKey: string, override?: string): string {\n if (override !== undefined && override !== \"\") {\n return override;\n }\n const endpoint = REGION_API_ENDPOINTS[regionKey];\n if (endpoint === undefined) {\n throw new Error(\n `Unknown region key: ${regionKey}. Pass \\`apiEndpoint\\` explicitly to override.`,\n );\n }\n return endpoint;\n}\n\nexport function listKnownRegionKeys(): readonly string[] {\n return Object.keys(REGION_API_ENDPOINTS);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\nimport { withFileLock } from \"./lock.js\";\nimport { stateFilePath, stateLockPath } from \"./paths.js\";\nimport { CfDebuggerError } from \"./types.js\";\nimport type { ActiveSession, SessionKey, StateFile } from \"./types.js\";\n\nasync function readJsonFile<T>(path: string): Promise<T | undefined> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n process.stderr.write(\n `[cf-debugger] warning: state file at ${path} is not valid JSON; resetting to empty.\\n`,\n );\n return undefined;\n }\n}\n\nasync function writeJsonFileAtomic(path: string, value: unknown): Promise<void> {\n const tempPath = `${path}.${randomUUID()}.tmp`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n await rename(tempPath, path);\n}\n\nfunction emptyState(): StateFile {\n return { version: \"1\", sessions: [] };\n}\n\nfunction isValidState(value: unknown): value is StateFile {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<StateFile>;\n return candidate.version === \"1\" && Array.isArray(candidate.sessions);\n}\n\nexport function isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n return false;\n }\n return true;\n }\n}\n\nfunction filterStaleSessions(sessions: readonly ActiveSession[]): readonly ActiveSession[] {\n const host = getHostname();\n return sessions.filter((session) => {\n if (session.hostname !== host) {\n return true;\n }\n return isPidAlive(session.pid);\n });\n}\n\nasync function readStateRaw(): Promise<StateFile> {\n const parsed = await readJsonFile<unknown>(stateFilePath());\n if (!isValidState(parsed)) {\n return emptyState();\n }\n return parsed;\n}\n\nasync function writeState(state: StateFile): Promise<void> {\n await writeJsonFileAtomic(stateFilePath(), state);\n}\n\nexport interface StateReaderResult {\n readonly sessions: readonly ActiveSession[];\n readonly removed: readonly ActiveSession[];\n}\n\nasync function readAndPruneLocked(): Promise<StateReaderResult> {\n const raw = await readStateRaw();\n const pruned = filterStaleSessions(raw.sessions);\n const removed = raw.sessions.filter(\n (session) => !pruned.some((active) => active.sessionId === session.sessionId),\n );\n\n if (removed.length > 0) {\n await writeState({ version: \"1\", sessions: pruned });\n }\n\n return { sessions: pruned, removed };\n}\n\nexport async function readActiveSessions(): Promise<readonly ActiveSession[]> {\n const result = await withFileLock(stateLockPath(), readAndPruneLocked);\n return result.sessions;\n}\n\nexport async function readAndPruneActiveSessions(): Promise<StateReaderResult> {\n return await withFileLock(stateLockPath(), readAndPruneLocked);\n}\n\nexport function sessionKeyString(key: SessionKey): string {\n return `${key.region}:${key.org}:${key.space}:${key.app}`;\n}\n\nexport function matchesKey(session: SessionKey, key: SessionKey): boolean {\n return (\n session.region === key.region &&\n session.org === key.org &&\n session.space === key.space &&\n session.app === key.app\n );\n}\n\nexport interface RegisterSessionResult {\n readonly session: ActiveSession;\n readonly existing?: ActiveSession;\n}\n\nexport interface RegisterSessionInput extends SessionKey {\n readonly apiEndpoint: string;\n readonly preferredPort?: number;\n readonly portProbe: (port: number) => Promise<boolean>;\n readonly sessionIdFactory?: () => string;\n readonly cfHomeForSession: (sessionId: string) => string;\n readonly basePort?: number;\n readonly maxPort?: number;\n}\n\nconst DEFAULT_BASE_PORT = 20_000;\nconst DEFAULT_MAX_PORT = 20_999;\n\nasync function pickPort(\n preferred: number | undefined,\n reserved: ReadonlySet<number>,\n probe: (port: number) => Promise<boolean>,\n basePort: number,\n maxPort: number,\n): Promise<number> {\n const tryOrder: number[] = [];\n if (preferred !== undefined) {\n tryOrder.push(preferred);\n }\n for (let port = basePort; port <= maxPort; port++) {\n if (port !== preferred) {\n tryOrder.push(port);\n }\n }\n\n for (const port of tryOrder) {\n if (reserved.has(port)) {\n continue;\n }\n const free = await probe(port);\n if (free) {\n return port;\n }\n }\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `No free local port available in range ${basePort.toString()}–${maxPort.toString()}`,\n );\n}\n\nexport async function registerNewSession(\n input: RegisterSessionInput,\n): Promise<RegisterSessionResult> {\n return await withFileLock(stateLockPath(), async (): Promise<RegisterSessionResult> => {\n const pruneResult = await readAndPruneLocked();\n const existing = pruneResult.sessions.find((session) => matchesKey(session, input));\n if (existing) {\n return { session: existing, existing };\n }\n\n const reservedPorts = new Set(pruneResult.sessions.map((session) => session.localPort));\n const localPort = await pickPort(\n input.preferredPort,\n reservedPorts,\n input.portProbe,\n input.basePort ?? DEFAULT_BASE_PORT,\n input.maxPort ?? DEFAULT_MAX_PORT,\n );\n\n const sessionId = (input.sessionIdFactory ?? randomUUID)();\n const cfHomeDir = input.cfHomeForSession(sessionId);\n\n const session: ActiveSession = {\n sessionId,\n pid: process.pid,\n hostname: getHostname(),\n region: input.region,\n org: input.org,\n space: input.space,\n app: input.app,\n apiEndpoint: input.apiEndpoint,\n localPort,\n remotePort: 9229,\n cfHomeDir,\n startedAt: new Date().toISOString(),\n status: \"starting\",\n };\n\n const nextSessions: readonly ActiveSession[] = [...pruneResult.sessions, session];\n await writeState({ version: \"1\", sessions: nextSessions });\n\n return { session };\n });\n}\n\nexport async function updateSessionStatus(\n sessionId: string,\n status: ActiveSession[\"status\"],\n message?: string,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const base: ActiveSession = {\n sessionId: session.sessionId,\n pid: session.pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status,\n };\n const next: ActiveSession = message === undefined ? base : { ...base, message };\n updated = next;\n return next;\n });\n\n if (updated) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function removeSession(sessionId: string): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n const target = raw.sessions.find((session) => session.sessionId === sessionId);\n if (!target) {\n return undefined;\n }\n const remaining = raw.sessions.filter((session) => session.sessionId !== sessionId);\n await writeState({ version: \"1\", sessions: remaining });\n return target;\n });\n}\n","import { mkdir, open, unlink } from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst DEFAULT_POLL_MS = 50;\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function acquireFileLock(\n lockPath: string,\n timeoutMs: number,\n pollMs: number,\n): Promise<FileHandle> {\n const deadline = Date.now() + timeoutMs;\n await mkdir(dirname(lockPath), { recursive: true });\n\n for (;;) {\n try {\n return await open(lockPath, \"wx\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n if (Date.now() > deadline) {\n throw new Error(`Timed out acquiring file lock at ${lockPath}`);\n }\n\n await sleep(pollMs);\n }\n}\n\nasync function releaseFileLock(lockPath: string, handle: FileHandle): Promise<void> {\n await handle.close();\n await unlink(lockPath).catch((err: unknown) => {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw err;\n }\n });\n}\n\nexport interface WithLockOptions {\n readonly timeoutMs?: number;\n readonly pollMs?: number;\n}\n\nexport async function withFileLock<T>(\n lockPath: string,\n work: () => Promise<T>,\n options?: WithLockOptions,\n): Promise<T> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n const handle = await acquireFileLock(lockPath, timeoutMs, pollMs);\n try {\n return await work();\n } finally {\n await releaseFileLock(lockPath, handle);\n }\n}\n"],"mappings":"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","names":["process","mkdir","getHostname","process","execFile","promisify","execFileAsync","mkdir","dirname","process","process","mkdir","dirname","session","process","process","getHostname","mkdir","process"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/debugger.ts","../src/cf.ts","../src/types.ts","../src/paths.ts","../src/port.ts","../src/regions.ts","../src/state.ts","../src/lock.ts"],"sourcesContent":["import process from \"node:process\";\n\nimport { Command } from \"commander\";\n\nimport {\n getSession,\n listSessions,\n startDebugger,\n stopAllDebuggers,\n stopDebugger,\n} from \"./debugger.js\";\nimport type { SessionKey, SessionStatus } from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nfunction readRequiredOption(value: string | undefined, flag: string): string {\n if (value === undefined || value === \"\") {\n process.stderr.write(`Missing required option ${flag}\\n`);\n process.exit(1);\n }\n return value;\n}\n\nfunction parseOptionalPort(raw: string | undefined): number | undefined {\n if (raw === undefined) {\n return undefined;\n }\n const port = Number.parseInt(raw, 10);\n if (Number.isNaN(port) || port <= 0 || port > 65_535) {\n process.stderr.write(`Invalid port: ${raw}\\n`);\n process.exit(1);\n }\n return port;\n}\n\nfunction parseOptionalTimeout(raw: string | undefined): number | undefined {\n if (raw === undefined) {\n return undefined;\n }\n const seconds = Number.parseInt(raw, 10);\n if (Number.isNaN(seconds) || seconds <= 0) {\n process.stderr.write(`Invalid timeout: ${raw}\\n`);\n process.exit(1);\n }\n return seconds * 1000;\n}\n\ninterface StartCommandOptions {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n readonly port?: string;\n readonly timeout?: string;\n readonly verbose?: boolean;\n}\n\ninterface StopCommandOptions {\n readonly region?: string;\n readonly org?: string;\n readonly space?: string;\n readonly app?: string;\n readonly sessionId?: string;\n readonly all?: boolean;\n}\n\ninterface StatusCommandOptions {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nfunction logStatus(verbose: boolean, status: SessionStatus, message?: string): void {\n if (verbose) {\n const suffix = message === undefined ? \"\" : `: ${message}`;\n process.stdout.write(`[cf-debugger] ${status}${suffix}\\n`);\n }\n}\n\nasync function handleStart(opts: StartCommandOptions): Promise<void> {\n const region = readRequiredOption(opts.region, \"--region\");\n const org = readRequiredOption(opts.org, \"--org\");\n const space = readRequiredOption(opts.space, \"--space\");\n const app = readRequiredOption(opts.app, \"--app\");\n const verbose = opts.verbose ?? false;\n\n const preferredPort = parseOptionalPort(opts.port);\n const tunnelReadyTimeoutMs = parseOptionalTimeout(opts.timeout);\n\n const abortController = new AbortController();\n const onStartupSignal = (exitCode: number) => (): void => {\n abortController.abort();\n process.stderr.write(`\\nAborting startup for ${app}...\\n`);\n setTimeout(() => {\n process.exit(exitCode);\n }, 5_000).unref();\n };\n const startupSigint = onStartupSignal(130);\n const startupSigterm = onStartupSignal(143);\n process.on(\"SIGINT\", startupSigint);\n process.on(\"SIGTERM\", startupSigterm);\n\n let handle;\n try {\n handle = await startDebugger({\n region,\n org,\n space,\n app,\n verbose,\n signal: abortController.signal,\n ...(preferredPort === undefined ? {} : { preferredPort }),\n ...(tunnelReadyTimeoutMs === undefined ? {} : { tunnelReadyTimeoutMs }),\n onStatus: (status, message) => {\n logStatus(verbose, status, message);\n },\n });\n } finally {\n process.off(\"SIGINT\", startupSigint);\n process.off(\"SIGTERM\", startupSigterm);\n }\n\n process.stdout.write(\n `Debugger ready for ${app} (${region}/${org}/${space}).\\n` +\n ` Local port: ${handle.session.localPort.toString()}\\n` +\n ` Remote port: ${handle.session.remotePort.toString()}\\n` +\n ` Session id: ${handle.session.sessionId}\\n` +\n ` PID: ${handle.session.pid.toString()}\\n` +\n `Press Ctrl+C to stop.\\n`,\n );\n\n let disposePromise: Promise<void> | undefined;\n const dispose = async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n process.stdout.write(`\\nStopping debugger for ${app}...\\n`);\n try {\n await handle.dispose();\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`Error during stop: ${msg}\\n`);\n }\n })();\n await disposePromise;\n };\n\n process.on(\"SIGINT\", () => {\n void dispose().then(() => {\n process.exit(130);\n });\n });\n process.on(\"SIGTERM\", () => {\n void dispose().then(() => {\n process.exit(143);\n });\n });\n\n const code = await handle.waitForExit();\n await dispose();\n process.exit(code ?? 0);\n}\n\nfunction resolveKeyFromOpts(opts: StopCommandOptions): SessionKey | undefined {\n if (\n opts.region !== undefined &&\n opts.org !== undefined &&\n opts.space !== undefined &&\n opts.app !== undefined\n ) {\n return {\n region: opts.region,\n org: opts.org,\n space: opts.space,\n app: opts.app,\n };\n }\n return undefined;\n}\n\nasync function handleStop(opts: StopCommandOptions): Promise<void> {\n if (opts.all === true) {\n const count = await stopAllDebuggers();\n process.stdout.write(`Stopped ${count.toString()} session(s).\\n`);\n return;\n }\n const key = resolveKeyFromOpts(opts);\n const result = await stopDebugger({\n ...(opts.sessionId === undefined ? {} : { sessionId: opts.sessionId }),\n ...(key === undefined ? {} : { key }),\n });\n if (result === undefined) {\n process.stderr.write(\"No matching session found.\\n\");\n process.exit(1);\n }\n process.stdout.write(\n `Stopped session ${result.sessionId} (${result.app}, port ${result.localPort.toString()}).\\n`,\n );\n}\n\nasync function handleList(): Promise<void> {\n const sessions = await listSessions();\n process.stdout.write(`${JSON.stringify(sessions, null, 2)}\\n`);\n}\n\nasync function handleStatus(opts: StatusCommandOptions): Promise<void> {\n const session = await getSession({\n region: opts.region,\n org: opts.org,\n space: opts.space,\n app: opts.app,\n });\n process.stdout.write(`${JSON.stringify(session ?? null, null, 2)}\\n`);\n}\n\nexport async function main(argv: readonly string[]): Promise<void> {\n const program = new Command();\n\n program\n .name(\"cf-debugger\")\n .description(\"Open an SSH debug tunnel to a SAP BTP Cloud Foundry app's Node.js inspector\");\n\n program\n .command(\"start\")\n .description(\"Open a debug tunnel for one app\")\n .requiredOption(\"--region <key>\", \"CF region key (e.g. eu10)\")\n .requiredOption(\"--org <name>\", \"CF org name\")\n .requiredOption(\"--space <name>\", \"CF space name\")\n .requiredOption(\"--app <name>\", \"CF app name\")\n .option(\"--port <number>\", \"Preferred local port (auto-assigned if omitted)\")\n .option(\"--timeout <seconds>\", \"Tunnel-ready timeout in seconds (default: 30)\")\n .option(\"--verbose\", \"Print status transitions\", false)\n .action(async (opts: StartCommandOptions): Promise<void> => {\n await handleStart(opts);\n });\n\n program\n .command(\"stop\")\n .description(\"Stop one session (by key or id) or all sessions with --all\")\n .option(\"--region <key>\")\n .option(\"--org <name>\")\n .option(\"--space <name>\")\n .option(\"--app <name>\")\n .option(\"--session-id <id>\")\n .option(\"--all\", \"Stop every active session\", false)\n .action(async (opts: StopCommandOptions): Promise<void> => {\n await handleStop(opts);\n });\n\n program\n .command(\"list\")\n .description(\"Print every active debugger session as JSON\")\n .action(async (): Promise<void> => {\n await handleList();\n });\n\n program\n .command(\"status\")\n .description(\"Print one session by key as JSON (null if not active)\")\n .requiredOption(\"--region <key>\")\n .requiredOption(\"--org <name>\")\n .requiredOption(\"--space <name>\")\n .requiredOption(\"--app <name>\")\n .action(async (opts: StatusCommandOptions): Promise<void> => {\n await handleStatus(opts);\n });\n\n await program.parseAsync([...argv]);\n}\n\ntry {\n await main(process.argv);\n} catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n if (err.code === \"ABORTED\") {\n process.stderr.write(`Aborted: ${err.message}\\n`);\n process.exit(130);\n }\n process.stderr.write(`Error [${err.code}]: ${err.message}\\n`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`Error: ${msg}\\n`);\n }\n process.exit(1);\n}\n","import type { ChildProcess } from \"node:child_process\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport process from \"node:process\";\n\nimport type { CfExecContext } from \"./cf.js\";\nimport {\n cfEnableSsh,\n cfLogin,\n cfRestartApp,\n cfSshEnabled,\n cfSshOneShot,\n cfTarget,\n isSshDisabledError,\n spawnSshTunnel,\n} from \"./cf.js\";\nimport { sessionCfHomeDir } from \"./paths.js\";\nimport { findListeningProcessId, isPortFree, killProcessOnPort, probeTunnelReady } from \"./port.js\";\nimport { resolveApiEndpoint } from \"./regions.js\";\nimport {\n isPidAlive,\n matchesKey,\n readAndPruneActiveSessions,\n registerNewSession,\n removeSession,\n sessionKeyString,\n updateSessionPid,\n updateSessionStatus,\n} from \"./state.js\";\nimport type {\n ActiveSession,\n DebuggerHandle,\n SessionKey,\n SessionStatus,\n StartDebuggerOptions,\n} from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nconst DEFAULT_TUNNEL_READY_TIMEOUT_MS = 30_000;\nconst POST_USR1_DELAY_MS = 300;\nconst PORT_CLEANUP_DELAY_MS = 600;\nconst CHILD_SIGTERM_GRACE_MS = 2_000;\nconst PORT_RECLAIM_DELAY_MS = 250;\nconst PID_TERMINATION_POLL_MS = 100;\n\nfunction signalPidOrGroup(pid: number, signal: NodeJS.Signals): void {\n const isWindows = process.platform === \"win32\";\n if (!isWindows) {\n try {\n process.kill(-pid, signal);\n return;\n } catch {\n // fall through to direct pid signal\n }\n }\n try {\n process.kill(pid, signal);\n } catch {\n // already gone\n }\n}\n\nasync function terminatePidOrGroup(\n pid: number,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (!isPidAlive(pid)) {\n return;\n }\n\n signalPidOrGroup(pid, \"SIGTERM\");\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if (!isPidAlive(pid)) {\n return;\n }\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PID_TERMINATION_POLL_MS);\n });\n }\n\n signalPidOrGroup(pid, \"SIGKILL\");\n}\n\nasync function killProcessGroupOrProc(\n child: ChildProcess,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (child.pid === undefined) {\n return;\n }\n if (child.exitCode !== null || child.signalCode !== null) {\n return;\n }\n await terminatePidOrGroup(child.pid, timeoutMs);\n}\n\nasync function pruneAndCleanupOrphans(): Promise<readonly ActiveSession[]> {\n const result = await readAndPruneActiveSessions();\n const host = getHostname();\n for (const removed of result.removed) {\n if (removed.hostname === host) {\n void killProcessOnPort(removed.localPort);\n }\n }\n return result.sessions;\n}\n\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CfDebuggerError(\"ABORTED\", \"Operation aborted by caller\");\n }\n}\n\nfunction requireCredentials(options: StartDebuggerOptions): {\n readonly email: string;\n readonly password: string;\n} {\n const email = options.email ?? process.env[\"SAP_EMAIL\"];\n const password = options.password ?? process.env[\"SAP_PASSWORD\"];\n if (email === undefined || email === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP email is required. Pass `email` or set SAP_EMAIL env var.\",\n );\n }\n if (password === undefined || password === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP password is required. Pass `password` or set SAP_PASSWORD env var.\",\n );\n }\n return { email, password };\n}\n\nexport async function startDebugger(options: StartDebuggerOptions): Promise<DebuggerHandle> {\n const { email, password } = requireCredentials(options);\n const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);\n const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;\n const emit = (status: SessionStatus, message?: string): void => {\n options.onStatus?.(status, message);\n };\n\n checkAbort(options.signal);\n\n await pruneAndCleanupOrphans();\n\n const registration = await registerNewSession({\n region: options.region,\n org: options.org,\n space: options.space,\n app: options.app,\n apiEndpoint,\n ...(options.preferredPort === undefined ? {} : { preferredPort: options.preferredPort }),\n portProbe: isPortFree,\n cfHomeForSession: sessionCfHomeDir,\n });\n\n if (registration.existing) {\n throw new CfDebuggerError(\n \"SESSION_ALREADY_RUNNING\",\n `A debugger session is already running for ${sessionKeyString(options)} ` +\n `on port ${registration.existing.localPort.toString()} ` +\n `(pid ${registration.existing.pid.toString()}, sessionId ${registration.existing.sessionId}). ` +\n `Stop it first with \\`cf-debugger stop\\`.`,\n );\n }\n\n const session = registration.session;\n const context: CfExecContext = { cfHome: session.cfHomeDir };\n\n let child: ChildProcess | undefined;\n let tunnelClosed = false;\n let exitResolve: ((code: number | null) => void) | undefined;\n const exitPromise = new Promise<number | null>((resolve) => {\n exitResolve = resolve;\n });\n\n const cleanupFilesystem = async (): Promise<void> => {\n try {\n await rm(session.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n };\n\n const finalize = async (): Promise<void> => {\n if (!tunnelClosed) {\n tunnelClosed = true;\n if (child) {\n await killProcessGroupOrProc(child);\n }\n setTimeout(() => {\n void killProcessOnPort(session.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n }\n await removeSession(session.sessionId);\n await cleanupFilesystem();\n emit(\"stopped\");\n };\n\n try {\n await mkdir(session.cfHomeDir, { recursive: true });\n\n emit(\"logging-in\");\n await updateSessionStatus(session.sessionId, \"logging-in\");\n await cfLogin(apiEndpoint, email, password, context);\n checkAbort(options.signal);\n\n emit(\"targeting\");\n await updateSessionStatus(session.sessionId, \"targeting\");\n await cfTarget(options.org, options.space, context);\n checkAbort(options.signal);\n\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 200);\n });\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const signalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n\n if (isSshDisabledError(signalResult.stderr)) {\n const alreadyEnabled = await cfSshEnabled(options.app, context);\n if (!alreadyEnabled) {\n emit(\"ssh-enabling\", \"Enabling SSH on the app\");\n await updateSessionStatus(session.sessionId, \"ssh-enabling\");\n await cfEnableSsh(options.app, context);\n }\n emit(\"ssh-restarting\", \"Restarting app so SSH becomes active\");\n await updateSessionStatus(session.sessionId, \"ssh-restarting\");\n await cfRestartApp(options.app, context);\n checkAbort(options.signal);\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const retrySignalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n if (retrySignalResult.exitCode !== 0) {\n const detail =\n retrySignalResult.stderr.trim().length > 0\n ? retrySignalResult.stderr.trim()\n : `exit code ${String(retrySignalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,\n retrySignalResult.stderr,\n );\n }\n } else if (signalResult.exitCode !== 0) {\n const detail =\n signalResult.stderr.trim().length > 0\n ? signalResult.stderr.trim()\n : `exit code ${String(signalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,\n signalResult.stderr,\n );\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, POST_USR1_DELAY_MS);\n });\n checkAbort(options.signal);\n\n emit(\"tunneling\");\n await updateSessionStatus(session.sessionId, \"tunneling\");\n\n if (!(await isPortFree(session.localPort))) {\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PORT_RECLAIM_DELAY_MS);\n });\n if (!(await isPortFree(session.localPort))) {\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `Local port ${session.localPort.toString()} is in use and could not be reclaimed for the tunnel.`,\n );\n }\n }\n\n child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);\n if (child.pid !== undefined) {\n await updateSessionPid(session.sessionId, child.pid);\n }\n\n child.on(\"close\", (code) => {\n tunnelClosed = true;\n exitResolve?.(code);\n });\n\n child.on(\"error\", (err: Error) => {\n emit(\"error\", err.message);\n });\n\n const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);\n checkAbort(options.signal);\n if (!ready) {\n throw new CfDebuggerError(\n \"TUNNEL_NOT_READY\",\n `SSH tunnel on port ${session.localPort.toString()} did not become ready within ` +\n `${Math.round(tunnelReadyTimeoutMs / 1000).toString()}s.`,\n );\n }\n\n const listeningPid = await findListeningProcessId(session.localPort);\n const activePid = listeningPid ?? child.pid ?? session.pid;\n if (activePid !== session.pid) {\n await updateSessionPid(session.sessionId, activePid);\n }\n\n emit(\"ready\");\n const readySession = await updateSessionStatus(session.sessionId, \"ready\");\n const activeSession: ActiveSession = readySession ?? { ...session, pid: activePid, status: \"ready\" };\n\n let disposePromise: Promise<void> | undefined;\n const handle: DebuggerHandle = {\n session: activeSession,\n dispose: async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n emit(\"stopping\");\n await updateSessionStatus(session.sessionId, \"stopping\");\n await finalize();\n })();\n await disposePromise;\n },\n waitForExit: async (): Promise<number | null> => {\n return await exitPromise;\n },\n };\n\n return handle;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n emit(\"error\", message);\n await finalize();\n throw err;\n }\n}\n\nexport interface StopOptions {\n readonly sessionId?: string;\n readonly key?: SessionKey;\n}\n\nexport async function stopDebugger(options: StopOptions): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n let target: ActiveSession | undefined;\n if (options.sessionId !== undefined) {\n target = sessions.find((s) => s.sessionId === options.sessionId);\n } else if (options.key !== undefined) {\n const key = options.key;\n target = sessions.find((s) => matchesKey(s, key));\n }\n if (target === undefined) {\n return undefined;\n }\n if (target.pid !== process.pid) {\n try {\n await terminatePidOrGroup(target.pid);\n } catch {\n // process already gone — cleanup below\n }\n }\n setTimeout(() => {\n void killProcessOnPort(target.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n const removed = await removeSession(target.sessionId);\n try {\n await rm(target.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n return removed ?? target;\n}\n\nexport async function stopAllDebuggers(): Promise<number> {\n const sessions = await pruneAndCleanupOrphans();\n let stopped = 0;\n for (const session of sessions) {\n const result = await stopDebugger({ sessionId: session.sessionId });\n if (result) {\n stopped += 1;\n }\n }\n return stopped;\n}\n\nexport async function listSessions(): Promise<readonly ActiveSession[]> {\n return await pruneAndCleanupOrphans();\n}\n\nexport async function getSession(key: SessionKey): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n return sessions.find((s) => matchesKey(s, key));\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { CfDebuggerError } from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst MAX_BUFFER = 16 * 1024 * 1024;\nconst CF_CLI_TIMEOUT_MS = 30_000;\nconst CF_RESTART_TIMEOUT_MS = 120_000;\nconst CF_SSH_SIGNAL_TIMEOUT_MS = 15_000;\nconst CF_AUTH_MAX_ATTEMPTS = 3;\n\nexport interface CfExecContext {\n readonly cfHome: string;\n readonly command?: string;\n}\n\nfunction buildEnv(cfHome: string): NodeJS.ProcessEnv {\n return { ...process.env, CF_HOME: cfHome };\n}\n\nfunction resolveBin(context: CfExecContext): string {\n return context.command ?? process.env[\"CF_DEBUGGER_CF_BIN\"] ?? \"cf\";\n}\n\nasync function runCf(\n args: readonly string[],\n context: CfExecContext,\n timeoutMs: number = CF_CLI_TIMEOUT_MS,\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync(resolveBin(context), [...args], {\n env: buildEnv(context.cfHome),\n maxBuffer: MAX_BUFFER,\n timeout: timeoutMs,\n });\n return stdout;\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException & { stderr?: string; stdout?: string };\n const stderr = e.stderr?.trim() ?? \"\";\n throw new CfDebuggerError(\n \"CF_CLI_FAILED\",\n `cf ${args.join(\" \")} failed: ${stderr.length > 0 ? stderr : e.message}`,\n stderr,\n );\n }\n}\n\nexport async function cfApi(apiEndpoint: string, context: CfExecContext): Promise<void> {\n await runCf([\"api\", apiEndpoint], context);\n}\n\nexport async function cfAuth(\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n let lastError: unknown;\n for (let attempt = 0; attempt < CF_AUTH_MAX_ATTEMPTS; attempt++) {\n try {\n await runCf([\"auth\", email, password], context);\n return;\n } catch (err: unknown) {\n lastError = err;\n if (attempt < CF_AUTH_MAX_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 1000 * (attempt + 1));\n });\n }\n }\n }\n if (lastError instanceof Error) {\n throw lastError;\n }\n throw new CfDebuggerError(\"CF_AUTH_FAILED\", `cf auth failed: ${String(lastError)}`);\n}\n\nexport async function cfLogin(\n apiEndpoint: string,\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await cfApi(apiEndpoint, context);\n await cfAuth(email, password, context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_LOGIN_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfTarget(\n org: string,\n space: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await runCf([\"target\", \"-o\", org, \"-s\", space], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_TARGET_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfAppExists(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n await runCf([\"app\", appName], context);\n return true;\n } catch (err: unknown) {\n const stderr = (err as CfDebuggerError).stderr ?? \"\";\n if (stderr.toLowerCase().includes(\"not found\")) {\n return false;\n }\n throw err;\n }\n}\n\nexport async function cfSshEnabled(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n const stdout = await runCf([\"ssh-enabled\", appName], context);\n return stdout.toLowerCase().includes(\"ssh support is enabled\");\n } catch {\n return false;\n }\n}\n\nexport async function cfEnableSsh(appName: string, context: CfExecContext): Promise<void> {\n try {\n await runCf([\"enable-ssh\", appName], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"SSH_NOT_ENABLED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfRestartApp(appName: string, context: CfExecContext): Promise<void> {\n await runCf([\"restart\", appName], context, CF_RESTART_TIMEOUT_MS);\n}\n\nexport interface CfSshSignalResult {\n readonly exitCode: number | null;\n readonly stderr: string;\n}\n\nexport async function cfSshOneShot(\n appName: string,\n command: string,\n context: CfExecContext,\n): Promise<CfSshSignalResult> {\n return await new Promise<CfSshSignalResult>((resolve) => {\n const child = spawn(resolveBin(context), [\"ssh\", appName, \"-c\", command], {\n env: buildEnv(context.cfHome),\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stderrBuf = \"\";\n let settled = false;\n\n const timeout = setTimeout(() => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n child.kill();\n } catch {\n // already gone\n }\n resolve({ exitCode: null, stderr: stderrBuf });\n }, CF_SSH_SIGNAL_TIMEOUT_MS);\n\n child.stderr.on(\"data\", (data: Buffer | string) => {\n stderrBuf += data.toString();\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: code, stderr: stderrBuf });\n });\n\n child.on(\"error\", (err: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: null, stderr: err.message });\n });\n });\n}\n\nexport function isSshDisabledError(stderr: string): boolean {\n const lower = stderr.toLowerCase();\n return lower.includes(\"not authorized\") || lower.includes(\"ssh support is disabled\");\n}\n\nexport function spawnSshTunnel(\n appName: string,\n localPort: number,\n remotePort: number,\n context: CfExecContext,\n): ReturnType<typeof spawn> {\n const tunnelArg = `${localPort.toString()}:localhost:${remotePort.toString()}`;\n const isWindows = process.platform === \"win32\";\n return spawn(resolveBin(context), [\"ssh\", appName, \"-N\", \"-L\", tunnelArg], {\n env: buildEnv(context.cfHome),\n shell: isWindows,\n detached: !isWindows,\n });\n}\n\nexport function parseAppNames(stdout: string): readonly string[] {\n const apps: string[] = [];\n let pastHeader = false;\n for (const line of stdout.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!pastHeader) {\n if (trimmed.startsWith(\"name\")) {\n pastHeader = true;\n }\n continue;\n }\n if (trimmed.length === 0) {\n continue;\n }\n const first = trimmed.split(/\\s+/)[0];\n if (first !== undefined && first.length > 0) {\n apps.push(first);\n }\n }\n return apps;\n}\n\nexport async function cfApps(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"apps\"], context);\n return parseAppNames(stdout);\n}\n\nexport function parseNameTable(stdout: string): readonly string[] {\n const lines = stdout.split(\"\\n\");\n const headerIdx = lines.findIndex((l) => l.trim() === \"name\");\n if (headerIdx === -1) {\n return [];\n }\n return lines\n .slice(headerIdx + 1)\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function cfOrgs(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"orgs\"], context);\n return parseNameTable(stdout);\n}\n\nexport async function cfSpaces(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"spaces\"], context);\n return parseNameTable(stdout);\n}\n","export interface SessionKey {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nexport type SessionStatus =\n | \"starting\"\n | \"logging-in\"\n | \"targeting\"\n | \"ssh-enabling\"\n | \"ssh-restarting\"\n | \"signaling\"\n | \"tunneling\"\n | \"ready\"\n | \"stopping\"\n | \"stopped\"\n | \"error\";\n\nexport interface ActiveSession extends SessionKey {\n readonly sessionId: string;\n readonly pid: number;\n readonly hostname: string;\n readonly localPort: number;\n readonly remotePort: number;\n readonly apiEndpoint: string;\n readonly cfHomeDir: string;\n readonly startedAt: string;\n readonly status: SessionStatus;\n readonly message?: string;\n}\n\nexport interface StartDebuggerOptions extends SessionKey {\n readonly email?: string;\n readonly password?: string;\n readonly apiEndpoint?: string;\n readonly preferredPort?: number;\n readonly tunnelReadyTimeoutMs?: number;\n readonly verbose?: boolean;\n readonly onStatus?: (status: SessionStatus, message?: string) => void;\n readonly signal?: AbortSignal;\n}\n\nexport interface DebuggerHandle {\n readonly session: ActiveSession;\n dispose(): Promise<void>;\n waitForExit(): Promise<number | null>;\n}\n\nexport interface StateFile {\n readonly version: \"1\";\n readonly sessions: readonly ActiveSession[];\n}\n\nexport class CfDebuggerError extends Error {\n public readonly code: string;\n public readonly stderr?: string;\n\n public constructor(code: string, message: string, stderr?: string) {\n super(message);\n this.name = \"CfDebuggerError\";\n this.code = code;\n if (stderr !== undefined) {\n this.stderr = stderr;\n }\n }\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const SAPTOOLS_DIR_NAME = \".saptools\";\nexport const CF_DEBUGGER_STATE_FILENAME = \"cf-debugger-state.json\";\nexport const CF_DEBUGGER_LOCK_FILENAME = \"cf-debugger-state.lock\";\nexport const CF_DEBUGGER_HOMES_DIRNAME = \"cf-debugger-homes\";\n\nexport function saptoolsDir(): string {\n return join(homedir(), SAPTOOLS_DIR_NAME);\n}\n\nexport function stateFilePath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_STATE_FILENAME);\n}\n\nexport function stateLockPath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_LOCK_FILENAME);\n}\n\nexport function sessionCfHomeDir(sessionId: string): string {\n return join(saptoolsDir(), CF_DEBUGGER_HOMES_DIRNAME, sessionId);\n}\n","import { execFile } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nasync function findListeningPidsWithNetstat(port: number): Promise<readonly number[]> {\n try {\n const { stdout } = await execFileAsync(\"netstat\", [\"-ano\"]);\n const pids = new Set<number>();\n for (const line of stdout.split(\"\\n\")) {\n if (!line.includes(`:${port.toString()}`) || !line.includes(\"LISTENING\")) {\n continue;\n }\n const parts = line.trim().split(/\\s+/);\n const last = parts[parts.length - 1];\n if (last === undefined) {\n continue;\n }\n const pid = Number.parseInt(last, 10);\n if (!Number.isNaN(pid)) {\n pids.add(pid);\n }\n }\n return [...pids];\n } catch {\n return [];\n }\n}\n\nasync function findListeningPidsWithLsof(port: number): Promise<readonly number[]> {\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-nP\", \"-t\", \"-i\", `tcp:${port.toString()}`, \"-sTCP:LISTEN\"]);\n return stdout\n .trim()\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((line) => Number.parseInt(line, 10))\n .filter((pid) => !Number.isNaN(pid));\n } catch {\n return [];\n }\n}\n\nasync function findListeningPids(port: number): Promise<readonly number[]> {\n if (process.platform === \"win32\") {\n return await findListeningPidsWithNetstat(port);\n }\n return await findListeningPidsWithLsof(port);\n}\n\nexport async function isPortFree(port: number): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close(() => {\n resolve(true);\n });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport async function probeTunnelReady(port: number, timeoutMs: number): Promise<boolean> {\n const pollIntervalMs = 250;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const connected = await new Promise<boolean>((resolve) => {\n const socket = createConnection({ port, host: \"127.0.0.1\" });\n socket.setTimeout(200);\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(true);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.once(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n });\n\n if (connected) {\n return true;\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, pollIntervalMs);\n });\n }\n\n return false;\n}\n\nexport async function findListeningProcessId(port: number): Promise<number | undefined> {\n const pids = await findListeningPids(port);\n return pids[0];\n}\n\nexport async function killProcessOnPort(port: number): Promise<void> {\n const portStr = port.toString();\n if (process.platform === \"win32\") {\n try {\n const pids = await findListeningPidsWithNetstat(port);\n for (const pid of pids) {\n try {\n // cspell:ignore taskkill\n await execFileAsync(\"taskkill\", [\"/F\", \"/PID\", pid.toString()]);\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore\n }\n return;\n }\n\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-t\", \"-i\", `tcp:${portStr}`]);\n const lines = stdout.trim().split(\"\\n\").filter((line) => line.length > 0);\n for (const line of lines) {\n const pid = Number.parseInt(line, 10);\n if (Number.isNaN(pid)) {\n continue;\n }\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already dead\n }\n }\n } catch {\n // lsof missing or no match — ignore\n }\n}\n","export interface RegionInfo {\n readonly key: string;\n readonly apiEndpoint: string;\n}\n\nconst REGION_API_ENDPOINTS: Readonly<Record<string, string>> = {\n ae01: \"https://api.cf.ae01.hana.ondemand.com\",\n ap01: \"https://api.cf.ap01.hana.ondemand.com\",\n ap10: \"https://api.cf.ap10.hana.ondemand.com\",\n ap11: \"https://api.cf.ap11.hana.ondemand.com\",\n ap12: \"https://api.cf.ap12.hana.ondemand.com\",\n ap20: \"https://api.cf.ap20.hana.ondemand.com\",\n ap21: \"https://api.cf.ap21.hana.ondemand.com\",\n ap30: \"https://api.cf.ap30.hana.ondemand.com\",\n br10: \"https://api.cf.br10.hana.ondemand.com\",\n br20: \"https://api.cf.br20.hana.ondemand.com\",\n br30: \"https://api.cf.br30.hana.ondemand.com\",\n ca10: \"https://api.cf.ca10.hana.ondemand.com\",\n ca20: \"https://api.cf.ca20.hana.ondemand.com\",\n ch20: \"https://api.cf.ch20.hana.ondemand.com\",\n eu10: \"https://api.cf.eu10.hana.ondemand.com\",\n eu11: \"https://api.cf.eu11.hana.ondemand.com\",\n eu12: \"https://api.cf.eu12.hana.ondemand.com\",\n eu20: \"https://api.cf.eu20.hana.ondemand.com\",\n eu21: \"https://api.cf.eu21.hana.ondemand.com\",\n eu30: \"https://api.cf.eu30.hana.ondemand.com\",\n eu31: \"https://api.cf.eu31.hana.ondemand.com\",\n in30: \"https://api.cf.in30.hana.ondemand.com\",\n jp10: \"https://api.cf.jp10.hana.ondemand.com\",\n jp20: \"https://api.cf.jp20.hana.ondemand.com\",\n jp30: \"https://api.cf.jp30.hana.ondemand.com\",\n kr30: \"https://api.cf.kr30.hana.ondemand.com\",\n us10: \"https://api.cf.us10.hana.ondemand.com\",\n us11: \"https://api.cf.us11.hana.ondemand.com\",\n us20: \"https://api.cf.us20.hana.ondemand.com\",\n us21: \"https://api.cf.us21.hana.ondemand.com\",\n us30: \"https://api.cf.us30.hana.ondemand.com\",\n us31: \"https://api.cf.us31.hana.ondemand.com\",\n};\n\nexport function resolveApiEndpoint(regionKey: string, override?: string): string {\n if (override !== undefined && override !== \"\") {\n return override;\n }\n const endpoint = REGION_API_ENDPOINTS[regionKey];\n if (endpoint === undefined) {\n throw new Error(\n `Unknown region key: ${regionKey}. Pass \\`apiEndpoint\\` explicitly to override.`,\n );\n }\n return endpoint;\n}\n\nexport function listKnownRegionKeys(): readonly string[] {\n return Object.keys(REGION_API_ENDPOINTS);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\nimport { withFileLock } from \"./lock.js\";\nimport { stateFilePath, stateLockPath } from \"./paths.js\";\nimport { CfDebuggerError } from \"./types.js\";\nimport type { ActiveSession, SessionKey, StateFile } from \"./types.js\";\n\nasync function readJsonFile<T>(path: string): Promise<T | undefined> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n process.stderr.write(\n `[cf-debugger] warning: state file at ${path} is not valid JSON; resetting to empty.\\n`,\n );\n return undefined;\n }\n}\n\nasync function writeJsonFileAtomic(path: string, value: unknown): Promise<void> {\n const tempPath = `${path}.${randomUUID()}.tmp`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n await rename(tempPath, path);\n}\n\nfunction emptyState(): StateFile {\n return { version: \"1\", sessions: [] };\n}\n\nfunction isValidState(value: unknown): value is StateFile {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<StateFile>;\n return candidate.version === \"1\" && Array.isArray(candidate.sessions);\n}\n\nexport function isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n return false;\n }\n return true;\n }\n}\n\nfunction filterStaleSessions(sessions: readonly ActiveSession[]): readonly ActiveSession[] {\n const host = getHostname();\n return sessions.filter((session) => {\n if (session.hostname !== host) {\n return true;\n }\n return isPidAlive(session.pid);\n });\n}\n\nasync function readStateRaw(): Promise<StateFile> {\n const parsed = await readJsonFile<unknown>(stateFilePath());\n if (!isValidState(parsed)) {\n return emptyState();\n }\n return parsed;\n}\n\nasync function writeState(state: StateFile): Promise<void> {\n await writeJsonFileAtomic(stateFilePath(), state);\n}\n\nexport interface StateReaderResult {\n readonly sessions: readonly ActiveSession[];\n readonly removed: readonly ActiveSession[];\n}\n\nasync function readAndPruneLocked(): Promise<StateReaderResult> {\n const raw = await readStateRaw();\n const pruned = filterStaleSessions(raw.sessions);\n const removed = raw.sessions.filter(\n (session) => !pruned.some((active) => active.sessionId === session.sessionId),\n );\n\n if (removed.length > 0) {\n await writeState({ version: \"1\", sessions: pruned });\n }\n\n return { sessions: pruned, removed };\n}\n\nexport async function readActiveSessions(): Promise<readonly ActiveSession[]> {\n const result = await withFileLock(stateLockPath(), readAndPruneLocked);\n return result.sessions;\n}\n\nexport async function readAndPruneActiveSessions(): Promise<StateReaderResult> {\n return await withFileLock(stateLockPath(), readAndPruneLocked);\n}\n\nexport function sessionKeyString(key: SessionKey): string {\n return `${key.region}:${key.org}:${key.space}:${key.app}`;\n}\n\nexport function matchesKey(session: SessionKey, key: SessionKey): boolean {\n return (\n session.region === key.region &&\n session.org === key.org &&\n session.space === key.space &&\n session.app === key.app\n );\n}\n\nexport interface RegisterSessionResult {\n readonly session: ActiveSession;\n readonly existing?: ActiveSession;\n}\n\nexport interface RegisterSessionInput extends SessionKey {\n readonly apiEndpoint: string;\n readonly preferredPort?: number;\n readonly portProbe: (port: number) => Promise<boolean>;\n readonly sessionIdFactory?: () => string;\n readonly cfHomeForSession: (sessionId: string) => string;\n readonly basePort?: number;\n readonly maxPort?: number;\n}\n\nconst DEFAULT_BASE_PORT = 20_000;\nconst DEFAULT_MAX_PORT = 20_999;\n\nasync function pickPort(\n preferred: number | undefined,\n reserved: ReadonlySet<number>,\n probe: (port: number) => Promise<boolean>,\n basePort: number,\n maxPort: number,\n): Promise<number> {\n const tryOrder: number[] = [];\n if (preferred !== undefined) {\n tryOrder.push(preferred);\n }\n for (let port = basePort; port <= maxPort; port++) {\n if (port !== preferred) {\n tryOrder.push(port);\n }\n }\n\n for (const port of tryOrder) {\n if (reserved.has(port)) {\n continue;\n }\n const free = await probe(port);\n if (free) {\n return port;\n }\n }\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `No free local port available in range ${basePort.toString()}–${maxPort.toString()}`,\n );\n}\n\nexport async function registerNewSession(\n input: RegisterSessionInput,\n): Promise<RegisterSessionResult> {\n return await withFileLock(stateLockPath(), async (): Promise<RegisterSessionResult> => {\n const pruneResult = await readAndPruneLocked();\n const existing = pruneResult.sessions.find((session) => matchesKey(session, input));\n if (existing) {\n return { session: existing, existing };\n }\n\n const reservedPorts = new Set(pruneResult.sessions.map((session) => session.localPort));\n const localPort = await pickPort(\n input.preferredPort,\n reservedPorts,\n input.portProbe,\n input.basePort ?? DEFAULT_BASE_PORT,\n input.maxPort ?? DEFAULT_MAX_PORT,\n );\n\n const sessionId = (input.sessionIdFactory ?? randomUUID)();\n const cfHomeDir = input.cfHomeForSession(sessionId);\n\n const session: ActiveSession = {\n sessionId,\n pid: process.pid,\n hostname: getHostname(),\n region: input.region,\n org: input.org,\n space: input.space,\n app: input.app,\n apiEndpoint: input.apiEndpoint,\n localPort,\n remotePort: 9229,\n cfHomeDir,\n startedAt: new Date().toISOString(),\n status: \"starting\",\n };\n\n const nextSessions: readonly ActiveSession[] = [...pruneResult.sessions, session];\n await writeState({ version: \"1\", sessions: nextSessions });\n\n return { session };\n });\n}\n\nexport async function updateSessionStatus(\n sessionId: string,\n status: ActiveSession[\"status\"],\n message?: string,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const base: ActiveSession = {\n sessionId: session.sessionId,\n pid: session.pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status,\n };\n const next: ActiveSession = message === undefined ? base : { ...base, message };\n updated = next;\n return next;\n });\n\n if (updated) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function updateSessionPid(\n sessionId: string,\n pid: number,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const next: ActiveSession = {\n sessionId: session.sessionId,\n pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status: session.status,\n ...(session.message === undefined ? {} : { message: session.message }),\n };\n updated = next;\n return next;\n });\n\n if (updated !== undefined) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function removeSession(sessionId: string): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n const target = raw.sessions.find((session) => session.sessionId === sessionId);\n if (!target) {\n return undefined;\n }\n const remaining = raw.sessions.filter((session) => session.sessionId !== sessionId);\n await writeState({ version: \"1\", sessions: remaining });\n return target;\n });\n}\n","import { mkdir, open, unlink } from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst DEFAULT_POLL_MS = 50;\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function acquireFileLock(\n lockPath: string,\n timeoutMs: number,\n pollMs: number,\n): Promise<FileHandle> {\n const deadline = Date.now() + timeoutMs;\n await mkdir(dirname(lockPath), { recursive: true });\n\n for (;;) {\n try {\n return await open(lockPath, \"wx\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n if (Date.now() > deadline) {\n throw new Error(`Timed out acquiring file lock at ${lockPath}`);\n }\n\n await sleep(pollMs);\n }\n}\n\nasync function releaseFileLock(lockPath: string, handle: FileHandle): Promise<void> {\n await handle.close();\n await unlink(lockPath).catch((err: unknown) => {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw err;\n }\n });\n}\n\nexport interface WithLockOptions {\n readonly timeoutMs?: number;\n readonly pollMs?: number;\n}\n\nexport async function withFileLock<T>(\n lockPath: string,\n work: () => Promise<T>,\n options?: WithLockOptions,\n): Promise<T> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n const handle = await acquireFileLock(lockPath, timeoutMs, pollMs);\n try {\n return await work();\n } finally {\n await releaseFileLock(lockPath, handle);\n }\n}\n"],"mappings":"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","names":["process","mkdir","getHostname","process","execFile","promisify","execFileAsync","mkdir","dirname","process","process","mkdir","dirname","session","process","process","getHostname","mkdir","process"]}
package/dist/index.js CHANGED
@@ -196,6 +196,43 @@ import { execFile as execFile2 } from "child_process";
196
196
  import { createConnection, createServer } from "net";
197
197
  import { promisify as promisify2 } from "util";
198
198
  var execFileAsync2 = promisify2(execFile2);
199
+ async function findListeningPidsWithNetstat(port) {
200
+ try {
201
+ const { stdout } = await execFileAsync2("netstat", ["-ano"]);
202
+ const pids = /* @__PURE__ */ new Set();
203
+ for (const line of stdout.split("\n")) {
204
+ if (!line.includes(`:${port.toString()}`) || !line.includes("LISTENING")) {
205
+ continue;
206
+ }
207
+ const parts = line.trim().split(/\s+/);
208
+ const last = parts[parts.length - 1];
209
+ if (last === void 0) {
210
+ continue;
211
+ }
212
+ const pid = Number.parseInt(last, 10);
213
+ if (!Number.isNaN(pid)) {
214
+ pids.add(pid);
215
+ }
216
+ }
217
+ return [...pids];
218
+ } catch {
219
+ return [];
220
+ }
221
+ }
222
+ async function findListeningPidsWithLsof(port) {
223
+ try {
224
+ const { stdout } = await execFileAsync2("lsof", ["-nP", "-t", "-i", `tcp:${port.toString()}`, "-sTCP:LISTEN"]);
225
+ return stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => Number.parseInt(line, 10)).filter((pid) => !Number.isNaN(pid));
226
+ } catch {
227
+ return [];
228
+ }
229
+ }
230
+ async function findListeningPids(port) {
231
+ if (process.platform === "win32") {
232
+ return await findListeningPidsWithNetstat(port);
233
+ }
234
+ return await findListeningPidsWithLsof(port);
235
+ }
199
236
  async function isPortFree(port) {
200
237
  return await new Promise((resolve) => {
201
238
  const server = createServer();
@@ -239,24 +276,15 @@ async function probeTunnelReady(port, timeoutMs) {
239
276
  }
240
277
  return false;
241
278
  }
279
+ async function findListeningProcessId(port) {
280
+ const pids = await findListeningPids(port);
281
+ return pids[0];
282
+ }
242
283
  async function killProcessOnPort(port) {
243
284
  const portStr = port.toString();
244
285
  if (process.platform === "win32") {
245
286
  try {
246
- const { stdout } = await execFileAsync2("netstat", ["-ano"]);
247
- const pids = /* @__PURE__ */ new Set();
248
- for (const line of stdout.split("\n")) {
249
- if (line.includes(`:${portStr}`) && line.includes("LISTENING")) {
250
- const parts = line.trim().split(/\s+/);
251
- const last = parts[parts.length - 1];
252
- if (last !== void 0) {
253
- const pid = Number.parseInt(last, 10);
254
- if (!Number.isNaN(pid)) {
255
- pids.add(pid);
256
- }
257
- }
258
- }
259
- }
287
+ const pids = await findListeningPidsWithNetstat(port);
260
288
  for (const pid of pids) {
261
289
  try {
262
290
  await execFileAsync2("taskkill", ["/F", "/PID", pid.toString()]);
@@ -269,14 +297,15 @@ async function killProcessOnPort(port) {
269
297
  }
270
298
  try {
271
299
  const { stdout } = await execFileAsync2("lsof", ["-t", "-i", `tcp:${portStr}`]);
272
- const lines = stdout.trim().split("\n").filter((l) => l.length > 0);
273
- for (const pidStr of lines) {
274
- const pid = Number.parseInt(pidStr, 10);
275
- if (!Number.isNaN(pid)) {
276
- try {
277
- process.kill(pid, "SIGKILL");
278
- } catch {
279
- }
300
+ const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
301
+ for (const line of lines) {
302
+ const pid = Number.parseInt(line, 10);
303
+ if (Number.isNaN(pid)) {
304
+ continue;
305
+ }
306
+ try {
307
+ process.kill(pid, "SIGKILL");
308
+ } catch {
280
309
  }
281
310
  }
282
311
  } catch {
@@ -575,6 +604,39 @@ async function updateSessionStatus(sessionId, status, message) {
575
604
  return updated;
576
605
  });
577
606
  }
607
+ async function updateSessionPid(sessionId, pid) {
608
+ return await withFileLock(stateLockPath(), async () => {
609
+ const raw = await readStateRaw();
610
+ let updated;
611
+ const nextSessions = raw.sessions.map((session) => {
612
+ if (session.sessionId !== sessionId) {
613
+ return session;
614
+ }
615
+ const next = {
616
+ sessionId: session.sessionId,
617
+ pid,
618
+ hostname: session.hostname,
619
+ region: session.region,
620
+ org: session.org,
621
+ space: session.space,
622
+ app: session.app,
623
+ apiEndpoint: session.apiEndpoint,
624
+ localPort: session.localPort,
625
+ remotePort: session.remotePort,
626
+ cfHomeDir: session.cfHomeDir,
627
+ startedAt: session.startedAt,
628
+ status: session.status,
629
+ ...session.message === void 0 ? {} : { message: session.message }
630
+ };
631
+ updated = next;
632
+ return next;
633
+ });
634
+ if (updated !== void 0) {
635
+ await writeState({ version: "1", sessions: nextSessions });
636
+ }
637
+ return updated;
638
+ });
639
+ }
578
640
  async function removeSession(sessionId) {
579
641
  return await withFileLock(stateLockPath(), async () => {
580
642
  const raw = await readStateRaw();
@@ -594,41 +656,45 @@ var POST_USR1_DELAY_MS = 300;
594
656
  var PORT_CLEANUP_DELAY_MS = 600;
595
657
  var CHILD_SIGTERM_GRACE_MS = 2e3;
596
658
  var PORT_RECLAIM_DELAY_MS = 250;
597
- async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
598
- if (child.pid === void 0) {
599
- return;
600
- }
601
- if (child.exitCode !== null || child.signalCode !== null) {
602
- return;
603
- }
659
+ var PID_TERMINATION_POLL_MS = 100;
660
+ function signalPidOrGroup(pid, signal) {
604
661
  const isWindows = process3.platform === "win32";
605
- const send = (sig) => {
662
+ if (!isWindows) {
606
663
  try {
607
- if (!isWindows && child.pid !== void 0) {
608
- process3.kill(-child.pid, sig);
609
- } else {
610
- child.kill(sig);
611
- }
664
+ process3.kill(-pid, signal);
665
+ return;
612
666
  } catch {
613
667
  }
614
- };
615
- send("SIGTERM");
616
- const closed = await new Promise((resolve) => {
617
- if (child.exitCode !== null || child.signalCode !== null) {
618
- resolve(true);
668
+ }
669
+ try {
670
+ process3.kill(pid, signal);
671
+ } catch {
672
+ }
673
+ }
674
+ async function terminatePidOrGroup(pid, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
675
+ if (!isPidAlive(pid)) {
676
+ return;
677
+ }
678
+ signalPidOrGroup(pid, "SIGTERM");
679
+ const startedAt = Date.now();
680
+ while (Date.now() - startedAt < timeoutMs) {
681
+ if (!isPidAlive(pid)) {
619
682
  return;
620
683
  }
621
- const t = setTimeout(() => {
622
- resolve(false);
623
- }, timeoutMs);
624
- child.once("close", () => {
625
- clearTimeout(t);
626
- resolve(true);
684
+ await new Promise((resolve) => {
685
+ setTimeout(resolve, PID_TERMINATION_POLL_MS);
627
686
  });
628
- });
629
- if (!closed) {
630
- send("SIGKILL");
631
687
  }
688
+ signalPidOrGroup(pid, "SIGKILL");
689
+ }
690
+ async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS) {
691
+ if (child.pid === void 0) {
692
+ return;
693
+ }
694
+ if (child.exitCode !== null || child.signalCode !== null) {
695
+ return;
696
+ }
697
+ await terminatePidOrGroup(child.pid, timeoutMs);
632
698
  }
633
699
  async function pruneAndCleanupOrphans() {
634
700
  const result = await readAndPruneActiveSessions();
@@ -789,6 +855,9 @@ async function startDebugger(options) {
789
855
  }
790
856
  }
791
857
  child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);
858
+ if (child.pid !== void 0) {
859
+ await updateSessionPid(session.sessionId, child.pid);
860
+ }
792
861
  child.on("close", (code) => {
793
862
  tunnelClosed = true;
794
863
  exitResolve?.(code);
@@ -804,9 +873,14 @@ async function startDebugger(options) {
804
873
  `SSH tunnel on port ${session.localPort.toString()} did not become ready within ${Math.round(tunnelReadyTimeoutMs / 1e3).toString()}s.`
805
874
  );
806
875
  }
876
+ const listeningPid = await findListeningProcessId(session.localPort);
877
+ const activePid = listeningPid ?? child.pid ?? session.pid;
878
+ if (activePid !== session.pid) {
879
+ await updateSessionPid(session.sessionId, activePid);
880
+ }
807
881
  emit("ready");
808
882
  const readySession = await updateSessionStatus(session.sessionId, "ready");
809
- const activeSession = readySession ?? { ...session, status: "ready" };
883
+ const activeSession = readySession ?? { ...session, pid: activePid, status: "ready" };
810
884
  let disposePromise;
811
885
  const handle = {
812
886
  session: activeSession,
@@ -844,7 +918,7 @@ async function stopDebugger(options) {
844
918
  }
845
919
  if (target.pid !== process3.pid) {
846
920
  try {
847
- process3.kill(target.pid, "SIGTERM");
921
+ await terminatePidOrGroup(target.pid);
848
922
  } catch {
849
923
  }
850
924
  }
@@ -856,7 +930,7 @@ async function stopDebugger(options) {
856
930
  await rm(target.cfHomeDir, { recursive: true, force: true });
857
931
  } catch {
858
932
  }
859
- return removed;
933
+ return removed ?? target;
860
934
  }
861
935
  async function stopAllDebuggers() {
862
936
  const sessions = await pruneAndCleanupOrphans();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/debugger.ts","../src/cf.ts","../src/paths.ts","../src/port.ts","../src/regions.ts","../src/state.ts","../src/lock.ts"],"sourcesContent":["export interface SessionKey {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nexport type SessionStatus =\n | \"starting\"\n | \"logging-in\"\n | \"targeting\"\n | \"ssh-enabling\"\n | \"ssh-restarting\"\n | \"signaling\"\n | \"tunneling\"\n | \"ready\"\n | \"stopping\"\n | \"stopped\"\n | \"error\";\n\nexport interface ActiveSession extends SessionKey {\n readonly sessionId: string;\n readonly pid: number;\n readonly hostname: string;\n readonly localPort: number;\n readonly remotePort: number;\n readonly apiEndpoint: string;\n readonly cfHomeDir: string;\n readonly startedAt: string;\n readonly status: SessionStatus;\n readonly message?: string;\n}\n\nexport interface StartDebuggerOptions extends SessionKey {\n readonly email?: string;\n readonly password?: string;\n readonly apiEndpoint?: string;\n readonly preferredPort?: number;\n readonly tunnelReadyTimeoutMs?: number;\n readonly verbose?: boolean;\n readonly onStatus?: (status: SessionStatus, message?: string) => void;\n readonly signal?: AbortSignal;\n}\n\nexport interface DebuggerHandle {\n readonly session: ActiveSession;\n dispose(): Promise<void>;\n waitForExit(): Promise<number | null>;\n}\n\nexport interface StateFile {\n readonly version: \"1\";\n readonly sessions: readonly ActiveSession[];\n}\n\nexport class CfDebuggerError extends Error {\n public readonly code: string;\n public readonly stderr?: string;\n\n public constructor(code: string, message: string, stderr?: string) {\n super(message);\n this.name = \"CfDebuggerError\";\n this.code = code;\n if (stderr !== undefined) {\n this.stderr = stderr;\n }\n }\n}\n","import type { ChildProcess } from \"node:child_process\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport process from \"node:process\";\n\nimport type { CfExecContext } from \"./cf.js\";\nimport {\n cfEnableSsh,\n cfLogin,\n cfRestartApp,\n cfSshEnabled,\n cfSshOneShot,\n cfTarget,\n isSshDisabledError,\n spawnSshTunnel,\n} from \"./cf.js\";\nimport { sessionCfHomeDir } from \"./paths.js\";\nimport { isPortFree, killProcessOnPort, probeTunnelReady } from \"./port.js\";\nimport { resolveApiEndpoint } from \"./regions.js\";\nimport {\n matchesKey,\n readAndPruneActiveSessions,\n registerNewSession,\n removeSession,\n sessionKeyString,\n updateSessionStatus,\n} from \"./state.js\";\nimport type {\n ActiveSession,\n DebuggerHandle,\n SessionKey,\n SessionStatus,\n StartDebuggerOptions,\n} from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nconst DEFAULT_TUNNEL_READY_TIMEOUT_MS = 30_000;\nconst POST_USR1_DELAY_MS = 300;\nconst PORT_CLEANUP_DELAY_MS = 600;\nconst CHILD_SIGTERM_GRACE_MS = 2_000;\nconst PORT_RECLAIM_DELAY_MS = 250;\n\nasync function killProcessGroupOrProc(\n child: ChildProcess,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (child.pid === undefined) {\n return;\n }\n if (child.exitCode !== null || child.signalCode !== null) {\n return;\n }\n const isWindows = process.platform === \"win32\";\n const send = (sig: NodeJS.Signals): void => {\n try {\n if (!isWindows && child.pid !== undefined) {\n process.kill(-child.pid, sig);\n } else {\n child.kill(sig);\n }\n } catch {\n // already gone\n }\n };\n send(\"SIGTERM\");\n const closed = await new Promise<boolean>((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve(true);\n return;\n }\n const t = setTimeout(() => {\n resolve(false);\n }, timeoutMs);\n child.once(\"close\", () => {\n clearTimeout(t);\n resolve(true);\n });\n });\n if (!closed) {\n send(\"SIGKILL\");\n }\n}\n\nasync function pruneAndCleanupOrphans(): Promise<readonly ActiveSession[]> {\n const result = await readAndPruneActiveSessions();\n const host = getHostname();\n for (const removed of result.removed) {\n if (removed.hostname === host) {\n void killProcessOnPort(removed.localPort);\n }\n }\n return result.sessions;\n}\n\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CfDebuggerError(\"ABORTED\", \"Operation aborted by caller\");\n }\n}\n\nfunction requireCredentials(options: StartDebuggerOptions): {\n readonly email: string;\n readonly password: string;\n} {\n const email = options.email ?? process.env[\"SAP_EMAIL\"];\n const password = options.password ?? process.env[\"SAP_PASSWORD\"];\n if (email === undefined || email === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP email is required. Pass `email` or set SAP_EMAIL env var.\",\n );\n }\n if (password === undefined || password === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP password is required. Pass `password` or set SAP_PASSWORD env var.\",\n );\n }\n return { email, password };\n}\n\nexport async function startDebugger(options: StartDebuggerOptions): Promise<DebuggerHandle> {\n const { email, password } = requireCredentials(options);\n const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);\n const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;\n const emit = (status: SessionStatus, message?: string): void => {\n options.onStatus?.(status, message);\n };\n\n checkAbort(options.signal);\n\n await pruneAndCleanupOrphans();\n\n const registration = await registerNewSession({\n region: options.region,\n org: options.org,\n space: options.space,\n app: options.app,\n apiEndpoint,\n ...(options.preferredPort === undefined ? {} : { preferredPort: options.preferredPort }),\n portProbe: isPortFree,\n cfHomeForSession: sessionCfHomeDir,\n });\n\n if (registration.existing) {\n throw new CfDebuggerError(\n \"SESSION_ALREADY_RUNNING\",\n `A debugger session is already running for ${sessionKeyString(options)} ` +\n `on port ${registration.existing.localPort.toString()} ` +\n `(pid ${registration.existing.pid.toString()}, sessionId ${registration.existing.sessionId}). ` +\n `Stop it first with \\`cf-debugger stop\\`.`,\n );\n }\n\n const session = registration.session;\n const context: CfExecContext = { cfHome: session.cfHomeDir };\n\n let child: ChildProcess | undefined;\n let tunnelClosed = false;\n let exitResolve: ((code: number | null) => void) | undefined;\n const exitPromise = new Promise<number | null>((resolve) => {\n exitResolve = resolve;\n });\n\n const cleanupFilesystem = async (): Promise<void> => {\n try {\n await rm(session.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n };\n\n const finalize = async (): Promise<void> => {\n if (!tunnelClosed) {\n tunnelClosed = true;\n if (child) {\n await killProcessGroupOrProc(child);\n }\n setTimeout(() => {\n void killProcessOnPort(session.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n }\n await removeSession(session.sessionId);\n await cleanupFilesystem();\n emit(\"stopped\");\n };\n\n try {\n await mkdir(session.cfHomeDir, { recursive: true });\n\n emit(\"logging-in\");\n await updateSessionStatus(session.sessionId, \"logging-in\");\n await cfLogin(apiEndpoint, email, password, context);\n checkAbort(options.signal);\n\n emit(\"targeting\");\n await updateSessionStatus(session.sessionId, \"targeting\");\n await cfTarget(options.org, options.space, context);\n checkAbort(options.signal);\n\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 200);\n });\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const signalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n\n if (isSshDisabledError(signalResult.stderr)) {\n const alreadyEnabled = await cfSshEnabled(options.app, context);\n if (!alreadyEnabled) {\n emit(\"ssh-enabling\", \"Enabling SSH on the app\");\n await updateSessionStatus(session.sessionId, \"ssh-enabling\");\n await cfEnableSsh(options.app, context);\n }\n emit(\"ssh-restarting\", \"Restarting app so SSH becomes active\");\n await updateSessionStatus(session.sessionId, \"ssh-restarting\");\n await cfRestartApp(options.app, context);\n checkAbort(options.signal);\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const retrySignalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n if (retrySignalResult.exitCode !== 0) {\n const detail =\n retrySignalResult.stderr.trim().length > 0\n ? retrySignalResult.stderr.trim()\n : `exit code ${String(retrySignalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,\n retrySignalResult.stderr,\n );\n }\n } else if (signalResult.exitCode !== 0) {\n const detail =\n signalResult.stderr.trim().length > 0\n ? signalResult.stderr.trim()\n : `exit code ${String(signalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,\n signalResult.stderr,\n );\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, POST_USR1_DELAY_MS);\n });\n checkAbort(options.signal);\n\n emit(\"tunneling\");\n await updateSessionStatus(session.sessionId, \"tunneling\");\n\n if (!(await isPortFree(session.localPort))) {\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PORT_RECLAIM_DELAY_MS);\n });\n if (!(await isPortFree(session.localPort))) {\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `Local port ${session.localPort.toString()} is in use and could not be reclaimed for the tunnel.`,\n );\n }\n }\n\n child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);\n\n child.on(\"close\", (code) => {\n tunnelClosed = true;\n exitResolve?.(code);\n });\n\n child.on(\"error\", (err: Error) => {\n emit(\"error\", err.message);\n });\n\n const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);\n checkAbort(options.signal);\n if (!ready) {\n throw new CfDebuggerError(\n \"TUNNEL_NOT_READY\",\n `SSH tunnel on port ${session.localPort.toString()} did not become ready within ` +\n `${Math.round(tunnelReadyTimeoutMs / 1000).toString()}s.`,\n );\n }\n\n emit(\"ready\");\n const readySession = await updateSessionStatus(session.sessionId, \"ready\");\n const activeSession: ActiveSession = readySession ?? { ...session, status: \"ready\" };\n\n let disposePromise: Promise<void> | undefined;\n const handle: DebuggerHandle = {\n session: activeSession,\n dispose: async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n emit(\"stopping\");\n await updateSessionStatus(session.sessionId, \"stopping\");\n await finalize();\n })();\n await disposePromise;\n },\n waitForExit: async (): Promise<number | null> => {\n return await exitPromise;\n },\n };\n\n return handle;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n emit(\"error\", message);\n await finalize();\n throw err;\n }\n}\n\nexport interface StopOptions {\n readonly sessionId?: string;\n readonly key?: SessionKey;\n}\n\nexport async function stopDebugger(options: StopOptions): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n let target: ActiveSession | undefined;\n if (options.sessionId !== undefined) {\n target = sessions.find((s) => s.sessionId === options.sessionId);\n } else if (options.key !== undefined) {\n const key = options.key;\n target = sessions.find((s) => matchesKey(s, key));\n }\n if (target === undefined) {\n return undefined;\n }\n if (target.pid !== process.pid) {\n try {\n process.kill(target.pid, \"SIGTERM\");\n } catch {\n // process already gone — cleanup below\n }\n }\n setTimeout(() => {\n void killProcessOnPort(target.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n const removed = await removeSession(target.sessionId);\n try {\n await rm(target.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n return removed;\n}\n\nexport async function stopAllDebuggers(): Promise<number> {\n const sessions = await pruneAndCleanupOrphans();\n let stopped = 0;\n for (const session of sessions) {\n const result = await stopDebugger({ sessionId: session.sessionId });\n if (result) {\n stopped += 1;\n }\n }\n return stopped;\n}\n\nexport async function listSessions(): Promise<readonly ActiveSession[]> {\n return await pruneAndCleanupOrphans();\n}\n\nexport async function getSession(key: SessionKey): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n return sessions.find((s) => matchesKey(s, key));\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { CfDebuggerError } from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst MAX_BUFFER = 16 * 1024 * 1024;\nconst CF_CLI_TIMEOUT_MS = 30_000;\nconst CF_RESTART_TIMEOUT_MS = 120_000;\nconst CF_SSH_SIGNAL_TIMEOUT_MS = 15_000;\nconst CF_AUTH_MAX_ATTEMPTS = 3;\n\nexport interface CfExecContext {\n readonly cfHome: string;\n readonly command?: string;\n}\n\nfunction buildEnv(cfHome: string): NodeJS.ProcessEnv {\n return { ...process.env, CF_HOME: cfHome };\n}\n\nfunction resolveBin(context: CfExecContext): string {\n return context.command ?? process.env[\"CF_DEBUGGER_CF_BIN\"] ?? \"cf\";\n}\n\nasync function runCf(\n args: readonly string[],\n context: CfExecContext,\n timeoutMs: number = CF_CLI_TIMEOUT_MS,\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync(resolveBin(context), [...args], {\n env: buildEnv(context.cfHome),\n maxBuffer: MAX_BUFFER,\n timeout: timeoutMs,\n });\n return stdout;\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException & { stderr?: string; stdout?: string };\n const stderr = e.stderr?.trim() ?? \"\";\n throw new CfDebuggerError(\n \"CF_CLI_FAILED\",\n `cf ${args.join(\" \")} failed: ${stderr.length > 0 ? stderr : e.message}`,\n stderr,\n );\n }\n}\n\nexport async function cfApi(apiEndpoint: string, context: CfExecContext): Promise<void> {\n await runCf([\"api\", apiEndpoint], context);\n}\n\nexport async function cfAuth(\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n let lastError: unknown;\n for (let attempt = 0; attempt < CF_AUTH_MAX_ATTEMPTS; attempt++) {\n try {\n await runCf([\"auth\", email, password], context);\n return;\n } catch (err: unknown) {\n lastError = err;\n if (attempt < CF_AUTH_MAX_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 1000 * (attempt + 1));\n });\n }\n }\n }\n if (lastError instanceof Error) {\n throw lastError;\n }\n throw new CfDebuggerError(\"CF_AUTH_FAILED\", `cf auth failed: ${String(lastError)}`);\n}\n\nexport async function cfLogin(\n apiEndpoint: string,\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await cfApi(apiEndpoint, context);\n await cfAuth(email, password, context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_LOGIN_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfTarget(\n org: string,\n space: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await runCf([\"target\", \"-o\", org, \"-s\", space], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_TARGET_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfAppExists(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n await runCf([\"app\", appName], context);\n return true;\n } catch (err: unknown) {\n const stderr = (err as CfDebuggerError).stderr ?? \"\";\n if (stderr.toLowerCase().includes(\"not found\")) {\n return false;\n }\n throw err;\n }\n}\n\nexport async function cfSshEnabled(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n const stdout = await runCf([\"ssh-enabled\", appName], context);\n return stdout.toLowerCase().includes(\"ssh support is enabled\");\n } catch {\n return false;\n }\n}\n\nexport async function cfEnableSsh(appName: string, context: CfExecContext): Promise<void> {\n try {\n await runCf([\"enable-ssh\", appName], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"SSH_NOT_ENABLED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfRestartApp(appName: string, context: CfExecContext): Promise<void> {\n await runCf([\"restart\", appName], context, CF_RESTART_TIMEOUT_MS);\n}\n\nexport interface CfSshSignalResult {\n readonly exitCode: number | null;\n readonly stderr: string;\n}\n\nexport async function cfSshOneShot(\n appName: string,\n command: string,\n context: CfExecContext,\n): Promise<CfSshSignalResult> {\n return await new Promise<CfSshSignalResult>((resolve) => {\n const child = spawn(resolveBin(context), [\"ssh\", appName, \"-c\", command], {\n env: buildEnv(context.cfHome),\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stderrBuf = \"\";\n let settled = false;\n\n const timeout = setTimeout(() => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n child.kill();\n } catch {\n // already gone\n }\n resolve({ exitCode: null, stderr: stderrBuf });\n }, CF_SSH_SIGNAL_TIMEOUT_MS);\n\n child.stderr.on(\"data\", (data: Buffer | string) => {\n stderrBuf += data.toString();\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: code, stderr: stderrBuf });\n });\n\n child.on(\"error\", (err: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: null, stderr: err.message });\n });\n });\n}\n\nexport function isSshDisabledError(stderr: string): boolean {\n const lower = stderr.toLowerCase();\n return lower.includes(\"not authorized\") || lower.includes(\"ssh support is disabled\");\n}\n\nexport function spawnSshTunnel(\n appName: string,\n localPort: number,\n remotePort: number,\n context: CfExecContext,\n): ReturnType<typeof spawn> {\n const tunnelArg = `${localPort.toString()}:localhost:${remotePort.toString()}`;\n const isWindows = process.platform === \"win32\";\n return spawn(resolveBin(context), [\"ssh\", appName, \"-N\", \"-L\", tunnelArg], {\n env: buildEnv(context.cfHome),\n shell: isWindows,\n detached: !isWindows,\n });\n}\n\nexport function parseAppNames(stdout: string): readonly string[] {\n const apps: string[] = [];\n let pastHeader = false;\n for (const line of stdout.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!pastHeader) {\n if (trimmed.startsWith(\"name\")) {\n pastHeader = true;\n }\n continue;\n }\n if (trimmed.length === 0) {\n continue;\n }\n const first = trimmed.split(/\\s+/)[0];\n if (first !== undefined && first.length > 0) {\n apps.push(first);\n }\n }\n return apps;\n}\n\nexport async function cfApps(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"apps\"], context);\n return parseAppNames(stdout);\n}\n\nexport function parseNameTable(stdout: string): readonly string[] {\n const lines = stdout.split(\"\\n\");\n const headerIdx = lines.findIndex((l) => l.trim() === \"name\");\n if (headerIdx === -1) {\n return [];\n }\n return lines\n .slice(headerIdx + 1)\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function cfOrgs(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"orgs\"], context);\n return parseNameTable(stdout);\n}\n\nexport async function cfSpaces(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"spaces\"], context);\n return parseNameTable(stdout);\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const SAPTOOLS_DIR_NAME = \".saptools\";\nexport const CF_DEBUGGER_STATE_FILENAME = \"cf-debugger-state.json\";\nexport const CF_DEBUGGER_LOCK_FILENAME = \"cf-debugger-state.lock\";\nexport const CF_DEBUGGER_HOMES_DIRNAME = \"cf-debugger-homes\";\n\nexport function saptoolsDir(): string {\n return join(homedir(), SAPTOOLS_DIR_NAME);\n}\n\nexport function stateFilePath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_STATE_FILENAME);\n}\n\nexport function stateLockPath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_LOCK_FILENAME);\n}\n\nexport function sessionCfHomeDir(sessionId: string): string {\n return join(saptoolsDir(), CF_DEBUGGER_HOMES_DIRNAME, sessionId);\n}\n","import { execFile } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function isPortFree(port: number): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close(() => {\n resolve(true);\n });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport async function probeTunnelReady(port: number, timeoutMs: number): Promise<boolean> {\n const pollIntervalMs = 250;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const connected = await new Promise<boolean>((resolve) => {\n const socket = createConnection({ port, host: \"127.0.0.1\" });\n socket.setTimeout(200);\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(true);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.once(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n });\n\n if (connected) {\n return true;\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, pollIntervalMs);\n });\n }\n\n return false;\n}\n\nexport async function killProcessOnPort(port: number): Promise<void> {\n const portStr = port.toString();\n if (process.platform === \"win32\") {\n try {\n const { stdout } = await execFileAsync(\"netstat\", [\"-ano\"]);\n const pids = new Set<number>();\n for (const line of stdout.split(\"\\n\")) {\n if (line.includes(`:${portStr}`) && line.includes(\"LISTENING\")) {\n const parts = line.trim().split(/\\s+/);\n const last = parts[parts.length - 1];\n if (last !== undefined) {\n const pid = Number.parseInt(last, 10);\n if (!Number.isNaN(pid)) {\n pids.add(pid);\n }\n }\n }\n }\n for (const pid of pids) {\n try {\n // cspell:ignore taskkill\n await execFileAsync(\"taskkill\", [\"/F\", \"/PID\", pid.toString()]);\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore\n }\n return;\n }\n\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-t\", \"-i\", `tcp:${portStr}`]);\n const lines = stdout\n .trim()\n .split(\"\\n\")\n .filter((l) => l.length > 0);\n for (const pidStr of lines) {\n const pid = Number.parseInt(pidStr, 10);\n if (!Number.isNaN(pid)) {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already dead\n }\n }\n }\n } catch {\n // lsof missing or no match — ignore\n }\n}\n","export interface RegionInfo {\n readonly key: string;\n readonly apiEndpoint: string;\n}\n\nconst REGION_API_ENDPOINTS: Readonly<Record<string, string>> = {\n ae01: \"https://api.cf.ae01.hana.ondemand.com\",\n ap01: \"https://api.cf.ap01.hana.ondemand.com\",\n ap10: \"https://api.cf.ap10.hana.ondemand.com\",\n ap11: \"https://api.cf.ap11.hana.ondemand.com\",\n ap12: \"https://api.cf.ap12.hana.ondemand.com\",\n ap20: \"https://api.cf.ap20.hana.ondemand.com\",\n ap21: \"https://api.cf.ap21.hana.ondemand.com\",\n ap30: \"https://api.cf.ap30.hana.ondemand.com\",\n br10: \"https://api.cf.br10.hana.ondemand.com\",\n br20: \"https://api.cf.br20.hana.ondemand.com\",\n br30: \"https://api.cf.br30.hana.ondemand.com\",\n ca10: \"https://api.cf.ca10.hana.ondemand.com\",\n ca20: \"https://api.cf.ca20.hana.ondemand.com\",\n ch20: \"https://api.cf.ch20.hana.ondemand.com\",\n eu10: \"https://api.cf.eu10.hana.ondemand.com\",\n eu11: \"https://api.cf.eu11.hana.ondemand.com\",\n eu12: \"https://api.cf.eu12.hana.ondemand.com\",\n eu20: \"https://api.cf.eu20.hana.ondemand.com\",\n eu21: \"https://api.cf.eu21.hana.ondemand.com\",\n eu30: \"https://api.cf.eu30.hana.ondemand.com\",\n eu31: \"https://api.cf.eu31.hana.ondemand.com\",\n in30: \"https://api.cf.in30.hana.ondemand.com\",\n jp10: \"https://api.cf.jp10.hana.ondemand.com\",\n jp20: \"https://api.cf.jp20.hana.ondemand.com\",\n jp30: \"https://api.cf.jp30.hana.ondemand.com\",\n kr30: \"https://api.cf.kr30.hana.ondemand.com\",\n us10: \"https://api.cf.us10.hana.ondemand.com\",\n us11: \"https://api.cf.us11.hana.ondemand.com\",\n us20: \"https://api.cf.us20.hana.ondemand.com\",\n us21: \"https://api.cf.us21.hana.ondemand.com\",\n us30: \"https://api.cf.us30.hana.ondemand.com\",\n us31: \"https://api.cf.us31.hana.ondemand.com\",\n};\n\nexport function resolveApiEndpoint(regionKey: string, override?: string): string {\n if (override !== undefined && override !== \"\") {\n return override;\n }\n const endpoint = REGION_API_ENDPOINTS[regionKey];\n if (endpoint === undefined) {\n throw new Error(\n `Unknown region key: ${regionKey}. Pass \\`apiEndpoint\\` explicitly to override.`,\n );\n }\n return endpoint;\n}\n\nexport function listKnownRegionKeys(): readonly string[] {\n return Object.keys(REGION_API_ENDPOINTS);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\nimport { withFileLock } from \"./lock.js\";\nimport { stateFilePath, stateLockPath } from \"./paths.js\";\nimport { CfDebuggerError } from \"./types.js\";\nimport type { ActiveSession, SessionKey, StateFile } from \"./types.js\";\n\nasync function readJsonFile<T>(path: string): Promise<T | undefined> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n process.stderr.write(\n `[cf-debugger] warning: state file at ${path} is not valid JSON; resetting to empty.\\n`,\n );\n return undefined;\n }\n}\n\nasync function writeJsonFileAtomic(path: string, value: unknown): Promise<void> {\n const tempPath = `${path}.${randomUUID()}.tmp`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n await rename(tempPath, path);\n}\n\nfunction emptyState(): StateFile {\n return { version: \"1\", sessions: [] };\n}\n\nfunction isValidState(value: unknown): value is StateFile {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<StateFile>;\n return candidate.version === \"1\" && Array.isArray(candidate.sessions);\n}\n\nexport function isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n return false;\n }\n return true;\n }\n}\n\nfunction filterStaleSessions(sessions: readonly ActiveSession[]): readonly ActiveSession[] {\n const host = getHostname();\n return sessions.filter((session) => {\n if (session.hostname !== host) {\n return true;\n }\n return isPidAlive(session.pid);\n });\n}\n\nasync function readStateRaw(): Promise<StateFile> {\n const parsed = await readJsonFile<unknown>(stateFilePath());\n if (!isValidState(parsed)) {\n return emptyState();\n }\n return parsed;\n}\n\nasync function writeState(state: StateFile): Promise<void> {\n await writeJsonFileAtomic(stateFilePath(), state);\n}\n\nexport interface StateReaderResult {\n readonly sessions: readonly ActiveSession[];\n readonly removed: readonly ActiveSession[];\n}\n\nasync function readAndPruneLocked(): Promise<StateReaderResult> {\n const raw = await readStateRaw();\n const pruned = filterStaleSessions(raw.sessions);\n const removed = raw.sessions.filter(\n (session) => !pruned.some((active) => active.sessionId === session.sessionId),\n );\n\n if (removed.length > 0) {\n await writeState({ version: \"1\", sessions: pruned });\n }\n\n return { sessions: pruned, removed };\n}\n\nexport async function readActiveSessions(): Promise<readonly ActiveSession[]> {\n const result = await withFileLock(stateLockPath(), readAndPruneLocked);\n return result.sessions;\n}\n\nexport async function readAndPruneActiveSessions(): Promise<StateReaderResult> {\n return await withFileLock(stateLockPath(), readAndPruneLocked);\n}\n\nexport function sessionKeyString(key: SessionKey): string {\n return `${key.region}:${key.org}:${key.space}:${key.app}`;\n}\n\nexport function matchesKey(session: SessionKey, key: SessionKey): boolean {\n return (\n session.region === key.region &&\n session.org === key.org &&\n session.space === key.space &&\n session.app === key.app\n );\n}\n\nexport interface RegisterSessionResult {\n readonly session: ActiveSession;\n readonly existing?: ActiveSession;\n}\n\nexport interface RegisterSessionInput extends SessionKey {\n readonly apiEndpoint: string;\n readonly preferredPort?: number;\n readonly portProbe: (port: number) => Promise<boolean>;\n readonly sessionIdFactory?: () => string;\n readonly cfHomeForSession: (sessionId: string) => string;\n readonly basePort?: number;\n readonly maxPort?: number;\n}\n\nconst DEFAULT_BASE_PORT = 20_000;\nconst DEFAULT_MAX_PORT = 20_999;\n\nasync function pickPort(\n preferred: number | undefined,\n reserved: ReadonlySet<number>,\n probe: (port: number) => Promise<boolean>,\n basePort: number,\n maxPort: number,\n): Promise<number> {\n const tryOrder: number[] = [];\n if (preferred !== undefined) {\n tryOrder.push(preferred);\n }\n for (let port = basePort; port <= maxPort; port++) {\n if (port !== preferred) {\n tryOrder.push(port);\n }\n }\n\n for (const port of tryOrder) {\n if (reserved.has(port)) {\n continue;\n }\n const free = await probe(port);\n if (free) {\n return port;\n }\n }\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `No free local port available in range ${basePort.toString()}–${maxPort.toString()}`,\n );\n}\n\nexport async function registerNewSession(\n input: RegisterSessionInput,\n): Promise<RegisterSessionResult> {\n return await withFileLock(stateLockPath(), async (): Promise<RegisterSessionResult> => {\n const pruneResult = await readAndPruneLocked();\n const existing = pruneResult.sessions.find((session) => matchesKey(session, input));\n if (existing) {\n return { session: existing, existing };\n }\n\n const reservedPorts = new Set(pruneResult.sessions.map((session) => session.localPort));\n const localPort = await pickPort(\n input.preferredPort,\n reservedPorts,\n input.portProbe,\n input.basePort ?? DEFAULT_BASE_PORT,\n input.maxPort ?? DEFAULT_MAX_PORT,\n );\n\n const sessionId = (input.sessionIdFactory ?? randomUUID)();\n const cfHomeDir = input.cfHomeForSession(sessionId);\n\n const session: ActiveSession = {\n sessionId,\n pid: process.pid,\n hostname: getHostname(),\n region: input.region,\n org: input.org,\n space: input.space,\n app: input.app,\n apiEndpoint: input.apiEndpoint,\n localPort,\n remotePort: 9229,\n cfHomeDir,\n startedAt: new Date().toISOString(),\n status: \"starting\",\n };\n\n const nextSessions: readonly ActiveSession[] = [...pruneResult.sessions, session];\n await writeState({ version: \"1\", sessions: nextSessions });\n\n return { session };\n });\n}\n\nexport async function updateSessionStatus(\n sessionId: string,\n status: ActiveSession[\"status\"],\n message?: string,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const base: ActiveSession = {\n sessionId: session.sessionId,\n pid: session.pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status,\n };\n const next: ActiveSession = message === undefined ? base : { ...base, message };\n updated = next;\n return next;\n });\n\n if (updated) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function removeSession(sessionId: string): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n const target = raw.sessions.find((session) => session.sessionId === sessionId);\n if (!target) {\n return undefined;\n }\n const remaining = raw.sessions.filter((session) => session.sessionId !== sessionId);\n await writeState({ version: \"1\", sessions: remaining });\n return target;\n });\n}\n","import { mkdir, open, unlink } from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst DEFAULT_POLL_MS = 50;\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function acquireFileLock(\n lockPath: string,\n timeoutMs: number,\n pollMs: number,\n): Promise<FileHandle> {\n const deadline = Date.now() + timeoutMs;\n await mkdir(dirname(lockPath), { recursive: true });\n\n for (;;) {\n try {\n return await open(lockPath, \"wx\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n if (Date.now() > deadline) {\n throw new Error(`Timed out acquiring file lock at ${lockPath}`);\n }\n\n await sleep(pollMs);\n }\n}\n\nasync function releaseFileLock(lockPath: string, handle: FileHandle): Promise<void> {\n await handle.close();\n await unlink(lockPath).catch((err: unknown) => {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw err;\n }\n });\n}\n\nexport interface WithLockOptions {\n readonly timeoutMs?: number;\n readonly pollMs?: number;\n}\n\nexport async function withFileLock<T>(\n lockPath: string,\n work: () => Promise<T>,\n options?: WithLockOptions,\n): Promise<T> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n const handle = await acquireFileLock(lockPath, timeoutMs, pollMs);\n try {\n return await work();\n } finally {\n await releaseFileLock(lockPath, handle);\n }\n}\n"],"mappings":"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","names":["mkdir","getHostname","process","execFile","promisify","execFileAsync","mkdir","dirname","process","process","mkdir","dirname","session","process","process","getHostname","mkdir"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/debugger.ts","../src/cf.ts","../src/paths.ts","../src/port.ts","../src/regions.ts","../src/state.ts","../src/lock.ts"],"sourcesContent":["export interface SessionKey {\n readonly region: string;\n readonly org: string;\n readonly space: string;\n readonly app: string;\n}\n\nexport type SessionStatus =\n | \"starting\"\n | \"logging-in\"\n | \"targeting\"\n | \"ssh-enabling\"\n | \"ssh-restarting\"\n | \"signaling\"\n | \"tunneling\"\n | \"ready\"\n | \"stopping\"\n | \"stopped\"\n | \"error\";\n\nexport interface ActiveSession extends SessionKey {\n readonly sessionId: string;\n readonly pid: number;\n readonly hostname: string;\n readonly localPort: number;\n readonly remotePort: number;\n readonly apiEndpoint: string;\n readonly cfHomeDir: string;\n readonly startedAt: string;\n readonly status: SessionStatus;\n readonly message?: string;\n}\n\nexport interface StartDebuggerOptions extends SessionKey {\n readonly email?: string;\n readonly password?: string;\n readonly apiEndpoint?: string;\n readonly preferredPort?: number;\n readonly tunnelReadyTimeoutMs?: number;\n readonly verbose?: boolean;\n readonly onStatus?: (status: SessionStatus, message?: string) => void;\n readonly signal?: AbortSignal;\n}\n\nexport interface DebuggerHandle {\n readonly session: ActiveSession;\n dispose(): Promise<void>;\n waitForExit(): Promise<number | null>;\n}\n\nexport interface StateFile {\n readonly version: \"1\";\n readonly sessions: readonly ActiveSession[];\n}\n\nexport class CfDebuggerError extends Error {\n public readonly code: string;\n public readonly stderr?: string;\n\n public constructor(code: string, message: string, stderr?: string) {\n super(message);\n this.name = \"CfDebuggerError\";\n this.code = code;\n if (stderr !== undefined) {\n this.stderr = stderr;\n }\n }\n}\n","import type { ChildProcess } from \"node:child_process\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport process from \"node:process\";\n\nimport type { CfExecContext } from \"./cf.js\";\nimport {\n cfEnableSsh,\n cfLogin,\n cfRestartApp,\n cfSshEnabled,\n cfSshOneShot,\n cfTarget,\n isSshDisabledError,\n spawnSshTunnel,\n} from \"./cf.js\";\nimport { sessionCfHomeDir } from \"./paths.js\";\nimport { findListeningProcessId, isPortFree, killProcessOnPort, probeTunnelReady } from \"./port.js\";\nimport { resolveApiEndpoint } from \"./regions.js\";\nimport {\n isPidAlive,\n matchesKey,\n readAndPruneActiveSessions,\n registerNewSession,\n removeSession,\n sessionKeyString,\n updateSessionPid,\n updateSessionStatus,\n} from \"./state.js\";\nimport type {\n ActiveSession,\n DebuggerHandle,\n SessionKey,\n SessionStatus,\n StartDebuggerOptions,\n} from \"./types.js\";\nimport { CfDebuggerError } from \"./types.js\";\n\nconst DEFAULT_TUNNEL_READY_TIMEOUT_MS = 30_000;\nconst POST_USR1_DELAY_MS = 300;\nconst PORT_CLEANUP_DELAY_MS = 600;\nconst CHILD_SIGTERM_GRACE_MS = 2_000;\nconst PORT_RECLAIM_DELAY_MS = 250;\nconst PID_TERMINATION_POLL_MS = 100;\n\nfunction signalPidOrGroup(pid: number, signal: NodeJS.Signals): void {\n const isWindows = process.platform === \"win32\";\n if (!isWindows) {\n try {\n process.kill(-pid, signal);\n return;\n } catch {\n // fall through to direct pid signal\n }\n }\n try {\n process.kill(pid, signal);\n } catch {\n // already gone\n }\n}\n\nasync function terminatePidOrGroup(\n pid: number,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (!isPidAlive(pid)) {\n return;\n }\n\n signalPidOrGroup(pid, \"SIGTERM\");\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if (!isPidAlive(pid)) {\n return;\n }\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PID_TERMINATION_POLL_MS);\n });\n }\n\n signalPidOrGroup(pid, \"SIGKILL\");\n}\n\nasync function killProcessGroupOrProc(\n child: ChildProcess,\n timeoutMs: number = CHILD_SIGTERM_GRACE_MS,\n): Promise<void> {\n if (child.pid === undefined) {\n return;\n }\n if (child.exitCode !== null || child.signalCode !== null) {\n return;\n }\n await terminatePidOrGroup(child.pid, timeoutMs);\n}\n\nasync function pruneAndCleanupOrphans(): Promise<readonly ActiveSession[]> {\n const result = await readAndPruneActiveSessions();\n const host = getHostname();\n for (const removed of result.removed) {\n if (removed.hostname === host) {\n void killProcessOnPort(removed.localPort);\n }\n }\n return result.sessions;\n}\n\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CfDebuggerError(\"ABORTED\", \"Operation aborted by caller\");\n }\n}\n\nfunction requireCredentials(options: StartDebuggerOptions): {\n readonly email: string;\n readonly password: string;\n} {\n const email = options.email ?? process.env[\"SAP_EMAIL\"];\n const password = options.password ?? process.env[\"SAP_PASSWORD\"];\n if (email === undefined || email === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP email is required. Pass `email` or set SAP_EMAIL env var.\",\n );\n }\n if (password === undefined || password === \"\") {\n throw new CfDebuggerError(\n \"MISSING_CREDENTIALS\",\n \"SAP password is required. Pass `password` or set SAP_PASSWORD env var.\",\n );\n }\n return { email, password };\n}\n\nexport async function startDebugger(options: StartDebuggerOptions): Promise<DebuggerHandle> {\n const { email, password } = requireCredentials(options);\n const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);\n const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;\n const emit = (status: SessionStatus, message?: string): void => {\n options.onStatus?.(status, message);\n };\n\n checkAbort(options.signal);\n\n await pruneAndCleanupOrphans();\n\n const registration = await registerNewSession({\n region: options.region,\n org: options.org,\n space: options.space,\n app: options.app,\n apiEndpoint,\n ...(options.preferredPort === undefined ? {} : { preferredPort: options.preferredPort }),\n portProbe: isPortFree,\n cfHomeForSession: sessionCfHomeDir,\n });\n\n if (registration.existing) {\n throw new CfDebuggerError(\n \"SESSION_ALREADY_RUNNING\",\n `A debugger session is already running for ${sessionKeyString(options)} ` +\n `on port ${registration.existing.localPort.toString()} ` +\n `(pid ${registration.existing.pid.toString()}, sessionId ${registration.existing.sessionId}). ` +\n `Stop it first with \\`cf-debugger stop\\`.`,\n );\n }\n\n const session = registration.session;\n const context: CfExecContext = { cfHome: session.cfHomeDir };\n\n let child: ChildProcess | undefined;\n let tunnelClosed = false;\n let exitResolve: ((code: number | null) => void) | undefined;\n const exitPromise = new Promise<number | null>((resolve) => {\n exitResolve = resolve;\n });\n\n const cleanupFilesystem = async (): Promise<void> => {\n try {\n await rm(session.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n };\n\n const finalize = async (): Promise<void> => {\n if (!tunnelClosed) {\n tunnelClosed = true;\n if (child) {\n await killProcessGroupOrProc(child);\n }\n setTimeout(() => {\n void killProcessOnPort(session.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n }\n await removeSession(session.sessionId);\n await cleanupFilesystem();\n emit(\"stopped\");\n };\n\n try {\n await mkdir(session.cfHomeDir, { recursive: true });\n\n emit(\"logging-in\");\n await updateSessionStatus(session.sessionId, \"logging-in\");\n await cfLogin(apiEndpoint, email, password, context);\n checkAbort(options.signal);\n\n emit(\"targeting\");\n await updateSessionStatus(session.sessionId, \"targeting\");\n await cfTarget(options.org, options.space, context);\n checkAbort(options.signal);\n\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 200);\n });\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const signalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n\n if (isSshDisabledError(signalResult.stderr)) {\n const alreadyEnabled = await cfSshEnabled(options.app, context);\n if (!alreadyEnabled) {\n emit(\"ssh-enabling\", \"Enabling SSH on the app\");\n await updateSessionStatus(session.sessionId, \"ssh-enabling\");\n await cfEnableSsh(options.app, context);\n }\n emit(\"ssh-restarting\", \"Restarting app so SSH becomes active\");\n await updateSessionStatus(session.sessionId, \"ssh-restarting\");\n await cfRestartApp(options.app, context);\n checkAbort(options.signal);\n\n emit(\"signaling\");\n await updateSessionStatus(session.sessionId, \"signaling\");\n const retrySignalResult = await cfSshOneShot(\n options.app,\n `kill -s USR1 $(pidof node)`,\n context,\n );\n if (retrySignalResult.exitCode !== 0) {\n const detail =\n retrySignalResult.stderr.trim().length > 0\n ? retrySignalResult.stderr.trim()\n : `exit code ${String(retrySignalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,\n retrySignalResult.stderr,\n );\n }\n } else if (signalResult.exitCode !== 0) {\n const detail =\n signalResult.stderr.trim().length > 0\n ? signalResult.stderr.trim()\n : `exit code ${String(signalResult.exitCode)}`;\n throw new CfDebuggerError(\n \"USR1_SIGNAL_FAILED\",\n `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,\n signalResult.stderr,\n );\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, POST_USR1_DELAY_MS);\n });\n checkAbort(options.signal);\n\n emit(\"tunneling\");\n await updateSessionStatus(session.sessionId, \"tunneling\");\n\n if (!(await isPortFree(session.localPort))) {\n await killProcessOnPort(session.localPort);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PORT_RECLAIM_DELAY_MS);\n });\n if (!(await isPortFree(session.localPort))) {\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `Local port ${session.localPort.toString()} is in use and could not be reclaimed for the tunnel.`,\n );\n }\n }\n\n child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);\n if (child.pid !== undefined) {\n await updateSessionPid(session.sessionId, child.pid);\n }\n\n child.on(\"close\", (code) => {\n tunnelClosed = true;\n exitResolve?.(code);\n });\n\n child.on(\"error\", (err: Error) => {\n emit(\"error\", err.message);\n });\n\n const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);\n checkAbort(options.signal);\n if (!ready) {\n throw new CfDebuggerError(\n \"TUNNEL_NOT_READY\",\n `SSH tunnel on port ${session.localPort.toString()} did not become ready within ` +\n `${Math.round(tunnelReadyTimeoutMs / 1000).toString()}s.`,\n );\n }\n\n const listeningPid = await findListeningProcessId(session.localPort);\n const activePid = listeningPid ?? child.pid ?? session.pid;\n if (activePid !== session.pid) {\n await updateSessionPid(session.sessionId, activePid);\n }\n\n emit(\"ready\");\n const readySession = await updateSessionStatus(session.sessionId, \"ready\");\n const activeSession: ActiveSession = readySession ?? { ...session, pid: activePid, status: \"ready\" };\n\n let disposePromise: Promise<void> | undefined;\n const handle: DebuggerHandle = {\n session: activeSession,\n dispose: async (): Promise<void> => {\n disposePromise ??= (async (): Promise<void> => {\n emit(\"stopping\");\n await updateSessionStatus(session.sessionId, \"stopping\");\n await finalize();\n })();\n await disposePromise;\n },\n waitForExit: async (): Promise<number | null> => {\n return await exitPromise;\n },\n };\n\n return handle;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n emit(\"error\", message);\n await finalize();\n throw err;\n }\n}\n\nexport interface StopOptions {\n readonly sessionId?: string;\n readonly key?: SessionKey;\n}\n\nexport async function stopDebugger(options: StopOptions): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n let target: ActiveSession | undefined;\n if (options.sessionId !== undefined) {\n target = sessions.find((s) => s.sessionId === options.sessionId);\n } else if (options.key !== undefined) {\n const key = options.key;\n target = sessions.find((s) => matchesKey(s, key));\n }\n if (target === undefined) {\n return undefined;\n }\n if (target.pid !== process.pid) {\n try {\n await terminatePidOrGroup(target.pid);\n } catch {\n // process already gone — cleanup below\n }\n }\n setTimeout(() => {\n void killProcessOnPort(target.localPort);\n }, PORT_CLEANUP_DELAY_MS);\n const removed = await removeSession(target.sessionId);\n try {\n await rm(target.cfHomeDir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n return removed ?? target;\n}\n\nexport async function stopAllDebuggers(): Promise<number> {\n const sessions = await pruneAndCleanupOrphans();\n let stopped = 0;\n for (const session of sessions) {\n const result = await stopDebugger({ sessionId: session.sessionId });\n if (result) {\n stopped += 1;\n }\n }\n return stopped;\n}\n\nexport async function listSessions(): Promise<readonly ActiveSession[]> {\n return await pruneAndCleanupOrphans();\n}\n\nexport async function getSession(key: SessionKey): Promise<ActiveSession | undefined> {\n const sessions = await pruneAndCleanupOrphans();\n return sessions.find((s) => matchesKey(s, key));\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { CfDebuggerError } from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst MAX_BUFFER = 16 * 1024 * 1024;\nconst CF_CLI_TIMEOUT_MS = 30_000;\nconst CF_RESTART_TIMEOUT_MS = 120_000;\nconst CF_SSH_SIGNAL_TIMEOUT_MS = 15_000;\nconst CF_AUTH_MAX_ATTEMPTS = 3;\n\nexport interface CfExecContext {\n readonly cfHome: string;\n readonly command?: string;\n}\n\nfunction buildEnv(cfHome: string): NodeJS.ProcessEnv {\n return { ...process.env, CF_HOME: cfHome };\n}\n\nfunction resolveBin(context: CfExecContext): string {\n return context.command ?? process.env[\"CF_DEBUGGER_CF_BIN\"] ?? \"cf\";\n}\n\nasync function runCf(\n args: readonly string[],\n context: CfExecContext,\n timeoutMs: number = CF_CLI_TIMEOUT_MS,\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync(resolveBin(context), [...args], {\n env: buildEnv(context.cfHome),\n maxBuffer: MAX_BUFFER,\n timeout: timeoutMs,\n });\n return stdout;\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException & { stderr?: string; stdout?: string };\n const stderr = e.stderr?.trim() ?? \"\";\n throw new CfDebuggerError(\n \"CF_CLI_FAILED\",\n `cf ${args.join(\" \")} failed: ${stderr.length > 0 ? stderr : e.message}`,\n stderr,\n );\n }\n}\n\nexport async function cfApi(apiEndpoint: string, context: CfExecContext): Promise<void> {\n await runCf([\"api\", apiEndpoint], context);\n}\n\nexport async function cfAuth(\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n let lastError: unknown;\n for (let attempt = 0; attempt < CF_AUTH_MAX_ATTEMPTS; attempt++) {\n try {\n await runCf([\"auth\", email, password], context);\n return;\n } catch (err: unknown) {\n lastError = err;\n if (attempt < CF_AUTH_MAX_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 1000 * (attempt + 1));\n });\n }\n }\n }\n if (lastError instanceof Error) {\n throw lastError;\n }\n throw new CfDebuggerError(\"CF_AUTH_FAILED\", `cf auth failed: ${String(lastError)}`);\n}\n\nexport async function cfLogin(\n apiEndpoint: string,\n email: string,\n password: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await cfApi(apiEndpoint, context);\n await cfAuth(email, password, context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_LOGIN_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfTarget(\n org: string,\n space: string,\n context: CfExecContext,\n): Promise<void> {\n try {\n await runCf([\"target\", \"-o\", org, \"-s\", space], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"CF_TARGET_FAILED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfAppExists(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n await runCf([\"app\", appName], context);\n return true;\n } catch (err: unknown) {\n const stderr = (err as CfDebuggerError).stderr ?? \"\";\n if (stderr.toLowerCase().includes(\"not found\")) {\n return false;\n }\n throw err;\n }\n}\n\nexport async function cfSshEnabled(appName: string, context: CfExecContext): Promise<boolean> {\n try {\n const stdout = await runCf([\"ssh-enabled\", appName], context);\n return stdout.toLowerCase().includes(\"ssh support is enabled\");\n } catch {\n return false;\n }\n}\n\nexport async function cfEnableSsh(appName: string, context: CfExecContext): Promise<void> {\n try {\n await runCf([\"enable-ssh\", appName], context);\n } catch (err: unknown) {\n if (err instanceof CfDebuggerError) {\n throw new CfDebuggerError(\"SSH_NOT_ENABLED\", err.message, err.stderr);\n }\n throw err;\n }\n}\n\nexport async function cfRestartApp(appName: string, context: CfExecContext): Promise<void> {\n await runCf([\"restart\", appName], context, CF_RESTART_TIMEOUT_MS);\n}\n\nexport interface CfSshSignalResult {\n readonly exitCode: number | null;\n readonly stderr: string;\n}\n\nexport async function cfSshOneShot(\n appName: string,\n command: string,\n context: CfExecContext,\n): Promise<CfSshSignalResult> {\n return await new Promise<CfSshSignalResult>((resolve) => {\n const child = spawn(resolveBin(context), [\"ssh\", appName, \"-c\", command], {\n env: buildEnv(context.cfHome),\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stderrBuf = \"\";\n let settled = false;\n\n const timeout = setTimeout(() => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n child.kill();\n } catch {\n // already gone\n }\n resolve({ exitCode: null, stderr: stderrBuf });\n }, CF_SSH_SIGNAL_TIMEOUT_MS);\n\n child.stderr.on(\"data\", (data: Buffer | string) => {\n stderrBuf += data.toString();\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: code, stderr: stderrBuf });\n });\n\n child.on(\"error\", (err: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n resolve({ exitCode: null, stderr: err.message });\n });\n });\n}\n\nexport function isSshDisabledError(stderr: string): boolean {\n const lower = stderr.toLowerCase();\n return lower.includes(\"not authorized\") || lower.includes(\"ssh support is disabled\");\n}\n\nexport function spawnSshTunnel(\n appName: string,\n localPort: number,\n remotePort: number,\n context: CfExecContext,\n): ReturnType<typeof spawn> {\n const tunnelArg = `${localPort.toString()}:localhost:${remotePort.toString()}`;\n const isWindows = process.platform === \"win32\";\n return spawn(resolveBin(context), [\"ssh\", appName, \"-N\", \"-L\", tunnelArg], {\n env: buildEnv(context.cfHome),\n shell: isWindows,\n detached: !isWindows,\n });\n}\n\nexport function parseAppNames(stdout: string): readonly string[] {\n const apps: string[] = [];\n let pastHeader = false;\n for (const line of stdout.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!pastHeader) {\n if (trimmed.startsWith(\"name\")) {\n pastHeader = true;\n }\n continue;\n }\n if (trimmed.length === 0) {\n continue;\n }\n const first = trimmed.split(/\\s+/)[0];\n if (first !== undefined && first.length > 0) {\n apps.push(first);\n }\n }\n return apps;\n}\n\nexport async function cfApps(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"apps\"], context);\n return parseAppNames(stdout);\n}\n\nexport function parseNameTable(stdout: string): readonly string[] {\n const lines = stdout.split(\"\\n\");\n const headerIdx = lines.findIndex((l) => l.trim() === \"name\");\n if (headerIdx === -1) {\n return [];\n }\n return lines\n .slice(headerIdx + 1)\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function cfOrgs(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"orgs\"], context);\n return parseNameTable(stdout);\n}\n\nexport async function cfSpaces(context: CfExecContext): Promise<readonly string[]> {\n const stdout = await runCf([\"spaces\"], context);\n return parseNameTable(stdout);\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const SAPTOOLS_DIR_NAME = \".saptools\";\nexport const CF_DEBUGGER_STATE_FILENAME = \"cf-debugger-state.json\";\nexport const CF_DEBUGGER_LOCK_FILENAME = \"cf-debugger-state.lock\";\nexport const CF_DEBUGGER_HOMES_DIRNAME = \"cf-debugger-homes\";\n\nexport function saptoolsDir(): string {\n return join(homedir(), SAPTOOLS_DIR_NAME);\n}\n\nexport function stateFilePath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_STATE_FILENAME);\n}\n\nexport function stateLockPath(): string {\n return join(saptoolsDir(), CF_DEBUGGER_LOCK_FILENAME);\n}\n\nexport function sessionCfHomeDir(sessionId: string): string {\n return join(saptoolsDir(), CF_DEBUGGER_HOMES_DIRNAME, sessionId);\n}\n","import { execFile } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nasync function findListeningPidsWithNetstat(port: number): Promise<readonly number[]> {\n try {\n const { stdout } = await execFileAsync(\"netstat\", [\"-ano\"]);\n const pids = new Set<number>();\n for (const line of stdout.split(\"\\n\")) {\n if (!line.includes(`:${port.toString()}`) || !line.includes(\"LISTENING\")) {\n continue;\n }\n const parts = line.trim().split(/\\s+/);\n const last = parts[parts.length - 1];\n if (last === undefined) {\n continue;\n }\n const pid = Number.parseInt(last, 10);\n if (!Number.isNaN(pid)) {\n pids.add(pid);\n }\n }\n return [...pids];\n } catch {\n return [];\n }\n}\n\nasync function findListeningPidsWithLsof(port: number): Promise<readonly number[]> {\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-nP\", \"-t\", \"-i\", `tcp:${port.toString()}`, \"-sTCP:LISTEN\"]);\n return stdout\n .trim()\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((line) => Number.parseInt(line, 10))\n .filter((pid) => !Number.isNaN(pid));\n } catch {\n return [];\n }\n}\n\nasync function findListeningPids(port: number): Promise<readonly number[]> {\n if (process.platform === \"win32\") {\n return await findListeningPidsWithNetstat(port);\n }\n return await findListeningPidsWithLsof(port);\n}\n\nexport async function isPortFree(port: number): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close(() => {\n resolve(true);\n });\n });\n server.listen(port, \"127.0.0.1\");\n });\n}\n\nexport async function probeTunnelReady(port: number, timeoutMs: number): Promise<boolean> {\n const pollIntervalMs = 250;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const connected = await new Promise<boolean>((resolve) => {\n const socket = createConnection({ port, host: \"127.0.0.1\" });\n socket.setTimeout(200);\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(true);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.once(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n });\n\n if (connected) {\n return true;\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, pollIntervalMs);\n });\n }\n\n return false;\n}\n\nexport async function findListeningProcessId(port: number): Promise<number | undefined> {\n const pids = await findListeningPids(port);\n return pids[0];\n}\n\nexport async function killProcessOnPort(port: number): Promise<void> {\n const portStr = port.toString();\n if (process.platform === \"win32\") {\n try {\n const pids = await findListeningPidsWithNetstat(port);\n for (const pid of pids) {\n try {\n // cspell:ignore taskkill\n await execFileAsync(\"taskkill\", [\"/F\", \"/PID\", pid.toString()]);\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore\n }\n return;\n }\n\n try {\n const { stdout } = await execFileAsync(\"lsof\", [\"-t\", \"-i\", `tcp:${portStr}`]);\n const lines = stdout.trim().split(\"\\n\").filter((line) => line.length > 0);\n for (const line of lines) {\n const pid = Number.parseInt(line, 10);\n if (Number.isNaN(pid)) {\n continue;\n }\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already dead\n }\n }\n } catch {\n // lsof missing or no match — ignore\n }\n}\n","export interface RegionInfo {\n readonly key: string;\n readonly apiEndpoint: string;\n}\n\nconst REGION_API_ENDPOINTS: Readonly<Record<string, string>> = {\n ae01: \"https://api.cf.ae01.hana.ondemand.com\",\n ap01: \"https://api.cf.ap01.hana.ondemand.com\",\n ap10: \"https://api.cf.ap10.hana.ondemand.com\",\n ap11: \"https://api.cf.ap11.hana.ondemand.com\",\n ap12: \"https://api.cf.ap12.hana.ondemand.com\",\n ap20: \"https://api.cf.ap20.hana.ondemand.com\",\n ap21: \"https://api.cf.ap21.hana.ondemand.com\",\n ap30: \"https://api.cf.ap30.hana.ondemand.com\",\n br10: \"https://api.cf.br10.hana.ondemand.com\",\n br20: \"https://api.cf.br20.hana.ondemand.com\",\n br30: \"https://api.cf.br30.hana.ondemand.com\",\n ca10: \"https://api.cf.ca10.hana.ondemand.com\",\n ca20: \"https://api.cf.ca20.hana.ondemand.com\",\n ch20: \"https://api.cf.ch20.hana.ondemand.com\",\n eu10: \"https://api.cf.eu10.hana.ondemand.com\",\n eu11: \"https://api.cf.eu11.hana.ondemand.com\",\n eu12: \"https://api.cf.eu12.hana.ondemand.com\",\n eu20: \"https://api.cf.eu20.hana.ondemand.com\",\n eu21: \"https://api.cf.eu21.hana.ondemand.com\",\n eu30: \"https://api.cf.eu30.hana.ondemand.com\",\n eu31: \"https://api.cf.eu31.hana.ondemand.com\",\n in30: \"https://api.cf.in30.hana.ondemand.com\",\n jp10: \"https://api.cf.jp10.hana.ondemand.com\",\n jp20: \"https://api.cf.jp20.hana.ondemand.com\",\n jp30: \"https://api.cf.jp30.hana.ondemand.com\",\n kr30: \"https://api.cf.kr30.hana.ondemand.com\",\n us10: \"https://api.cf.us10.hana.ondemand.com\",\n us11: \"https://api.cf.us11.hana.ondemand.com\",\n us20: \"https://api.cf.us20.hana.ondemand.com\",\n us21: \"https://api.cf.us21.hana.ondemand.com\",\n us30: \"https://api.cf.us30.hana.ondemand.com\",\n us31: \"https://api.cf.us31.hana.ondemand.com\",\n};\n\nexport function resolveApiEndpoint(regionKey: string, override?: string): string {\n if (override !== undefined && override !== \"\") {\n return override;\n }\n const endpoint = REGION_API_ENDPOINTS[regionKey];\n if (endpoint === undefined) {\n throw new Error(\n `Unknown region key: ${regionKey}. Pass \\`apiEndpoint\\` explicitly to override.`,\n );\n }\n return endpoint;\n}\n\nexport function listKnownRegionKeys(): readonly string[] {\n return Object.keys(REGION_API_ENDPOINTS);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { hostname as getHostname } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\nimport { withFileLock } from \"./lock.js\";\nimport { stateFilePath, stateLockPath } from \"./paths.js\";\nimport { CfDebuggerError } from \"./types.js\";\nimport type { ActiveSession, SessionKey, StateFile } from \"./types.js\";\n\nasync function readJsonFile<T>(path: string): Promise<T | undefined> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n process.stderr.write(\n `[cf-debugger] warning: state file at ${path} is not valid JSON; resetting to empty.\\n`,\n );\n return undefined;\n }\n}\n\nasync function writeJsonFileAtomic(path: string, value: unknown): Promise<void> {\n const tempPath = `${path}.${randomUUID()}.tmp`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n await rename(tempPath, path);\n}\n\nfunction emptyState(): StateFile {\n return { version: \"1\", sessions: [] };\n}\n\nfunction isValidState(value: unknown): value is StateFile {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<StateFile>;\n return candidate.version === \"1\" && Array.isArray(candidate.sessions);\n}\n\nexport function isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n return false;\n }\n return true;\n }\n}\n\nfunction filterStaleSessions(sessions: readonly ActiveSession[]): readonly ActiveSession[] {\n const host = getHostname();\n return sessions.filter((session) => {\n if (session.hostname !== host) {\n return true;\n }\n return isPidAlive(session.pid);\n });\n}\n\nasync function readStateRaw(): Promise<StateFile> {\n const parsed = await readJsonFile<unknown>(stateFilePath());\n if (!isValidState(parsed)) {\n return emptyState();\n }\n return parsed;\n}\n\nasync function writeState(state: StateFile): Promise<void> {\n await writeJsonFileAtomic(stateFilePath(), state);\n}\n\nexport interface StateReaderResult {\n readonly sessions: readonly ActiveSession[];\n readonly removed: readonly ActiveSession[];\n}\n\nasync function readAndPruneLocked(): Promise<StateReaderResult> {\n const raw = await readStateRaw();\n const pruned = filterStaleSessions(raw.sessions);\n const removed = raw.sessions.filter(\n (session) => !pruned.some((active) => active.sessionId === session.sessionId),\n );\n\n if (removed.length > 0) {\n await writeState({ version: \"1\", sessions: pruned });\n }\n\n return { sessions: pruned, removed };\n}\n\nexport async function readActiveSessions(): Promise<readonly ActiveSession[]> {\n const result = await withFileLock(stateLockPath(), readAndPruneLocked);\n return result.sessions;\n}\n\nexport async function readAndPruneActiveSessions(): Promise<StateReaderResult> {\n return await withFileLock(stateLockPath(), readAndPruneLocked);\n}\n\nexport function sessionKeyString(key: SessionKey): string {\n return `${key.region}:${key.org}:${key.space}:${key.app}`;\n}\n\nexport function matchesKey(session: SessionKey, key: SessionKey): boolean {\n return (\n session.region === key.region &&\n session.org === key.org &&\n session.space === key.space &&\n session.app === key.app\n );\n}\n\nexport interface RegisterSessionResult {\n readonly session: ActiveSession;\n readonly existing?: ActiveSession;\n}\n\nexport interface RegisterSessionInput extends SessionKey {\n readonly apiEndpoint: string;\n readonly preferredPort?: number;\n readonly portProbe: (port: number) => Promise<boolean>;\n readonly sessionIdFactory?: () => string;\n readonly cfHomeForSession: (sessionId: string) => string;\n readonly basePort?: number;\n readonly maxPort?: number;\n}\n\nconst DEFAULT_BASE_PORT = 20_000;\nconst DEFAULT_MAX_PORT = 20_999;\n\nasync function pickPort(\n preferred: number | undefined,\n reserved: ReadonlySet<number>,\n probe: (port: number) => Promise<boolean>,\n basePort: number,\n maxPort: number,\n): Promise<number> {\n const tryOrder: number[] = [];\n if (preferred !== undefined) {\n tryOrder.push(preferred);\n }\n for (let port = basePort; port <= maxPort; port++) {\n if (port !== preferred) {\n tryOrder.push(port);\n }\n }\n\n for (const port of tryOrder) {\n if (reserved.has(port)) {\n continue;\n }\n const free = await probe(port);\n if (free) {\n return port;\n }\n }\n throw new CfDebuggerError(\n \"PORT_UNAVAILABLE\",\n `No free local port available in range ${basePort.toString()}–${maxPort.toString()}`,\n );\n}\n\nexport async function registerNewSession(\n input: RegisterSessionInput,\n): Promise<RegisterSessionResult> {\n return await withFileLock(stateLockPath(), async (): Promise<RegisterSessionResult> => {\n const pruneResult = await readAndPruneLocked();\n const existing = pruneResult.sessions.find((session) => matchesKey(session, input));\n if (existing) {\n return { session: existing, existing };\n }\n\n const reservedPorts = new Set(pruneResult.sessions.map((session) => session.localPort));\n const localPort = await pickPort(\n input.preferredPort,\n reservedPorts,\n input.portProbe,\n input.basePort ?? DEFAULT_BASE_PORT,\n input.maxPort ?? DEFAULT_MAX_PORT,\n );\n\n const sessionId = (input.sessionIdFactory ?? randomUUID)();\n const cfHomeDir = input.cfHomeForSession(sessionId);\n\n const session: ActiveSession = {\n sessionId,\n pid: process.pid,\n hostname: getHostname(),\n region: input.region,\n org: input.org,\n space: input.space,\n app: input.app,\n apiEndpoint: input.apiEndpoint,\n localPort,\n remotePort: 9229,\n cfHomeDir,\n startedAt: new Date().toISOString(),\n status: \"starting\",\n };\n\n const nextSessions: readonly ActiveSession[] = [...pruneResult.sessions, session];\n await writeState({ version: \"1\", sessions: nextSessions });\n\n return { session };\n });\n}\n\nexport async function updateSessionStatus(\n sessionId: string,\n status: ActiveSession[\"status\"],\n message?: string,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const base: ActiveSession = {\n sessionId: session.sessionId,\n pid: session.pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status,\n };\n const next: ActiveSession = message === undefined ? base : { ...base, message };\n updated = next;\n return next;\n });\n\n if (updated) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function updateSessionPid(\n sessionId: string,\n pid: number,\n): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n let updated: ActiveSession | undefined;\n const nextSessions = raw.sessions.map((session): ActiveSession => {\n if (session.sessionId !== sessionId) {\n return session;\n }\n const next: ActiveSession = {\n sessionId: session.sessionId,\n pid,\n hostname: session.hostname,\n region: session.region,\n org: session.org,\n space: session.space,\n app: session.app,\n apiEndpoint: session.apiEndpoint,\n localPort: session.localPort,\n remotePort: session.remotePort,\n cfHomeDir: session.cfHomeDir,\n startedAt: session.startedAt,\n status: session.status,\n ...(session.message === undefined ? {} : { message: session.message }),\n };\n updated = next;\n return next;\n });\n\n if (updated !== undefined) {\n await writeState({ version: \"1\", sessions: nextSessions });\n }\n return updated;\n });\n}\n\nexport async function removeSession(sessionId: string): Promise<ActiveSession | undefined> {\n return await withFileLock(stateLockPath(), async (): Promise<ActiveSession | undefined> => {\n const raw = await readStateRaw();\n const target = raw.sessions.find((session) => session.sessionId === sessionId);\n if (!target) {\n return undefined;\n }\n const remaining = raw.sessions.filter((session) => session.sessionId !== sessionId);\n await writeState({ version: \"1\", sessions: remaining });\n return target;\n });\n}\n","import { mkdir, open, unlink } from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst DEFAULT_POLL_MS = 50;\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function acquireFileLock(\n lockPath: string,\n timeoutMs: number,\n pollMs: number,\n): Promise<FileHandle> {\n const deadline = Date.now() + timeoutMs;\n await mkdir(dirname(lockPath), { recursive: true });\n\n for (;;) {\n try {\n return await open(lockPath, \"wx\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n if (Date.now() > deadline) {\n throw new Error(`Timed out acquiring file lock at ${lockPath}`);\n }\n\n await sleep(pollMs);\n }\n}\n\nasync function releaseFileLock(lockPath: string, handle: FileHandle): Promise<void> {\n await handle.close();\n await unlink(lockPath).catch((err: unknown) => {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw err;\n }\n });\n}\n\nexport interface WithLockOptions {\n readonly timeoutMs?: number;\n readonly pollMs?: number;\n}\n\nexport async function withFileLock<T>(\n lockPath: string,\n work: () => Promise<T>,\n options?: WithLockOptions,\n): Promise<T> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n const handle = await acquireFileLock(lockPath, timeoutMs, pollMs);\n try {\n return await work();\n } finally {\n await releaseFileLock(lockPath, handle);\n }\n}\n"],"mappings":";;;AAuDO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,MAAc,SAAiB,QAAiB;AACjE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,QAAI,WAAW,QAAW;AACxB,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AACF;;;AClEA,SAAS,SAAAA,QAAO,UAAU;AAC1B,SAAS,YAAYC,oBAAmB;AACxC,OAAOC,cAAa;;;ACHpB,SAAS,UAAU,aAAa;AAChC,SAAS,iBAAiB;AAI1B,IAAM,gBAAgB,UAAU,QAAQ;AAExC,IAAM,aAAa,KAAK,OAAO;AAC/B,IAAM,oBAAoB;AAC1B,IAAM,wBAAwB;AAC9B,IAAM,2BAA2B;AACjC,IAAM,uBAAuB;AAO7B,SAAS,SAAS,QAAmC;AACnD,SAAO,EAAE,GAAG,QAAQ,KAAK,SAAS,OAAO;AAC3C;AAEA,SAAS,WAAW,SAAgC;AAClD,SAAO,QAAQ,WAAW,QAAQ,IAAI,oBAAoB,KAAK;AACjE;AAEA,eAAe,MACb,MACA,SACA,YAAoB,mBACH;AACjB,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc,WAAW,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG;AAAA,MACrE,KAAK,SAAS,QAAQ,MAAM;AAAA,MAC5B,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AACD,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,UAAM,IAAI;AACV,UAAM,SAAS,EAAE,QAAQ,KAAK,KAAK;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,MACA,MAAM,KAAK,KAAK,GAAG,CAAC,YAAY,OAAO,SAAS,IAAI,SAAS,EAAE,OAAO;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,MAAM,aAAqB,SAAuC;AACtF,QAAM,MAAM,CAAC,OAAO,WAAW,GAAG,OAAO;AAC3C;AAEA,eAAsB,OACpB,OACA,UACA,SACe;AACf,MAAI;AACJ,WAAS,UAAU,GAAG,UAAU,sBAAsB,WAAW;AAC/D,QAAI;AACF,YAAM,MAAM,CAAC,QAAQ,OAAO,QAAQ,GAAG,OAAO;AAC9C;AAAA,IACF,SAAS,KAAc;AACrB,kBAAY;AACZ,UAAI,UAAU,uBAAuB,GAAG;AACtC,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,qBAAW,SAAS,OAAQ,UAAU,EAAE;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,MAAI,qBAAqB,OAAO;AAC9B,UAAM;AAAA,EACR;AACA,QAAM,IAAI,gBAAgB,kBAAkB,mBAAmB,OAAO,SAAS,CAAC,EAAE;AACpF;AAEA,eAAsB,QACpB,aACA,OACA,UACA,SACe;AACf,MAAI;AACF,UAAM,MAAM,aAAa,OAAO;AAChC,UAAM,OAAO,OAAO,UAAU,OAAO;AAAA,EACvC,SAAS,KAAc;AACrB,QAAI,eAAe,iBAAiB;AAClC,YAAM,IAAI,gBAAgB,mBAAmB,IAAI,SAAS,IAAI,MAAM;AAAA,IACtE;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,SACpB,KACA,OACA,SACe;AACf,MAAI;AACF,UAAM,MAAM,CAAC,UAAU,MAAM,KAAK,MAAM,KAAK,GAAG,OAAO;AAAA,EACzD,SAAS,KAAc;AACrB,QAAI,eAAe,iBAAiB;AAClC,YAAM,IAAI,gBAAgB,oBAAoB,IAAI,SAAS,IAAI,MAAM;AAAA,IACvE;AACA,UAAM;AAAA,EACR;AACF;AAeA,eAAsB,aAAa,SAAiB,SAA0C;AAC5F,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,CAAC,eAAe,OAAO,GAAG,OAAO;AAC5D,WAAO,OAAO,YAAY,EAAE,SAAS,wBAAwB;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,YAAY,SAAiB,SAAuC;AACxF,MAAI;AACF,UAAM,MAAM,CAAC,cAAc,OAAO,GAAG,OAAO;AAAA,EAC9C,SAAS,KAAc;AACrB,QAAI,eAAe,iBAAiB;AAClC,YAAM,IAAI,gBAAgB,mBAAmB,IAAI,SAAS,IAAI,MAAM;AAAA,IACtE;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,aAAa,SAAiB,SAAuC;AACzF,QAAM,MAAM,CAAC,WAAW,OAAO,GAAG,SAAS,qBAAqB;AAClE;AAOA,eAAsB,aACpB,SACA,SACA,SAC4B;AAC5B,SAAO,MAAM,IAAI,QAA2B,CAAC,YAAY;AACvD,UAAM,QAAQ,MAAM,WAAW,OAAO,GAAG,CAAC,OAAO,SAAS,MAAM,OAAO,GAAG;AAAA,MACxE,KAAK,SAAS,QAAQ,MAAM;AAAA,MAC5B,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AACD,QAAI,YAAY;AAChB,QAAI,UAAU;AAEd,UAAM,UAAU,WAAW,MAAM;AAC/B,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,UAAI;AACF,cAAM,KAAK;AAAA,MACb,QAAQ;AAAA,MAER;AACA,cAAQ,EAAE,UAAU,MAAM,QAAQ,UAAU,CAAC;AAAA,IAC/C,GAAG,wBAAwB;AAE3B,UAAM,OAAO,GAAG,QAAQ,CAAC,SAA0B;AACjD,mBAAa,KAAK,SAAS;AAAA,IAC7B,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,mBAAa,OAAO;AACpB,cAAQ,EAAE,UAAU,MAAM,QAAQ,UAAU,CAAC;AAAA,IAC/C,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAe;AAChC,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,mBAAa,OAAO;AACpB,cAAQ,EAAE,UAAU,MAAM,QAAQ,IAAI,QAAQ,CAAC;AAAA,IACjD,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,mBAAmB,QAAyB;AAC1D,QAAM,QAAQ,OAAO,YAAY;AACjC,SAAO,MAAM,SAAS,gBAAgB,KAAK,MAAM,SAAS,yBAAyB;AACrF;AAEO,SAAS,eACd,SACA,WACA,YACA,SAC0B;AAC1B,QAAM,YAAY,GAAG,UAAU,SAAS,CAAC,cAAc,WAAW,SAAS,CAAC;AAC5E,QAAM,YAAY,QAAQ,aAAa;AACvC,SAAO,MAAM,WAAW,OAAO,GAAG,CAAC,OAAO,SAAS,MAAM,MAAM,SAAS,GAAG;AAAA,IACzE,KAAK,SAAS,QAAQ,MAAM;AAAA,IAC5B,OAAO;AAAA,IACP,UAAU,CAAC;AAAA,EACb,CAAC;AACH;;;AC5NA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,IAAM,oBAAoB;AAC1B,IAAM,6BAA6B;AACnC,IAAM,4BAA4B;AAClC,IAAM,4BAA4B;AAElC,SAAS,cAAsB;AACpC,SAAO,KAAK,QAAQ,GAAG,iBAAiB;AAC1C;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,YAAY,GAAG,0BAA0B;AACvD;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,YAAY,GAAG,yBAAyB;AACtD;AAEO,SAAS,iBAAiB,WAA2B;AAC1D,SAAO,KAAK,YAAY,GAAG,2BAA2B,SAAS;AACjE;;;ACtBA,SAAS,YAAAC,iBAAgB;AACzB,SAAS,kBAAkB,oBAAoB;AAC/C,SAAS,aAAAC,kBAAiB;AAE1B,IAAMC,iBAAgBD,WAAUD,SAAQ;AAExC,eAAe,6BAA6B,MAA0C;AACpF,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAME,eAAc,WAAW,CAAC,MAAM,CAAC;AAC1D,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,UAAI,CAAC,KAAK,SAAS,IAAI,KAAK,SAAS,CAAC,EAAE,KAAK,CAAC,KAAK,SAAS,WAAW,GAAG;AACxE;AAAA,MACF;AACA,YAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,UAAI,SAAS,QAAW;AACtB;AAAA,MACF;AACA,YAAM,MAAM,OAAO,SAAS,MAAM,EAAE;AACpC,UAAI,CAAC,OAAO,MAAM,GAAG,GAAG;AACtB,aAAK,IAAI,GAAG;AAAA,MACd;AAAA,IACF;AACA,WAAO,CAAC,GAAG,IAAI;AAAA,EACjB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,0BAA0B,MAA0C;AACjF,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAMA,eAAc,QAAQ,CAAC,OAAO,MAAM,MAAM,OAAO,KAAK,SAAS,CAAC,IAAI,cAAc,CAAC;AAC5G,WAAO,OACJ,KAAK,EACL,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,IAAI,CAAC,SAAS,OAAO,SAAS,MAAM,EAAE,CAAC,EACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,MAAM,GAAG,CAAC;AAAA,EACvC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,kBAAkB,MAA0C;AACzE,MAAI,QAAQ,aAAa,SAAS;AAChC,WAAO,MAAM,6BAA6B,IAAI;AAAA,EAChD;AACA,SAAO,MAAM,0BAA0B,IAAI;AAC7C;AAEA,eAAsB,WAAW,MAAgC;AAC/D,SAAO,MAAM,IAAI,QAAiB,CAAC,YAAY;AAC7C,UAAM,SAAS,aAAa;AAC5B,WAAO,KAAK,SAAS,MAAM;AACzB,cAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO,KAAK,aAAa,MAAM;AAC7B,aAAO,MAAM,MAAM;AACjB,gBAAQ,IAAI;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AACD,WAAO,OAAO,MAAM,WAAW;AAAA,EACjC,CAAC;AACH;AAEA,eAAsB,iBAAiB,MAAc,WAAqC;AACxF,QAAM,iBAAiB;AACvB,QAAM,UAAU,KAAK,IAAI;AAEzB,SAAO,KAAK,IAAI,IAAI,UAAU,WAAW;AACvC,UAAM,YAAY,MAAM,IAAI,QAAiB,CAAC,YAAY;AACxD,YAAM,SAAS,iBAAiB,EAAE,MAAM,MAAM,YAAY,CAAC;AAC3D,aAAO,WAAW,GAAG;AACrB,aAAO,KAAK,WAAW,MAAM;AAC3B,eAAO,QAAQ;AACf,gBAAQ,IAAI;AAAA,MACd,CAAC;AACD,aAAO,KAAK,SAAS,MAAM;AACzB,eAAO,QAAQ;AACf,gBAAQ,KAAK;AAAA,MACf,CAAC;AACD,aAAO,KAAK,WAAW,MAAM;AAC3B,eAAO,QAAQ;AACf,gBAAQ,KAAK;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAED,QAAI,WAAW;AACb,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAW,SAAS,cAAc;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,eAAsB,uBAAuB,MAA2C;AACtF,QAAM,OAAO,MAAM,kBAAkB,IAAI;AACzC,SAAO,KAAK,CAAC;AACf;AAEA,eAAsB,kBAAkB,MAA6B;AACnE,QAAM,UAAU,KAAK,SAAS;AAC9B,MAAI,QAAQ,aAAa,SAAS;AAChC,QAAI;AACF,YAAM,OAAO,MAAM,6BAA6B,IAAI;AACpD,iBAAW,OAAO,MAAM;AACtB,YAAI;AAEF,gBAAMA,eAAc,YAAY,CAAC,MAAM,QAAQ,IAAI,SAAS,CAAC,CAAC;AAAA,QAChE,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAMA,eAAc,QAAQ,CAAC,MAAM,MAAM,OAAO,OAAO,EAAE,CAAC;AAC7E,UAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACxE,eAAW,QAAQ,OAAO;AACxB,YAAM,MAAM,OAAO,SAAS,MAAM,EAAE;AACpC,UAAI,OAAO,MAAM,GAAG,GAAG;AACrB;AAAA,MACF;AACA,UAAI;AACF,gBAAQ,KAAK,KAAK,SAAS;AAAA,MAC7B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;ACxIA,IAAM,uBAAyD;AAAA,EAC7D,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAEO,SAAS,mBAAmB,WAAmB,UAA2B;AAC/E,MAAI,aAAa,UAAa,aAAa,IAAI;AAC7C,WAAO;AAAA,EACT;AACA,QAAM,WAAW,qBAAqB,SAAS;AAC/C,MAAI,aAAa,QAAW;AAC1B,UAAM,IAAI;AAAA,MACR,uBAAuB,SAAS;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,sBAAyC;AACvD,SAAO,OAAO,KAAK,oBAAoB;AACzC;;;ACvDA,SAAS,kBAAkB;AAC3B,SAAS,SAAAC,QAAO,UAAU,QAAQ,iBAAiB;AACnD,SAAS,YAAY,mBAAmB;AACxC,SAAS,WAAAC,gBAAe;AACxB,OAAOC,cAAa;;;ACJpB,SAAS,OAAO,MAAM,cAAc;AAEpC,SAAS,eAAe;AAExB,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAE3B,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,eAAW,SAAS,EAAE;AAAA,EACxB,CAAC;AACH;AAEA,eAAe,gBACb,UACA,WACA,QACqB;AACrB,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAM,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAElD,aAAS;AACP,QAAI;AACF,aAAO,MAAM,KAAK,UAAU,IAAI;AAAA,IAClC,SAAS,KAAc;AACrB,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,UAAU;AACrB,cAAM;AAAA,MACR;AAAA,IACF;AAEA,QAAI,KAAK,IAAI,IAAI,UAAU;AACzB,YAAM,IAAI,MAAM,oCAAoC,QAAQ,EAAE;AAAA,IAChE;AAEA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF;AAEA,eAAe,gBAAgB,UAAkB,QAAmC;AAClF,QAAM,OAAO,MAAM;AACnB,QAAM,OAAO,QAAQ,EAAE,MAAM,CAAC,QAAiB;AAC7C,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AACrB,YAAM;AAAA,IACR;AAAA,EACF,CAAC;AACH;AAOA,eAAsB,aACpB,UACA,MACA,SACY;AACZ,QAAM,YAAY,SAAS,aAAa;AACxC,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,SAAS,MAAM,gBAAgB,UAAU,WAAW,MAAM;AAChE,MAAI;AACF,WAAO,MAAM,KAAK;AAAA,EACpB,UAAE;AACA,UAAM,gBAAgB,UAAU,MAAM;AAAA,EACxC;AACF;;;ADxDA,eAAe,aAAgB,MAAsC;AACnE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,MAAM,MAAM;AAAA,EACnC,SAAS,KAAc;AACrB,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AACrB,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,IAAAC,SAAQ,OAAO;AAAA,MACb,wCAAwC,IAAI;AAAA;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AACF;AAEA,eAAe,oBAAoB,MAAc,OAA+B;AAC9E,QAAM,WAAW,GAAG,IAAI,IAAI,WAAW,CAAC;AACxC,QAAMC,OAAMC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,UAAU,UAAU,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AACvE,QAAM,OAAO,UAAU,IAAI;AAC7B;AAEA,SAAS,aAAwB;AAC/B,SAAO,EAAE,SAAS,KAAK,UAAU,CAAC,EAAE;AACtC;AAEA,SAAS,aAAa,OAAoC;AACxD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,YAAY;AAClB,SAAO,UAAU,YAAY,OAAO,MAAM,QAAQ,UAAU,QAAQ;AACtE;AAEO,SAAS,WAAW,KAAsB;AAC/C,MAAI;AACF,IAAAF,SAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,SAAS;AACpB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oBAAoB,UAA8D;AACzF,QAAM,OAAO,YAAY;AACzB,SAAO,SAAS,OAAO,CAAC,YAAY;AAClC,QAAI,QAAQ,aAAa,MAAM;AAC7B,aAAO;AAAA,IACT;AACA,WAAO,WAAW,QAAQ,GAAG;AAAA,EAC/B,CAAC;AACH;AAEA,eAAe,eAAmC;AAChD,QAAM,SAAS,MAAM,aAAsB,cAAc,CAAC;AAC1D,MAAI,CAAC,aAAa,MAAM,GAAG;AACzB,WAAO,WAAW;AAAA,EACpB;AACA,SAAO;AACT;AAEA,eAAe,WAAW,OAAiC;AACzD,QAAM,oBAAoB,cAAc,GAAG,KAAK;AAClD;AAOA,eAAe,qBAAiD;AAC9D,QAAM,MAAM,MAAM,aAAa;AAC/B,QAAM,SAAS,oBAAoB,IAAI,QAAQ;AAC/C,QAAM,UAAU,IAAI,SAAS;AAAA,IAC3B,CAAC,YAAY,CAAC,OAAO,KAAK,CAAC,WAAW,OAAO,cAAc,QAAQ,SAAS;AAAA,EAC9E;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,WAAW,EAAE,SAAS,KAAK,UAAU,OAAO,CAAC;AAAA,EACrD;AAEA,SAAO,EAAE,UAAU,QAAQ,QAAQ;AACrC;AAOA,eAAsB,6BAAyD;AAC7E,SAAO,MAAM,aAAa,cAAc,GAAG,kBAAkB;AAC/D;AAEO,SAAS,iBAAiB,KAAyB;AACxD,SAAO,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AACzD;AAEO,SAAS,WAAW,SAAqB,KAA0B;AACxE,SACE,QAAQ,WAAW,IAAI,UACvB,QAAQ,QAAQ,IAAI,OACpB,QAAQ,UAAU,IAAI,SACtB,QAAQ,QAAQ,IAAI;AAExB;AAiBA,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AAEzB,eAAe,SACb,WACA,UACA,OACA,UACA,SACiB;AACjB,QAAM,WAAqB,CAAC;AAC5B,MAAI,cAAc,QAAW;AAC3B,aAAS,KAAK,SAAS;AAAA,EACzB;AACA,WAAS,OAAO,UAAU,QAAQ,SAAS,QAAQ;AACjD,QAAI,SAAS,WAAW;AACtB,eAAS,KAAK,IAAI;AAAA,IACpB;AAAA,EACF;AAEA,aAAW,QAAQ,UAAU;AAC3B,QAAI,SAAS,IAAI,IAAI,GAAG;AACtB;AAAA,IACF;AACA,UAAM,OAAO,MAAM,MAAM,IAAI;AAC7B,QAAI,MAAM;AACR,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,IACA,yCAAyC,SAAS,SAAS,CAAC,SAAI,QAAQ,SAAS,CAAC;AAAA,EACpF;AACF;AAEA,eAAsB,mBACpB,OACgC;AAChC,SAAO,MAAM,aAAa,cAAc,GAAG,YAA4C;AACrF,UAAM,cAAc,MAAM,mBAAmB;AAC7C,UAAM,WAAW,YAAY,SAAS,KAAK,CAACG,aAAY,WAAWA,UAAS,KAAK,CAAC;AAClF,QAAI,UAAU;AACZ,aAAO,EAAE,SAAS,UAAU,SAAS;AAAA,IACvC;AAEA,UAAM,gBAAgB,IAAI,IAAI,YAAY,SAAS,IAAI,CAACA,aAAYA,SAAQ,SAAS,CAAC;AACtF,UAAM,YAAY,MAAM;AAAA,MACtB,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN,MAAM,YAAY;AAAA,MAClB,MAAM,WAAW;AAAA,IACnB;AAEA,UAAM,aAAa,MAAM,oBAAoB,YAAY;AACzD,UAAM,YAAY,MAAM,iBAAiB,SAAS;AAElD,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,KAAKC,SAAQ;AAAA,MACb,UAAU,YAAY;AAAA,MACtB,QAAQ,MAAM;AAAA,MACd,KAAK,MAAM;AAAA,MACX,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,MACX,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,QAAQ;AAAA,IACV;AAEA,UAAM,eAAyC,CAAC,GAAG,YAAY,UAAU,OAAO;AAChF,UAAM,WAAW,EAAE,SAAS,KAAK,UAAU,aAAa,CAAC;AAEzD,WAAO,EAAE,QAAQ;AAAA,EACnB,CAAC;AACH;AAEA,eAAsB,oBACpB,WACA,QACA,SACoC;AACpC,SAAO,MAAM,aAAa,cAAc,GAAG,YAAgD;AACzF,UAAM,MAAM,MAAM,aAAa;AAC/B,QAAI;AACJ,UAAM,eAAe,IAAI,SAAS,IAAI,CAAC,YAA2B;AAChE,UAAI,QAAQ,cAAc,WAAW;AACnC,eAAO;AAAA,MACT;AACA,YAAM,OAAsB;AAAA,QAC1B,WAAW,QAAQ;AAAA,QACnB,KAAK,QAAQ;AAAA,QACb,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,OAAO,QAAQ;AAAA,QACf,KAAK,QAAQ;AAAA,QACb,aAAa,QAAQ;AAAA,QACrB,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,WAAW,QAAQ;AAAA,QACnB,WAAW,QAAQ;AAAA,QACnB;AAAA,MACF;AACA,YAAM,OAAsB,YAAY,SAAY,OAAO,EAAE,GAAG,MAAM,QAAQ;AAC9E,gBAAU;AACV,aAAO;AAAA,IACT,CAAC;AAED,QAAI,SAAS;AACX,YAAM,WAAW,EAAE,SAAS,KAAK,UAAU,aAAa,CAAC;AAAA,IAC3D;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,iBACpB,WACA,KACoC;AACpC,SAAO,MAAM,aAAa,cAAc,GAAG,YAAgD;AACzF,UAAM,MAAM,MAAM,aAAa;AAC/B,QAAI;AACJ,UAAM,eAAe,IAAI,SAAS,IAAI,CAAC,YAA2B;AAChE,UAAI,QAAQ,cAAc,WAAW;AACnC,eAAO;AAAA,MACT;AACA,YAAM,OAAsB;AAAA,QAC1B,WAAW,QAAQ;AAAA,QACnB;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,OAAO,QAAQ;AAAA,QACf,KAAK,QAAQ;AAAA,QACb,aAAa,QAAQ;AAAA,QACrB,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,WAAW,QAAQ;AAAA,QACnB,WAAW,QAAQ;AAAA,QACnB,QAAQ,QAAQ;AAAA,QAChB,GAAI,QAAQ,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ;AAAA,MACtE;AACA,gBAAU;AACV,aAAO;AAAA,IACT,CAAC;AAED,QAAI,YAAY,QAAW;AACzB,YAAM,WAAW,EAAE,SAAS,KAAK,UAAU,aAAa,CAAC;AAAA,IAC3D;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,cAAc,WAAuD;AACzF,SAAO,MAAM,aAAa,cAAc,GAAG,YAAgD;AACzF,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,SAAS,IAAI,SAAS,KAAK,CAAC,YAAY,QAAQ,cAAc,SAAS;AAC7E,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AACA,UAAM,YAAY,IAAI,SAAS,OAAO,CAAC,YAAY,QAAQ,cAAc,SAAS;AAClF,UAAM,WAAW,EAAE,SAAS,KAAK,UAAU,UAAU,CAAC;AACtD,WAAO;AAAA,EACT,CAAC;AACH;;;ALhRA,IAAM,kCAAkC;AACxC,IAAM,qBAAqB;AAC3B,IAAM,wBAAwB;AAC9B,IAAM,yBAAyB;AAC/B,IAAM,wBAAwB;AAC9B,IAAM,0BAA0B;AAEhC,SAAS,iBAAiB,KAAa,QAA8B;AACnE,QAAM,YAAYC,SAAQ,aAAa;AACvC,MAAI,CAAC,WAAW;AACd,QAAI;AACF,MAAAA,SAAQ,KAAK,CAAC,KAAK,MAAM;AACzB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI;AACF,IAAAA,SAAQ,KAAK,KAAK,MAAM;AAAA,EAC1B,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,oBACb,KACA,YAAoB,wBACL;AACf,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB;AAAA,EACF;AAEA,mBAAiB,KAAK,SAAS;AAC/B,QAAM,YAAY,KAAK,IAAI;AAC3B,SAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AACzC,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB;AAAA,IACF;AACA,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAW,SAAS,uBAAuB;AAAA,IAC7C,CAAC;AAAA,EACH;AAEA,mBAAiB,KAAK,SAAS;AACjC;AAEA,eAAe,uBACb,OACA,YAAoB,wBACL;AACf,MAAI,MAAM,QAAQ,QAAW;AAC3B;AAAA,EACF;AACA,MAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM;AACxD;AAAA,EACF;AACA,QAAM,oBAAoB,MAAM,KAAK,SAAS;AAChD;AAEA,eAAe,yBAA4D;AACzE,QAAM,SAAS,MAAM,2BAA2B;AAChD,QAAM,OAAOC,aAAY;AACzB,aAAW,WAAW,OAAO,SAAS;AACpC,QAAI,QAAQ,aAAa,MAAM;AAC7B,WAAK,kBAAkB,QAAQ,SAAS;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAEA,SAAS,WAAW,QAAuC;AACzD,MAAI,QAAQ,SAAS;AACnB,UAAM,IAAI,gBAAgB,WAAW,6BAA6B;AAAA,EACpE;AACF;AAEA,SAAS,mBAAmB,SAG1B;AACA,QAAM,QAAQ,QAAQ,SAASD,SAAQ,IAAI,WAAW;AACtD,QAAM,WAAW,QAAQ,YAAYA,SAAQ,IAAI,cAAc;AAC/D,MAAI,UAAU,UAAa,UAAU,IAAI;AACvC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,aAAa,UAAa,aAAa,IAAI;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,eAAsB,cAAc,SAAwD;AAC1F,QAAM,EAAE,OAAO,SAAS,IAAI,mBAAmB,OAAO;AACtD,QAAM,cAAc,mBAAmB,QAAQ,QAAQ,QAAQ,WAAW;AAC1E,QAAM,uBAAuB,QAAQ,wBAAwB;AAC7D,QAAM,OAAO,CAAC,QAAuB,YAA2B;AAC9D,YAAQ,WAAW,QAAQ,OAAO;AAAA,EACpC;AAEA,aAAW,QAAQ,MAAM;AAEzB,QAAM,uBAAuB;AAE7B,QAAM,eAAe,MAAM,mBAAmB;AAAA,IAC5C,QAAQ,QAAQ;AAAA,IAChB,KAAK,QAAQ;AAAA,IACb,OAAO,QAAQ;AAAA,IACf,KAAK,QAAQ;AAAA,IACb;AAAA,IACA,GAAI,QAAQ,kBAAkB,SAAY,CAAC,IAAI,EAAE,eAAe,QAAQ,cAAc;AAAA,IACtF,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB,CAAC;AAED,MAAI,aAAa,UAAU;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,6CAA6C,iBAAiB,OAAO,CAAC,YACzD,aAAa,SAAS,UAAU,SAAS,CAAC,SAC7C,aAAa,SAAS,IAAI,SAAS,CAAC,eAAe,aAAa,SAAS,SAAS;AAAA,IAE9F;AAAA,EACF;AAEA,QAAM,UAAU,aAAa;AAC7B,QAAM,UAAyB,EAAE,QAAQ,QAAQ,UAAU;AAE3D,MAAI;AACJ,MAAI,eAAe;AACnB,MAAI;AACJ,QAAM,cAAc,IAAI,QAAuB,CAAC,YAAY;AAC1D,kBAAc;AAAA,EAChB,CAAC;AAED,QAAM,oBAAoB,YAA2B;AACnD,QAAI;AACF,YAAM,GAAG,QAAQ,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAC9D,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,WAAW,YAA2B;AAC1C,QAAI,CAAC,cAAc;AACjB,qBAAe;AACf,UAAI,OAAO;AACT,cAAM,uBAAuB,KAAK;AAAA,MACpC;AACA,iBAAW,MAAM;AACf,aAAK,kBAAkB,QAAQ,SAAS;AAAA,MAC1C,GAAG,qBAAqB;AAAA,IAC1B;AACA,UAAM,cAAc,QAAQ,SAAS;AACrC,UAAM,kBAAkB;AACxB,SAAK,SAAS;AAAA,EAChB;AAEA,MAAI;AACF,UAAME,OAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;AAElD,SAAK,YAAY;AACjB,UAAM,oBAAoB,QAAQ,WAAW,YAAY;AACzD,UAAM,QAAQ,aAAa,OAAO,UAAU,OAAO;AACnD,eAAW,QAAQ,MAAM;AAEzB,SAAK,WAAW;AAChB,UAAM,oBAAoB,QAAQ,WAAW,WAAW;AACxD,UAAM,SAAS,QAAQ,KAAK,QAAQ,OAAO,OAAO;AAClD,eAAW,QAAQ,MAAM;AAEzB,UAAM,kBAAkB,QAAQ,SAAS;AACzC,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAW,SAAS,GAAG;AAAA,IACzB,CAAC;AAED,SAAK,WAAW;AAChB,UAAM,oBAAoB,QAAQ,WAAW,WAAW;AACxD,UAAM,eAAe,MAAM;AAAA,MACzB,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAEA,QAAI,mBAAmB,aAAa,MAAM,GAAG;AAC3C,YAAM,iBAAiB,MAAM,aAAa,QAAQ,KAAK,OAAO;AAC9D,UAAI,CAAC,gBAAgB;AACnB,aAAK,gBAAgB,yBAAyB;AAC9C,cAAM,oBAAoB,QAAQ,WAAW,cAAc;AAC3D,cAAM,YAAY,QAAQ,KAAK,OAAO;AAAA,MACxC;AACA,WAAK,kBAAkB,sCAAsC;AAC7D,YAAM,oBAAoB,QAAQ,WAAW,gBAAgB;AAC7D,YAAM,aAAa,QAAQ,KAAK,OAAO;AACvC,iBAAW,QAAQ,MAAM;AAEzB,WAAK,WAAW;AAChB,YAAM,oBAAoB,QAAQ,WAAW,WAAW;AACxD,YAAM,oBAAoB,MAAM;AAAA,QAC9B,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACF;AACA,UAAI,kBAAkB,aAAa,GAAG;AACpC,cAAM,SACJ,kBAAkB,OAAO,KAAK,EAAE,SAAS,IACrC,kBAAkB,OAAO,KAAK,IAC9B,aAAa,OAAO,kBAAkB,QAAQ,CAAC;AACrD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,oDAAoD,QAAQ,GAAG,wBAAwB,MAAM;AAAA,UAC7F,kBAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF,WAAW,aAAa,aAAa,GAAG;AACtC,YAAM,SACJ,aAAa,OAAO,KAAK,EAAE,SAAS,IAChC,aAAa,OAAO,KAAK,IACzB,aAAa,OAAO,aAAa,QAAQ,CAAC;AAChD,YAAM,IAAI;AAAA,QACR;AAAA,QACA,oDAAoD,QAAQ,GAAG,KAAK,MAAM;AAAA,QAC1E,aAAa;AAAA,MACf;AAAA,IACF;AAEA,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAW,SAAS,kBAAkB;AAAA,IACxC,CAAC;AACD,eAAW,QAAQ,MAAM;AAEzB,SAAK,WAAW;AAChB,UAAM,oBAAoB,QAAQ,WAAW,WAAW;AAExD,QAAI,CAAE,MAAM,WAAW,QAAQ,SAAS,GAAI;AAC1C,YAAM,kBAAkB,QAAQ,SAAS;AACzC,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,mBAAW,SAAS,qBAAqB;AAAA,MAC3C,CAAC;AACD,UAAI,CAAE,MAAM,WAAW,QAAQ,SAAS,GAAI;AAC1C,cAAM,IAAI;AAAA,UACR;AAAA,UACA,cAAc,QAAQ,UAAU,SAAS,CAAC;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,eAAe,QAAQ,KAAK,QAAQ,WAAW,QAAQ,YAAY,OAAO;AAClF,QAAI,MAAM,QAAQ,QAAW;AAC3B,YAAM,iBAAiB,QAAQ,WAAW,MAAM,GAAG;AAAA,IACrD;AAEA,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,qBAAe;AACf,oBAAc,IAAI;AAAA,IACpB,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAe;AAChC,WAAK,SAAS,IAAI,OAAO;AAAA,IAC3B,CAAC;AAED,UAAM,QAAQ,MAAM,iBAAiB,QAAQ,WAAW,oBAAoB;AAC5E,eAAW,QAAQ,MAAM;AACzB,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,QACA,sBAAsB,QAAQ,UAAU,SAAS,CAAC,gCAC7C,KAAK,MAAM,uBAAuB,GAAI,EAAE,SAAS,CAAC;AAAA,MACzD;AAAA,IACF;AAEA,UAAM,eAAe,MAAM,uBAAuB,QAAQ,SAAS;AACnE,UAAM,YAAY,gBAAgB,MAAM,OAAO,QAAQ;AACvD,QAAI,cAAc,QAAQ,KAAK;AAC7B,YAAM,iBAAiB,QAAQ,WAAW,SAAS;AAAA,IACrD;AAEA,SAAK,OAAO;AACZ,UAAM,eAAe,MAAM,oBAAoB,QAAQ,WAAW,OAAO;AACzE,UAAM,gBAA+B,gBAAgB,EAAE,GAAG,SAAS,KAAK,WAAW,QAAQ,QAAQ;AAEnG,QAAI;AACJ,UAAM,SAAyB;AAAA,MAC7B,SAAS;AAAA,MACT,SAAS,YAA2B;AAClC,4BAAoB,YAA2B;AAC7C,eAAK,UAAU;AACf,gBAAM,oBAAoB,QAAQ,WAAW,UAAU;AACvD,gBAAM,SAAS;AAAA,QACjB,GAAG;AACH,cAAM;AAAA,MACR;AAAA,MACA,aAAa,YAAoC;AAC/C,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAEA,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,SAAK,SAAS,OAAO;AACrB,UAAM,SAAS;AACf,UAAM;AAAA,EACR;AACF;AAOA,eAAsB,aAAa,SAA0D;AAC3F,QAAM,WAAW,MAAM,uBAAuB;AAC9C,MAAI;AACJ,MAAI,QAAQ,cAAc,QAAW;AACnC,aAAS,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,QAAQ,SAAS;AAAA,EACjE,WAAW,QAAQ,QAAQ,QAAW;AACpC,UAAM,MAAM,QAAQ;AACpB,aAAS,SAAS,KAAK,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,EAClD;AACA,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQF,SAAQ,KAAK;AAC9B,QAAI;AACF,YAAM,oBAAoB,OAAO,GAAG;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,aAAW,MAAM;AACf,SAAK,kBAAkB,OAAO,SAAS;AAAA,EACzC,GAAG,qBAAqB;AACxB,QAAM,UAAU,MAAM,cAAc,OAAO,SAAS;AACpD,MAAI;AACF,UAAM,GAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC7D,QAAQ;AAAA,EAER;AACA,SAAO,WAAW;AACpB;AAEA,eAAsB,mBAAoC;AACxD,QAAM,WAAW,MAAM,uBAAuB;AAC9C,MAAI,UAAU;AACd,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAS,MAAM,aAAa,EAAE,WAAW,QAAQ,UAAU,CAAC;AAClE,QAAI,QAAQ;AACV,iBAAW;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,eAAkD;AACtE,SAAO,MAAM,uBAAuB;AACtC;AAEA,eAAsB,WAAW,KAAqD;AACpF,QAAM,WAAW,MAAM,uBAAuB;AAC9C,SAAO,SAAS,KAAK,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAChD;","names":["mkdir","getHostname","process","execFile","promisify","execFileAsync","mkdir","dirname","process","process","mkdir","dirname","session","process","process","getHostname","mkdir"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saptools/cf-debugger",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Open an SSH debug tunnel to a SAP BTP Cloud Foundry app's Node.js inspector — from any terminal, no IDE required.",
5
5
  "type": "module",
6
6
  "publishConfig": {