@saptools/cf-debugger 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -846,8 +846,91 @@ var NODE_INSPECTOR_SCRIPT = [
846
846
 
847
847
  // src/cloud-foundry/ssh.ts
848
848
  import { spawn } from "child_process";
849
- import nodeProcess2 from "process";
849
+ import nodeProcess3 from "process";
850
850
  import { StringDecoder } from "string_decoder";
851
+
852
+ // src/debug-session/process-identity.ts
853
+ import { execFile as execFile2 } from "child_process";
854
+ import { readFile } from "fs/promises";
855
+ import nodeProcess2 from "process";
856
+ import { promisify } from "util";
857
+ var execFileAsync = promisify(execFile2);
858
+ function errorCode(error) {
859
+ if (typeof error !== "object" || error === null) {
860
+ return void 0;
861
+ }
862
+ const code = Reflect.get(error, "code");
863
+ return typeof code === "string" ? code : void 0;
864
+ }
865
+ function parseLinuxProcessStartTime(statLine) {
866
+ const commandEnd = statLine.lastIndexOf(")");
867
+ if (commandEnd < 0) {
868
+ return void 0;
869
+ }
870
+ const fieldsAfterCommand = statLine.slice(commandEnd + 1).trim().split(/\s+/);
871
+ const startTime = fieldsAfterCommand[19];
872
+ return startTime !== void 0 && /^\d+$/.test(startTime) ? startTime : void 0;
873
+ }
874
+ function throwIfAborted(signal) {
875
+ if (signal?.aborted) {
876
+ throw new CfDebuggerError("ABORTED", "Process identity inspection was aborted.");
877
+ }
878
+ }
879
+ async function linuxProcessIdentity(pid, signal) {
880
+ try {
881
+ const statLine = await readFile(`/proc/${pid.toString()}/stat`, {
882
+ encoding: "utf8",
883
+ ...signal === void 0 ? {} : { signal }
884
+ });
885
+ const startTime = parseLinuxProcessStartTime(statLine);
886
+ return startTime === void 0 ? void 0 : `linux:${startTime}`;
887
+ } catch (error) {
888
+ throwIfAborted(signal);
889
+ if (errorCode(error) === "ENOENT") {
890
+ return void 0;
891
+ }
892
+ return void 0;
893
+ }
894
+ }
895
+ async function darwinProcessIdentity(pid, signal) {
896
+ try {
897
+ const { stdout } = await execFileAsync("ps", ["-p", pid.toString(), "-o", "lstart="], {
898
+ ...signal === void 0 ? {} : { signal },
899
+ timeout: 2e3
900
+ });
901
+ const startedAt = stdout.trim();
902
+ return startedAt.length === 0 ? void 0 : `darwin:${startedAt}`;
903
+ } catch {
904
+ throwIfAborted(signal);
905
+ return void 0;
906
+ }
907
+ }
908
+ async function readProcessIdentity(pid, signal) {
909
+ throwIfAborted(signal);
910
+ if (!Number.isSafeInteger(pid) || pid <= 0) {
911
+ return void 0;
912
+ }
913
+ if (nodeProcess2.platform === "linux") {
914
+ return await linuxProcessIdentity(pid, signal);
915
+ }
916
+ if (nodeProcess2.platform === "darwin") {
917
+ return await darwinProcessIdentity(pid, signal);
918
+ }
919
+ return void 0;
920
+ }
921
+ async function inspectProcessIdentity(pid, expected, signal) {
922
+ throwIfAborted(signal);
923
+ if (expected === void 0) {
924
+ return "match";
925
+ }
926
+ const current = await readProcessIdentity(pid, signal);
927
+ if (current === void 0) {
928
+ return "unavailable";
929
+ }
930
+ return current === expected ? "match" : "mismatch";
931
+ }
932
+
933
+ // src/cloud-foundry/ssh.ts
851
934
  var DEFAULT_MAX_OUTPUT_BYTES = 65536;
852
935
  var LIVE_LINE_LIMIT_BYTES = 65536;
853
936
  var MAX_REDACTION_OVERLAP_BYTES = 4096;
@@ -986,14 +1069,77 @@ function createSshExecutionState(context, outputLimit) {
986
1069
  redaction,
987
1070
  settled: false,
988
1071
  aborted: false,
989
- timedOut: false
1072
+ timedOut: false,
1073
+ terminationStarted: false
990
1074
  };
991
1075
  }
992
- function terminateSshExecution(child, state) {
993
- signalChild(child, "SIGTERM");
994
- state.forceKillTimer ??= setTimeout(() => {
995
- signalChild(child, "SIGKILL");
996
- }, 1e3);
1076
+ function childIsOpen(child) {
1077
+ return child.exitCode === null && child.signalCode === null;
1078
+ }
1079
+ async function captureSshChildIdentity(child) {
1080
+ if (nodeProcess3.platform === "win32" || child.pid === void 0) {
1081
+ return;
1082
+ }
1083
+ try {
1084
+ return await readProcessIdentity(child.pid);
1085
+ } catch {
1086
+ return;
1087
+ }
1088
+ }
1089
+ async function canSignalSshChild(child, childIdentity) {
1090
+ if (!childIsOpen(child) || child.pid === void 0) {
1091
+ return false;
1092
+ }
1093
+ if (nodeProcess3.platform === "win32") {
1094
+ return true;
1095
+ }
1096
+ const expectedIdentity = await childIdentity;
1097
+ if (expectedIdentity === void 0) {
1098
+ return false;
1099
+ }
1100
+ return await inspectProcessIdentity(child.pid, expectedIdentity) === "match" && childIsOpen(child);
1101
+ }
1102
+ function abandonUnverifiedSshChild(child) {
1103
+ try {
1104
+ child.stdout?.destroy();
1105
+ } catch {
1106
+ }
1107
+ try {
1108
+ child.stderr?.destroy();
1109
+ } catch {
1110
+ }
1111
+ try {
1112
+ child.unref();
1113
+ } catch {
1114
+ }
1115
+ }
1116
+ function terminateSshExecution(child, childIdentity, state, settle) {
1117
+ if (state.settled || state.terminationStarted) {
1118
+ return;
1119
+ }
1120
+ state.terminationStarted = true;
1121
+ const settleUnverified = () => {
1122
+ if (!state.settled) {
1123
+ abandonUnverifiedSshChild(child);
1124
+ settle(createResult(null, state));
1125
+ }
1126
+ };
1127
+ void (async () => {
1128
+ if (!await canSignalSshChild(child, childIdentity) || state.settled) {
1129
+ settleUnverified();
1130
+ return;
1131
+ }
1132
+ signalChild(child, "SIGTERM");
1133
+ state.forceKillTimer = setTimeout(() => {
1134
+ void (async () => {
1135
+ if (!await canSignalSshChild(child, childIdentity) || state.settled) {
1136
+ settleUnverified();
1137
+ return;
1138
+ }
1139
+ signalChild(child, "SIGKILL");
1140
+ })().catch(settleUnverified);
1141
+ }, 1e3);
1142
+ })().catch(settleUnverified);
997
1143
  }
998
1144
  function sshAbortError(context) {
999
1145
  if (context.deadlineAt !== void 0 && Date.now() >= context.deadlineAt) {
@@ -1023,16 +1169,16 @@ function createSshSettler(state, options, context, onAbort, resolve, reject) {
1023
1169
  resolve(state.timedOut ? { ...result, timedOutAfterMs: options.timeoutMs } : result);
1024
1170
  };
1025
1171
  }
1026
- function attachSshExecution(child, context, options, resolve, reject) {
1172
+ function attachSshExecution(child, childIdentity, context, options, resolve, reject) {
1027
1173
  const state = createSshExecutionState(context, options.maxOutputBytes);
1028
1174
  const onAbort = () => {
1029
1175
  state.aborted = true;
1030
- terminateSshExecution(child, state);
1176
+ terminateSshExecution(child, childIdentity, state, settle);
1031
1177
  };
1032
1178
  const settle = createSshSettler(state, options, context, onAbort, resolve, reject);
1033
1179
  state.timeoutTimer = setTimeout(() => {
1034
1180
  state.timedOut = true;
1035
- terminateSshExecution(child, state);
1181
+ terminateSshExecution(child, childIdentity, state, settle);
1036
1182
  }, options.timeoutMs);
1037
1183
  if (context.signal?.aborted) {
1038
1184
  onAbort();
@@ -1061,16 +1207,17 @@ function runSshOneShot(args, context, options) {
1061
1207
  return new Promise((resolve, reject) => {
1062
1208
  const child = spawn(resolveBin(context), [...args], {
1063
1209
  env: buildEnv(context.cfHome),
1064
- detached: nodeProcess2.platform !== "win32",
1210
+ detached: nodeProcess3.platform !== "win32",
1065
1211
  stdio: ["ignore", "pipe", "pipe"]
1066
1212
  });
1067
- attachSshExecution(child, context, options, resolve, reject);
1213
+ const childIdentity = captureSshChildIdentity(child);
1214
+ attachSshExecution(child, childIdentity, context, options, resolve, reject);
1068
1215
  });
1069
1216
  }
1070
1217
  function signalChild(child, signal) {
1071
- if (nodeProcess2.platform !== "win32" && child.pid !== void 0) {
1218
+ if (nodeProcess3.platform !== "win32" && child.pid !== void 0) {
1072
1219
  try {
1073
- nodeProcess2.kill(-child.pid, signal);
1220
+ nodeProcess3.kill(-child.pid, signal);
1074
1221
  return;
1075
1222
  } catch {
1076
1223
  }
@@ -1246,7 +1393,7 @@ function spawnSshTunnel(appName, localPort, remotePort, context, target = {}) {
1246
1393
  const args = buildCfSshArgs(appName, target, ["-N", "-L", tunnelArg]);
1247
1394
  const child = spawn(resolveBin(context), [...args], {
1248
1395
  env: buildEnv(context.cfHome),
1249
- detached: nodeProcess2.platform !== "win32",
1396
+ detached: nodeProcess3.platform !== "win32",
1250
1397
  stdio: ["ignore", "pipe", "pipe"]
1251
1398
  });
1252
1399
  attachTunnelCapture(child, context);
@@ -1269,87 +1416,6 @@ var CHILD_SIGTERM_GRACE_MS = 2e3;
1269
1416
  var CHILD_SIGKILL_GRACE_MS = 1e3;
1270
1417
  var PID_TERMINATION_POLL_MS = 100;
1271
1418
 
1272
- // src/debug-session/process-identity.ts
1273
- import { execFile as execFile2 } from "child_process";
1274
- import { readFile } from "fs/promises";
1275
- import nodeProcess3 from "process";
1276
- import { promisify } from "util";
1277
- var execFileAsync = promisify(execFile2);
1278
- function errorCode(error) {
1279
- if (typeof error !== "object" || error === null) {
1280
- return void 0;
1281
- }
1282
- const code = Reflect.get(error, "code");
1283
- return typeof code === "string" ? code : void 0;
1284
- }
1285
- function parseLinuxProcessStartTime(statLine) {
1286
- const commandEnd = statLine.lastIndexOf(")");
1287
- if (commandEnd < 0) {
1288
- return void 0;
1289
- }
1290
- const fieldsAfterCommand = statLine.slice(commandEnd + 1).trim().split(/\s+/);
1291
- const startTime = fieldsAfterCommand[19];
1292
- return startTime !== void 0 && /^\d+$/.test(startTime) ? startTime : void 0;
1293
- }
1294
- function throwIfAborted(signal) {
1295
- if (signal?.aborted) {
1296
- throw new CfDebuggerError("ABORTED", "Process identity inspection was aborted.");
1297
- }
1298
- }
1299
- async function linuxProcessIdentity(pid, signal) {
1300
- try {
1301
- const statLine = await readFile(`/proc/${pid.toString()}/stat`, {
1302
- encoding: "utf8",
1303
- ...signal === void 0 ? {} : { signal }
1304
- });
1305
- const startTime = parseLinuxProcessStartTime(statLine);
1306
- return startTime === void 0 ? void 0 : `linux:${startTime}`;
1307
- } catch (error) {
1308
- throwIfAborted(signal);
1309
- if (errorCode(error) === "ENOENT") {
1310
- return void 0;
1311
- }
1312
- return void 0;
1313
- }
1314
- }
1315
- async function darwinProcessIdentity(pid, signal) {
1316
- try {
1317
- const { stdout } = await execFileAsync("ps", ["-p", pid.toString(), "-o", "lstart="], {
1318
- ...signal === void 0 ? {} : { signal },
1319
- timeout: 2e3
1320
- });
1321
- const startedAt = stdout.trim();
1322
- return startedAt.length === 0 ? void 0 : `darwin:${startedAt}`;
1323
- } catch {
1324
- throwIfAborted(signal);
1325
- return void 0;
1326
- }
1327
- }
1328
- async function readProcessIdentity(pid, signal) {
1329
- throwIfAborted(signal);
1330
- if (!Number.isSafeInteger(pid) || pid <= 0) {
1331
- return void 0;
1332
- }
1333
- if (nodeProcess3.platform === "linux") {
1334
- return await linuxProcessIdentity(pid, signal);
1335
- }
1336
- if (nodeProcess3.platform === "darwin") {
1337
- return await darwinProcessIdentity(pid, signal);
1338
- }
1339
- return void 0;
1340
- }
1341
- async function inspectProcessIdentity(pid, expected, signal) {
1342
- throwIfAborted(signal);
1343
- if (expected === void 0) {
1344
- return "match";
1345
- }
1346
- const current = await readProcessIdentity(pid, signal);
1347
- if (current === void 0) {
1348
- return "unavailable";
1349
- }
1350
- return current === expected ? "match" : "mismatch";
1351
- }
1352
-
1353
1419
  // src/lock.ts
1354
1420
  import { randomUUID } from "crypto";
1355
1421
  import { chmod, mkdir, open, readFile as readFile2, stat, unlink } from "fs/promises";
@@ -2962,7 +3028,7 @@ async function tryRemoveOwnedSessionCfHome(sessionId, candidate) {
2962
3028
 
2963
3029
  // src/debug-session/lifecycle.ts
2964
3030
  var handleLifecycles = /* @__PURE__ */ new WeakMap();
2965
- function childIsOpen(child) {
3031
+ function childIsOpen2(child) {
2966
3032
  return child.exitCode === null && child.signalCode === null;
2967
3033
  }
2968
3034
  function signalExitCode(signal) {
@@ -3094,12 +3160,12 @@ function createTunnelLifecycle(session, emit) {
3094
3160
  const verifyBeforeSignal = async (signal) => {
3095
3161
  const tunnelChild = child;
3096
3162
  const childPid = tunnelChild?.pid;
3097
- if (tunnelChild === void 0 || childPid === void 0 || !childIsOpen(tunnelChild)) {
3163
+ if (tunnelChild === void 0 || childPid === void 0 || !childIsOpen2(tunnelChild)) {
3098
3164
  return false;
3099
3165
  }
3100
3166
  const expectedIdentity = await childIdentity;
3101
3167
  if (expectedIdentity !== void 0) {
3102
- return await inspectProcessIdentity(childPid, expectedIdentity) === "match" && childIsOpen(tunnelChild);
3168
+ return await inspectProcessIdentity(childPid, expectedIdentity) === "match" && childIsOpen2(tunnelChild);
3103
3169
  }
3104
3170
  if (signal === "SIGKILL") {
3105
3171
  return await expectedTunnelStillOwned(session, tunnelChild) === true;
@@ -3126,7 +3192,7 @@ function createTunnelLifecycle(session, emit) {
3126
3192
  return {
3127
3193
  exitPromise,
3128
3194
  assertRunning: () => {
3129
- if (child === void 0 || !childIsOpen(child) || childFailed) {
3195
+ if (child === void 0 || !childIsOpen2(child) || childFailed) {
3130
3196
  throw new CfDebuggerError(
3131
3197
  "TUNNEL_NOT_READY",
3132
3198
  `SSH tunnel for session ${session.sessionId} exited before readiness could be committed.`,
@@ -4731,4 +4797,4 @@ export {
4731
4797
  listSessions,
4732
4798
  getSession
4733
4799
  };
4734
- //# sourceMappingURL=chunk-ZWAQD7KL.js.map
4800
+ //# sourceMappingURL=chunk-AKQR4Y74.js.map