@saptools/cf-debugger 0.1.4 → 0.1.6
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/dist/cli.js +261 -188
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +226 -151
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,26 +14,38 @@ var CfDebuggerError = class extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// src/
|
|
17
|
+
// src/debug-session/start.ts
|
|
18
18
|
import { mkdir as mkdir3, rm } from "fs/promises";
|
|
19
|
-
import
|
|
20
|
-
import process3 from "process";
|
|
19
|
+
import process4 from "process";
|
|
21
20
|
|
|
22
|
-
// src/
|
|
23
|
-
import { execFile
|
|
21
|
+
// src/cloud-foundry/execute.ts
|
|
22
|
+
import { execFile } from "child_process";
|
|
24
23
|
import { promisify } from "util";
|
|
25
24
|
var execFileAsync = promisify(execFile);
|
|
26
25
|
var MAX_BUFFER = 16 * 1024 * 1024;
|
|
27
26
|
var CF_CLI_TIMEOUT_MS = 3e4;
|
|
28
|
-
var
|
|
29
|
-
var CF_SSH_SIGNAL_TIMEOUT_MS = 15e3;
|
|
30
|
-
var CF_AUTH_MAX_ATTEMPTS = 3;
|
|
27
|
+
var REDACTED_ARG = "<redacted>";
|
|
31
28
|
function buildEnv(cfHome) {
|
|
32
29
|
return { ...process.env, CF_HOME: cfHome };
|
|
33
30
|
}
|
|
34
31
|
function resolveBin(context) {
|
|
35
32
|
return context.command ?? process.env["CF_DEBUGGER_CF_BIN"] ?? "cf";
|
|
36
33
|
}
|
|
34
|
+
function sensitiveArgs(args) {
|
|
35
|
+
if (args[0] !== "auth") {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
return args.slice(1).filter((arg) => arg.length > 0);
|
|
39
|
+
}
|
|
40
|
+
function redactText(text, values) {
|
|
41
|
+
return values.reduce((current, value) => current.split(value).join(REDACTED_ARG), text);
|
|
42
|
+
}
|
|
43
|
+
function formatArgsForError(args) {
|
|
44
|
+
if (args[0] !== "auth") {
|
|
45
|
+
return args.join(" ");
|
|
46
|
+
}
|
|
47
|
+
return args.map((arg, index) => index === 0 ? arg : REDACTED_ARG).join(" ");
|
|
48
|
+
}
|
|
37
49
|
async function runCf(args, context, timeoutMs = CF_CLI_TIMEOUT_MS) {
|
|
38
50
|
try {
|
|
39
51
|
const { stdout } = await execFileAsync(resolveBin(context), [...args], {
|
|
@@ -44,14 +56,20 @@ async function runCf(args, context, timeoutMs = CF_CLI_TIMEOUT_MS) {
|
|
|
44
56
|
return stdout;
|
|
45
57
|
} catch (err) {
|
|
46
58
|
const e = err;
|
|
47
|
-
const
|
|
59
|
+
const redactionValues = sensitiveArgs(args);
|
|
60
|
+
const stderr = redactText(e.stderr?.trim() ?? "", redactionValues);
|
|
61
|
+
const fallbackMessage = redactText(e.message, redactionValues);
|
|
48
62
|
throw new CfDebuggerError(
|
|
49
63
|
"CF_CLI_FAILED",
|
|
50
|
-
`cf ${args
|
|
64
|
+
`cf ${formatArgsForError(args)} failed: ${stderr.length > 0 ? stderr : fallbackMessage}`,
|
|
51
65
|
stderr
|
|
52
66
|
);
|
|
53
67
|
}
|
|
54
68
|
}
|
|
69
|
+
|
|
70
|
+
// src/cloud-foundry/commands.ts
|
|
71
|
+
var CF_RESTART_TIMEOUT_MS = 12e4;
|
|
72
|
+
var CF_AUTH_MAX_ATTEMPTS = 3;
|
|
55
73
|
async function cfApi(apiEndpoint, context) {
|
|
56
74
|
await runCf(["api", apiEndpoint], context);
|
|
57
75
|
}
|
|
@@ -117,6 +135,10 @@ async function cfEnableSsh(appName, context) {
|
|
|
117
135
|
async function cfRestartApp(appName, context) {
|
|
118
136
|
await runCf(["restart", appName], context, CF_RESTART_TIMEOUT_MS);
|
|
119
137
|
}
|
|
138
|
+
|
|
139
|
+
// src/cloud-foundry/ssh.ts
|
|
140
|
+
import { spawn } from "child_process";
|
|
141
|
+
var CF_SSH_SIGNAL_TIMEOUT_MS = 15e3;
|
|
120
142
|
async function cfSshOneShot(appName, command, context) {
|
|
121
143
|
return await new Promise((resolve) => {
|
|
122
144
|
const child = spawn(resolveBin(context), ["ssh", appName, "-c", command], {
|
|
@@ -191,7 +213,7 @@ function sessionCfHomeDir(sessionId) {
|
|
|
191
213
|
return join(saptoolsDir(), CF_DEBUGGER_HOMES_DIRNAME, sessionId);
|
|
192
214
|
}
|
|
193
215
|
|
|
194
|
-
// src/
|
|
216
|
+
// src/network/ports.ts
|
|
195
217
|
import { execFile as execFile2 } from "child_process";
|
|
196
218
|
import { createConnection, createServer } from "net";
|
|
197
219
|
import { promisify as promisify2 } from "util";
|
|
@@ -363,7 +385,7 @@ function listKnownRegionKeys() {
|
|
|
363
385
|
return Object.keys(REGION_API_ENDPOINTS);
|
|
364
386
|
}
|
|
365
387
|
|
|
366
|
-
// src/state.ts
|
|
388
|
+
// src/session-state/store.ts
|
|
367
389
|
import { randomUUID } from "crypto";
|
|
368
390
|
import { mkdir as mkdir2, readFile, rename, writeFile } from "fs/promises";
|
|
369
391
|
import { hostname as getHostname } from "os";
|
|
@@ -418,7 +440,7 @@ async function withFileLock(lockPath, work, options) {
|
|
|
418
440
|
}
|
|
419
441
|
}
|
|
420
442
|
|
|
421
|
-
// src/state.ts
|
|
443
|
+
// src/session-state/store.ts
|
|
422
444
|
async function readJsonFile(path) {
|
|
423
445
|
let raw;
|
|
424
446
|
try {
|
|
@@ -650,13 +672,29 @@ async function removeSession(sessionId) {
|
|
|
650
672
|
});
|
|
651
673
|
}
|
|
652
674
|
|
|
653
|
-
// src/
|
|
675
|
+
// src/debug-session/constants.ts
|
|
654
676
|
var DEFAULT_TUNNEL_READY_TIMEOUT_MS = 3e4;
|
|
655
677
|
var POST_USR1_DELAY_MS = 300;
|
|
656
678
|
var PORT_CLEANUP_DELAY_MS = 600;
|
|
657
679
|
var CHILD_SIGTERM_GRACE_MS = 2e3;
|
|
658
680
|
var PORT_RECLAIM_DELAY_MS = 250;
|
|
659
681
|
var PID_TERMINATION_POLL_MS = 100;
|
|
682
|
+
|
|
683
|
+
// src/debug-session/orphans.ts
|
|
684
|
+
import { hostname as getHostname2 } from "os";
|
|
685
|
+
async function pruneAndCleanupOrphans() {
|
|
686
|
+
const result = await readAndPruneActiveSessions();
|
|
687
|
+
const host = getHostname2();
|
|
688
|
+
for (const removed of result.removed) {
|
|
689
|
+
if (removed.hostname === host) {
|
|
690
|
+
void killProcessOnPort(removed.localPort);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return result.sessions;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/debug-session/processes.ts
|
|
697
|
+
import process3 from "process";
|
|
660
698
|
function signalPidOrGroup(pid, signal) {
|
|
661
699
|
const isWindows = process3.platform === "win32";
|
|
662
700
|
if (!isWindows) {
|
|
@@ -696,24 +734,16 @@ async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS)
|
|
|
696
734
|
}
|
|
697
735
|
await terminatePidOrGroup(child.pid, timeoutMs);
|
|
698
736
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const host = getHostname2();
|
|
702
|
-
for (const removed of result.removed) {
|
|
703
|
-
if (removed.hostname === host) {
|
|
704
|
-
void killProcessOnPort(removed.localPort);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
return result.sessions;
|
|
708
|
-
}
|
|
737
|
+
|
|
738
|
+
// src/debug-session/start.ts
|
|
709
739
|
function checkAbort(signal) {
|
|
710
740
|
if (signal?.aborted) {
|
|
711
741
|
throw new CfDebuggerError("ABORTED", "Operation aborted by caller");
|
|
712
742
|
}
|
|
713
743
|
}
|
|
714
744
|
function requireCredentials(options) {
|
|
715
|
-
const email = options.email ??
|
|
716
|
-
const password = options.password ??
|
|
745
|
+
const email = options.email ?? process4.env["SAP_EMAIL"];
|
|
746
|
+
const password = options.password ?? process4.env["SAP_PASSWORD"];
|
|
717
747
|
if (email === void 0 || email === "") {
|
|
718
748
|
throw new CfDebuggerError(
|
|
719
749
|
"MISSING_CREDENTIALS",
|
|
@@ -728,15 +758,7 @@ function requireCredentials(options) {
|
|
|
728
758
|
}
|
|
729
759
|
return { email, password };
|
|
730
760
|
}
|
|
731
|
-
async function
|
|
732
|
-
const { email, password } = requireCredentials(options);
|
|
733
|
-
const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);
|
|
734
|
-
const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;
|
|
735
|
-
const emit = (status, message) => {
|
|
736
|
-
options.onStatus?.(status, message);
|
|
737
|
-
};
|
|
738
|
-
checkAbort(options.signal);
|
|
739
|
-
await pruneAndCleanupOrphans();
|
|
761
|
+
async function registerSession(options, apiEndpoint) {
|
|
740
762
|
const registration = await registerNewSession({
|
|
741
763
|
region: options.region,
|
|
742
764
|
org: options.org,
|
|
@@ -753,20 +775,151 @@ async function startDebugger(options) {
|
|
|
753
775
|
`A debugger session is already running for ${sessionKeyString(options)} on port ${registration.existing.localPort.toString()} (pid ${registration.existing.pid.toString()}, sessionId ${registration.existing.sessionId}). Stop it first with \`cf-debugger stop\`.`
|
|
754
776
|
);
|
|
755
777
|
}
|
|
756
|
-
|
|
778
|
+
return registration.session;
|
|
779
|
+
}
|
|
780
|
+
async function loginAndTarget(options, apiEndpoint, email, password, context, sessionId, emit) {
|
|
781
|
+
emit("logging-in");
|
|
782
|
+
await updateSessionStatus(sessionId, "logging-in");
|
|
783
|
+
await cfLogin(apiEndpoint, email, password, context);
|
|
784
|
+
checkAbort(options.signal);
|
|
785
|
+
emit("targeting");
|
|
786
|
+
await updateSessionStatus(sessionId, "targeting");
|
|
787
|
+
await cfTarget(options.org, options.space, context);
|
|
788
|
+
checkAbort(options.signal);
|
|
789
|
+
}
|
|
790
|
+
async function signalRemoteNode(options, context, sessionId, emit) {
|
|
791
|
+
emit("signaling");
|
|
792
|
+
await updateSessionStatus(sessionId, "signaling");
|
|
793
|
+
const signalResult = await cfSshOneShot(options.app, `kill -s USR1 $(pidof node)`, context);
|
|
794
|
+
if (!isSshDisabledError(signalResult.stderr)) {
|
|
795
|
+
if (signalResult.exitCode === 0) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const detail = signalResult.stderr.trim().length > 0 ? signalResult.stderr.trim() : `exit code ${String(signalResult.exitCode)}`;
|
|
799
|
+
throw new CfDebuggerError(
|
|
800
|
+
"USR1_SIGNAL_FAILED",
|
|
801
|
+
`Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,
|
|
802
|
+
signalResult.stderr
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
const alreadyEnabled = await cfSshEnabled(options.app, context);
|
|
806
|
+
if (!alreadyEnabled) {
|
|
807
|
+
emit("ssh-enabling", "Enabling SSH on the app");
|
|
808
|
+
await updateSessionStatus(sessionId, "ssh-enabling");
|
|
809
|
+
await cfEnableSsh(options.app, context);
|
|
810
|
+
}
|
|
811
|
+
emit("ssh-restarting", "Restarting app so SSH becomes active");
|
|
812
|
+
await updateSessionStatus(sessionId, "ssh-restarting");
|
|
813
|
+
await cfRestartApp(options.app, context);
|
|
814
|
+
checkAbort(options.signal);
|
|
815
|
+
await retryRemoteSignal(options, context, sessionId, emit);
|
|
816
|
+
}
|
|
817
|
+
async function retryRemoteSignal(options, context, sessionId, emit) {
|
|
818
|
+
emit("signaling");
|
|
819
|
+
await updateSessionStatus(sessionId, "signaling");
|
|
820
|
+
const retrySignalResult = await cfSshOneShot(
|
|
821
|
+
options.app,
|
|
822
|
+
`kill -s USR1 $(pidof node)`,
|
|
823
|
+
context
|
|
824
|
+
);
|
|
825
|
+
if (retrySignalResult.exitCode === 0) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const detail = retrySignalResult.stderr.trim().length > 0 ? retrySignalResult.stderr.trim() : `exit code ${String(retrySignalResult.exitCode)}`;
|
|
829
|
+
throw new CfDebuggerError(
|
|
830
|
+
"USR1_SIGNAL_FAILED",
|
|
831
|
+
`Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,
|
|
832
|
+
retrySignalResult.stderr
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
async function waitAfterSignal(signal) {
|
|
836
|
+
await new Promise((resolve) => {
|
|
837
|
+
setTimeout(resolve, POST_USR1_DELAY_MS);
|
|
838
|
+
});
|
|
839
|
+
checkAbort(signal);
|
|
840
|
+
}
|
|
841
|
+
async function ensurePortAvailable(localPort) {
|
|
842
|
+
if (await isPortFree(localPort)) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
await killProcessOnPort(localPort);
|
|
846
|
+
await new Promise((resolve) => {
|
|
847
|
+
setTimeout(resolve, PORT_RECLAIM_DELAY_MS);
|
|
848
|
+
});
|
|
849
|
+
if (!await isPortFree(localPort)) {
|
|
850
|
+
throw new CfDebuggerError(
|
|
851
|
+
"PORT_UNAVAILABLE",
|
|
852
|
+
`Local port ${localPort.toString()} is in use and could not be reclaimed for the tunnel.`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async function openReadyTunnel(options, session, context, tunnelReadyTimeoutMs, onChild) {
|
|
857
|
+
await ensurePortAvailable(session.localPort);
|
|
858
|
+
const child = spawnSshTunnel(options.app, session.localPort, session.remotePort, context);
|
|
859
|
+
onChild(child);
|
|
860
|
+
if (child.pid !== void 0) {
|
|
861
|
+
await updateSessionPid(session.sessionId, child.pid);
|
|
862
|
+
}
|
|
863
|
+
const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);
|
|
864
|
+
checkAbort(options.signal);
|
|
865
|
+
if (!ready) {
|
|
866
|
+
throw new CfDebuggerError(
|
|
867
|
+
"TUNNEL_NOT_READY",
|
|
868
|
+
`SSH tunnel on port ${session.localPort.toString()} did not become ready within ${Math.round(tunnelReadyTimeoutMs / 1e3).toString()}s.`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
const listeningPid = await findListeningProcessId(session.localPort);
|
|
872
|
+
const activePid = listeningPid ?? child.pid ?? session.pid;
|
|
873
|
+
if (activePid !== session.pid) {
|
|
874
|
+
await updateSessionPid(session.sessionId, activePid);
|
|
875
|
+
}
|
|
876
|
+
return { child, activePid };
|
|
877
|
+
}
|
|
878
|
+
function attachTunnelEvents(child, markClosed, resolveExit, emit) {
|
|
879
|
+
child.on("close", (code) => {
|
|
880
|
+
markClosed();
|
|
881
|
+
resolveExit(code);
|
|
882
|
+
});
|
|
883
|
+
child.on("error", (err) => {
|
|
884
|
+
emit("error", err.message);
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function createHandle(session, emit, finalize, exitPromise) {
|
|
888
|
+
let disposePromise;
|
|
889
|
+
return {
|
|
890
|
+
session,
|
|
891
|
+
dispose: async () => {
|
|
892
|
+
disposePromise ??= (async () => {
|
|
893
|
+
emit("stopping");
|
|
894
|
+
await updateSessionStatus(session.sessionId, "stopping");
|
|
895
|
+
await finalize();
|
|
896
|
+
})();
|
|
897
|
+
await disposePromise;
|
|
898
|
+
},
|
|
899
|
+
waitForExit: async () => {
|
|
900
|
+
return await exitPromise;
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
async function startDebugger(options) {
|
|
905
|
+
const { email, password } = requireCredentials(options);
|
|
906
|
+
const apiEndpoint = resolveApiEndpoint(options.region, options.apiEndpoint);
|
|
907
|
+
const tunnelReadyTimeoutMs = options.tunnelReadyTimeoutMs ?? DEFAULT_TUNNEL_READY_TIMEOUT_MS;
|
|
908
|
+
const emit = (status, message) => {
|
|
909
|
+
options.onStatus?.(status, message);
|
|
910
|
+
};
|
|
911
|
+
checkAbort(options.signal);
|
|
912
|
+
await pruneAndCleanupOrphans();
|
|
913
|
+
const session = await registerSession(options, apiEndpoint);
|
|
757
914
|
const context = { cfHome: session.cfHomeDir };
|
|
758
915
|
let child;
|
|
759
916
|
let tunnelClosed = false;
|
|
760
|
-
let exitResolve
|
|
917
|
+
let exitResolve = (_code) => {
|
|
918
|
+
throw new Error("Exit resolver was used before initialization");
|
|
919
|
+
};
|
|
761
920
|
const exitPromise = new Promise((resolve) => {
|
|
762
921
|
exitResolve = resolve;
|
|
763
922
|
});
|
|
764
|
-
const cleanupFilesystem = async () => {
|
|
765
|
-
try {
|
|
766
|
-
await rm(session.cfHomeDir, { recursive: true, force: true });
|
|
767
|
-
} catch {
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
923
|
const finalize = async () => {
|
|
771
924
|
if (!tunnelClosed) {
|
|
772
925
|
tunnelClosed = true;
|
|
@@ -778,125 +931,37 @@ async function startDebugger(options) {
|
|
|
778
931
|
}, PORT_CLEANUP_DELAY_MS);
|
|
779
932
|
}
|
|
780
933
|
await removeSession(session.sessionId);
|
|
781
|
-
await cleanupFilesystem();
|
|
934
|
+
await cleanupFilesystem(session.cfHomeDir);
|
|
782
935
|
emit("stopped");
|
|
783
936
|
};
|
|
784
937
|
try {
|
|
785
938
|
await mkdir3(session.cfHomeDir, { recursive: true });
|
|
786
|
-
emit
|
|
787
|
-
await updateSessionStatus(session.sessionId, "logging-in");
|
|
788
|
-
await cfLogin(apiEndpoint, email, password, context);
|
|
789
|
-
checkAbort(options.signal);
|
|
790
|
-
emit("targeting");
|
|
791
|
-
await updateSessionStatus(session.sessionId, "targeting");
|
|
792
|
-
await cfTarget(options.org, options.space, context);
|
|
793
|
-
checkAbort(options.signal);
|
|
939
|
+
await loginAndTarget(options, apiEndpoint, email, password, context, session.sessionId, emit);
|
|
794
940
|
await killProcessOnPort(session.localPort);
|
|
795
941
|
await new Promise((resolve) => {
|
|
796
942
|
setTimeout(resolve, 200);
|
|
797
943
|
});
|
|
798
|
-
emit
|
|
799
|
-
await
|
|
800
|
-
const signalResult = await cfSshOneShot(
|
|
801
|
-
options.app,
|
|
802
|
-
`kill -s USR1 $(pidof node)`,
|
|
803
|
-
context
|
|
804
|
-
);
|
|
805
|
-
if (isSshDisabledError(signalResult.stderr)) {
|
|
806
|
-
const alreadyEnabled = await cfSshEnabled(options.app, context);
|
|
807
|
-
if (!alreadyEnabled) {
|
|
808
|
-
emit("ssh-enabling", "Enabling SSH on the app");
|
|
809
|
-
await updateSessionStatus(session.sessionId, "ssh-enabling");
|
|
810
|
-
await cfEnableSsh(options.app, context);
|
|
811
|
-
}
|
|
812
|
-
emit("ssh-restarting", "Restarting app so SSH becomes active");
|
|
813
|
-
await updateSessionStatus(session.sessionId, "ssh-restarting");
|
|
814
|
-
await cfRestartApp(options.app, context);
|
|
815
|
-
checkAbort(options.signal);
|
|
816
|
-
emit("signaling");
|
|
817
|
-
await updateSessionStatus(session.sessionId, "signaling");
|
|
818
|
-
const retrySignalResult = await cfSshOneShot(
|
|
819
|
-
options.app,
|
|
820
|
-
`kill -s USR1 $(pidof node)`,
|
|
821
|
-
context
|
|
822
|
-
);
|
|
823
|
-
if (retrySignalResult.exitCode !== 0) {
|
|
824
|
-
const detail = retrySignalResult.stderr.trim().length > 0 ? retrySignalResult.stderr.trim() : `exit code ${String(retrySignalResult.exitCode)}`;
|
|
825
|
-
throw new CfDebuggerError(
|
|
826
|
-
"USR1_SIGNAL_FAILED",
|
|
827
|
-
`Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,
|
|
828
|
-
retrySignalResult.stderr
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
} else if (signalResult.exitCode !== 0) {
|
|
832
|
-
const detail = signalResult.stderr.trim().length > 0 ? signalResult.stderr.trim() : `exit code ${String(signalResult.exitCode)}`;
|
|
833
|
-
throw new CfDebuggerError(
|
|
834
|
-
"USR1_SIGNAL_FAILED",
|
|
835
|
-
`Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,
|
|
836
|
-
signalResult.stderr
|
|
837
|
-
);
|
|
838
|
-
}
|
|
839
|
-
await new Promise((resolve) => {
|
|
840
|
-
setTimeout(resolve, POST_USR1_DELAY_MS);
|
|
841
|
-
});
|
|
842
|
-
checkAbort(options.signal);
|
|
944
|
+
await signalRemoteNode(options, context, session.sessionId, emit);
|
|
945
|
+
await waitAfterSignal(options.signal);
|
|
843
946
|
emit("tunneling");
|
|
844
947
|
await updateSessionStatus(session.sessionId, "tunneling");
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
);
|
|
948
|
+
const tunnel = await openReadyTunnel(
|
|
949
|
+
options,
|
|
950
|
+
session,
|
|
951
|
+
context,
|
|
952
|
+
tunnelReadyTimeoutMs,
|
|
953
|
+
(tunnelChild) => {
|
|
954
|
+
child = tunnelChild;
|
|
955
|
+
attachTunnelEvents(tunnelChild, () => {
|
|
956
|
+
tunnelClosed = true;
|
|
957
|
+
}, exitResolve, emit);
|
|
855
958
|
}
|
|
856
|
-
|
|
857
|
-
child =
|
|
858
|
-
if (child.pid !== void 0) {
|
|
859
|
-
await updateSessionPid(session.sessionId, child.pid);
|
|
860
|
-
}
|
|
861
|
-
child.on("close", (code) => {
|
|
862
|
-
tunnelClosed = true;
|
|
863
|
-
exitResolve?.(code);
|
|
864
|
-
});
|
|
865
|
-
child.on("error", (err) => {
|
|
866
|
-
emit("error", err.message);
|
|
867
|
-
});
|
|
868
|
-
const ready = await probeTunnelReady(session.localPort, tunnelReadyTimeoutMs);
|
|
869
|
-
checkAbort(options.signal);
|
|
870
|
-
if (!ready) {
|
|
871
|
-
throw new CfDebuggerError(
|
|
872
|
-
"TUNNEL_NOT_READY",
|
|
873
|
-
`SSH tunnel on port ${session.localPort.toString()} did not become ready within ${Math.round(tunnelReadyTimeoutMs / 1e3).toString()}s.`
|
|
874
|
-
);
|
|
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
|
-
}
|
|
959
|
+
);
|
|
960
|
+
child = tunnel.child;
|
|
881
961
|
emit("ready");
|
|
882
962
|
const readySession = await updateSessionStatus(session.sessionId, "ready");
|
|
883
|
-
const activeSession = readySession ?? { ...session, pid: activePid, status: "ready" };
|
|
884
|
-
|
|
885
|
-
const handle = {
|
|
886
|
-
session: activeSession,
|
|
887
|
-
dispose: async () => {
|
|
888
|
-
disposePromise ??= (async () => {
|
|
889
|
-
emit("stopping");
|
|
890
|
-
await updateSessionStatus(session.sessionId, "stopping");
|
|
891
|
-
await finalize();
|
|
892
|
-
})();
|
|
893
|
-
await disposePromise;
|
|
894
|
-
},
|
|
895
|
-
waitForExit: async () => {
|
|
896
|
-
return await exitPromise;
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
return handle;
|
|
963
|
+
const activeSession = readySession ?? { ...session, pid: tunnel.activePid, status: "ready" };
|
|
964
|
+
return createHandle(activeSession, emit, finalize, exitPromise);
|
|
900
965
|
} catch (err) {
|
|
901
966
|
const message = err instanceof Error ? err.message : String(err);
|
|
902
967
|
emit("error", message);
|
|
@@ -904,6 +969,16 @@ async function startDebugger(options) {
|
|
|
904
969
|
throw err;
|
|
905
970
|
}
|
|
906
971
|
}
|
|
972
|
+
async function cleanupFilesystem(cfHomeDir) {
|
|
973
|
+
try {
|
|
974
|
+
await rm(cfHomeDir, { recursive: true, force: true });
|
|
975
|
+
} catch {
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/debug-session/sessions.ts
|
|
980
|
+
import { rm as rm2 } from "fs/promises";
|
|
981
|
+
import process5 from "process";
|
|
907
982
|
async function stopDebugger(options) {
|
|
908
983
|
const sessions = await pruneAndCleanupOrphans();
|
|
909
984
|
let target;
|
|
@@ -916,7 +991,7 @@ async function stopDebugger(options) {
|
|
|
916
991
|
if (target === void 0) {
|
|
917
992
|
return void 0;
|
|
918
993
|
}
|
|
919
|
-
if (target.pid !==
|
|
994
|
+
if (target.pid !== process5.pid) {
|
|
920
995
|
try {
|
|
921
996
|
await terminatePidOrGroup(target.pid);
|
|
922
997
|
} catch {
|
|
@@ -927,7 +1002,7 @@ async function stopDebugger(options) {
|
|
|
927
1002
|
}, PORT_CLEANUP_DELAY_MS);
|
|
928
1003
|
const removed = await removeSession(target.sessionId);
|
|
929
1004
|
try {
|
|
930
|
-
await
|
|
1005
|
+
await rm2(target.cfHomeDir, { recursive: true, force: true });
|
|
931
1006
|
} catch {
|
|
932
1007
|
}
|
|
933
1008
|
return removed ?? target;
|