@saptools/cf-debugger 0.1.7 → 0.1.9

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/index.js CHANGED
@@ -18,12 +18,67 @@ var CfDebuggerError = class extends Error {
18
18
  import { mkdir as mkdir3, rm } from "fs/promises";
19
19
  import process4 from "process";
20
20
 
21
+ // src/cloud-foundry/commands.ts
22
+ import { execFile as execFile2 } from "child_process";
23
+ import { promisify as promisify2 } from "util";
24
+
25
+ // src/regions.ts
26
+ var REGION_API_ENDPOINTS = {
27
+ ae01: "https://api.cf.ae01.hana.ondemand.com",
28
+ ap01: "https://api.cf.ap01.hana.ondemand.com",
29
+ ap10: "https://api.cf.ap10.hana.ondemand.com",
30
+ ap11: "https://api.cf.ap11.hana.ondemand.com",
31
+ ap12: "https://api.cf.ap12.hana.ondemand.com",
32
+ ap20: "https://api.cf.ap20.hana.ondemand.com",
33
+ ap21: "https://api.cf.ap21.hana.ondemand.com",
34
+ ap30: "https://api.cf.ap30.hana.ondemand.com",
35
+ br10: "https://api.cf.br10.hana.ondemand.com",
36
+ br20: "https://api.cf.br20.hana.ondemand.com",
37
+ br30: "https://api.cf.br30.hana.ondemand.com",
38
+ ca10: "https://api.cf.ca10.hana.ondemand.com",
39
+ ca20: "https://api.cf.ca20.hana.ondemand.com",
40
+ ch20: "https://api.cf.ch20.hana.ondemand.com",
41
+ eu10: "https://api.cf.eu10.hana.ondemand.com",
42
+ eu11: "https://api.cf.eu11.hana.ondemand.com",
43
+ eu12: "https://api.cf.eu12.hana.ondemand.com",
44
+ eu20: "https://api.cf.eu20.hana.ondemand.com",
45
+ eu21: "https://api.cf.eu21.hana.ondemand.com",
46
+ eu30: "https://api.cf.eu30.hana.ondemand.com",
47
+ eu31: "https://api.cf.eu31.hana.ondemand.com",
48
+ in30: "https://api.cf.in30.hana.ondemand.com",
49
+ jp10: "https://api.cf.jp10.hana.ondemand.com",
50
+ jp20: "https://api.cf.jp20.hana.ondemand.com",
51
+ jp30: "https://api.cf.jp30.hana.ondemand.com",
52
+ kr30: "https://api.cf.kr30.hana.ondemand.com",
53
+ us10: "https://api.cf.us10.hana.ondemand.com",
54
+ us11: "https://api.cf.us11.hana.ondemand.com",
55
+ us20: "https://api.cf.us20.hana.ondemand.com",
56
+ us21: "https://api.cf.us21.hana.ondemand.com",
57
+ us30: "https://api.cf.us30.hana.ondemand.com",
58
+ us31: "https://api.cf.us31.hana.ondemand.com"
59
+ };
60
+ function resolveApiEndpoint(regionKey, override) {
61
+ if (override !== void 0 && override !== "") {
62
+ return override;
63
+ }
64
+ const endpoint = REGION_API_ENDPOINTS[regionKey];
65
+ if (endpoint === void 0) {
66
+ throw new Error(
67
+ `Unknown region key: ${regionKey}. Pass \`apiEndpoint\` explicitly to override.`
68
+ );
69
+ }
70
+ return endpoint;
71
+ }
72
+ function listKnownRegionKeys() {
73
+ return Object.keys(REGION_API_ENDPOINTS);
74
+ }
75
+
21
76
  // src/cloud-foundry/execute.ts
22
77
  import { execFile } from "child_process";
23
78
  import { promisify } from "util";
24
79
  var execFileAsync = promisify(execFile);
25
80
  var MAX_BUFFER = 16 * 1024 * 1024;
26
- var CF_CLI_TIMEOUT_MS = 3e4;
81
+ var DEFAULT_CF_COMMAND_TIMEOUT_MS = 18e4;
27
82
  var REDACTED_ARG = "<redacted>";
28
83
  function buildEnv(cfHome) {
29
84
  return { ...process.env, CF_HOME: cfHome };
@@ -46,7 +101,7 @@ function formatArgsForError(args) {
46
101
  }
47
102
  return args.map((arg, index) => index === 0 ? arg : REDACTED_ARG).join(" ");
48
103
  }
49
- async function runCf(args, context, timeoutMs = CF_CLI_TIMEOUT_MS) {
104
+ async function runCf(args, context, timeoutMs = DEFAULT_CF_COMMAND_TIMEOUT_MS) {
50
105
  try {
51
106
  const { stdout } = await execFileAsync(resolveBin(context), [...args], {
52
107
  env: buildEnv(context.cfHome),
@@ -68,8 +123,9 @@ async function runCf(args, context, timeoutMs = CF_CLI_TIMEOUT_MS) {
68
123
  }
69
124
 
70
125
  // src/cloud-foundry/commands.ts
71
- var CF_RESTART_TIMEOUT_MS = 12e4;
126
+ var execFileAsync2 = promisify2(execFile2);
72
127
  var CF_AUTH_MAX_ATTEMPTS = 3;
128
+ var CURRENT_TARGET_TIMEOUT_MS = 3e4;
73
129
  async function cfApi(apiEndpoint, context) {
74
130
  await runCf(["api", apiEndpoint], context);
75
131
  }
@@ -133,13 +189,74 @@ async function cfEnableSsh(appName, context) {
133
189
  }
134
190
  }
135
191
  async function cfRestartApp(appName, context) {
136
- await runCf(["restart", appName], context, CF_RESTART_TIMEOUT_MS);
192
+ await runCf(["restart", appName], context);
193
+ }
194
+ async function readCurrentCfTarget(options = {}) {
195
+ try {
196
+ const { stdout } = await execFileAsync2(options.command ?? process.env["CF_DEBUGGER_CF_BIN"] ?? "cf", ["target"], {
197
+ env: options.env ?? process.env,
198
+ maxBuffer: 16 * 1024 * 1024,
199
+ timeout: options.timeoutMs ?? CURRENT_TARGET_TIMEOUT_MS
200
+ });
201
+ return parseCurrentCfTarget(stdout);
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ throw new CfDebuggerError("CF_TARGET_FAILED", `cf target failed: ${message}`);
205
+ }
206
+ }
207
+ function parseCurrentCfTarget(stdout) {
208
+ const fields = parseTargetFields(stdout);
209
+ const apiEndpoint = fields.get("api endpoint");
210
+ const org = fields.get("org");
211
+ const space = fields.get("space");
212
+ if (!isPresent(apiEndpoint) || !isPresent(org) || !isPresent(space)) {
213
+ return void 0;
214
+ }
215
+ const region = regionKeyForApiEndpoint(apiEndpoint);
216
+ return {
217
+ apiEndpoint,
218
+ ...region === void 0 ? {} : { region },
219
+ org,
220
+ space
221
+ };
222
+ }
223
+ function requireCurrentCfRegion(target, instruction = "Pass --region explicitly.") {
224
+ if (target.region !== void 0) {
225
+ return target.region;
226
+ }
227
+ throw new CfDebuggerError(
228
+ "CF_TARGET_FAILED",
229
+ `Current CF API endpoint "${target.apiEndpoint}" does not match a known SAP region. ${instruction}`
230
+ );
231
+ }
232
+ function parseTargetFields(stdout) {
233
+ return new Map(
234
+ stdout.split("\n").map((line) => {
235
+ const separator = line.indexOf(":");
236
+ if (separator < 0) {
237
+ return void 0;
238
+ }
239
+ return [
240
+ line.slice(0, separator).trim().toLowerCase(),
241
+ line.slice(separator + 1).trim()
242
+ ];
243
+ }).filter((field) => field !== void 0)
244
+ );
245
+ }
246
+ function regionKeyForApiEndpoint(apiEndpoint) {
247
+ const normalized = normalizeApiEndpoint(apiEndpoint);
248
+ return listKnownRegionKeys().find((key) => normalizeApiEndpoint(resolveApiEndpoint(key)) === normalized);
249
+ }
250
+ function normalizeApiEndpoint(apiEndpoint) {
251
+ return apiEndpoint.trim().replace(/\/+$/, "").toLowerCase();
252
+ }
253
+ function isPresent(value) {
254
+ return value !== void 0 && value.length > 0;
137
255
  }
138
256
 
139
257
  // src/cloud-foundry/ssh.ts
140
258
  import { spawn } from "child_process";
141
- var CF_SSH_SIGNAL_TIMEOUT_MS = 15e3;
142
- async function cfSshOneShot(appName, command, context) {
259
+ async function cfSshOneShot(appName, command, context, timeoutMs = DEFAULT_CF_COMMAND_TIMEOUT_MS) {
143
260
  return await new Promise((resolve) => {
144
261
  const child = spawn(resolveBin(context), ["ssh", appName, "-c", command], {
145
262
  env: buildEnv(context.cfHome),
@@ -156,18 +273,20 @@ async function cfSshOneShot(appName, command, context) {
156
273
  child.kill();
157
274
  } catch {
158
275
  }
159
- resolve({ exitCode: null, stderr: stderrBuf });
160
- }, CF_SSH_SIGNAL_TIMEOUT_MS);
276
+ resolve({ exitCode: null, stderr: stderrBuf, timedOutAfterMs: timeoutMs });
277
+ }, timeoutMs);
161
278
  child.stderr.on("data", (data) => {
162
279
  stderrBuf += data.toString();
163
280
  });
164
- child.on("close", (code) => {
281
+ child.on("close", (code, signal) => {
165
282
  if (settled) {
166
283
  return;
167
284
  }
168
285
  settled = true;
169
286
  clearTimeout(timeout);
170
- resolve({ exitCode: code, stderr: stderrBuf });
287
+ resolve(
288
+ signal === null ? { exitCode: code, stderr: stderrBuf } : { exitCode: code, stderr: stderrBuf, signal }
289
+ );
171
290
  });
172
291
  child.on("error", (err) => {
173
292
  if (settled) {
@@ -214,13 +333,13 @@ function sessionCfHomeDir(sessionId) {
214
333
  }
215
334
 
216
335
  // src/network/ports.ts
217
- import { execFile as execFile2 } from "child_process";
336
+ import { execFile as execFile3 } from "child_process";
218
337
  import { createConnection, createServer } from "net";
219
- import { promisify as promisify2 } from "util";
220
- var execFileAsync2 = promisify2(execFile2);
338
+ import { promisify as promisify3 } from "util";
339
+ var execFileAsync3 = promisify3(execFile3);
221
340
  async function findListeningPidsWithNetstat(port) {
222
341
  try {
223
- const { stdout } = await execFileAsync2("netstat", ["-ano"]);
342
+ const { stdout } = await execFileAsync3("netstat", ["-ano"]);
224
343
  const pids = /* @__PURE__ */ new Set();
225
344
  for (const line of stdout.split("\n")) {
226
345
  if (!line.includes(`:${port.toString()}`) || !line.includes("LISTENING")) {
@@ -243,7 +362,7 @@ async function findListeningPidsWithNetstat(port) {
243
362
  }
244
363
  async function findListeningPidsWithLsof(port) {
245
364
  try {
246
- const { stdout } = await execFileAsync2("lsof", ["-nP", "-t", "-i", `tcp:${port.toString()}`, "-sTCP:LISTEN"]);
365
+ const { stdout } = await execFileAsync3("lsof", ["-nP", "-t", "-i", `tcp:${port.toString()}`, "-sTCP:LISTEN"]);
247
366
  return stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => Number.parseInt(line, 10)).filter((pid) => !Number.isNaN(pid));
248
367
  } catch {
249
368
  return [];
@@ -309,7 +428,7 @@ async function killProcessOnPort(port) {
309
428
  const pids = await findListeningPidsWithNetstat(port);
310
429
  for (const pid of pids) {
311
430
  try {
312
- await execFileAsync2("taskkill", ["/F", "/PID", pid.toString()]);
431
+ await execFileAsync3("taskkill", ["/F", "/PID", pid.toString()]);
313
432
  } catch {
314
433
  }
315
434
  }
@@ -318,7 +437,7 @@ async function killProcessOnPort(port) {
318
437
  return;
319
438
  }
320
439
  try {
321
- const { stdout } = await execFileAsync2("lsof", ["-t", "-i", `tcp:${portStr}`]);
440
+ const { stdout } = await execFileAsync3("lsof", ["-t", "-i", `tcp:${portStr}`]);
322
441
  const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
323
442
  for (const line of lines) {
324
443
  const pid = Number.parseInt(line, 10);
@@ -334,57 +453,6 @@ async function killProcessOnPort(port) {
334
453
  }
335
454
  }
336
455
 
337
- // src/regions.ts
338
- var REGION_API_ENDPOINTS = {
339
- ae01: "https://api.cf.ae01.hana.ondemand.com",
340
- ap01: "https://api.cf.ap01.hana.ondemand.com",
341
- ap10: "https://api.cf.ap10.hana.ondemand.com",
342
- ap11: "https://api.cf.ap11.hana.ondemand.com",
343
- ap12: "https://api.cf.ap12.hana.ondemand.com",
344
- ap20: "https://api.cf.ap20.hana.ondemand.com",
345
- ap21: "https://api.cf.ap21.hana.ondemand.com",
346
- ap30: "https://api.cf.ap30.hana.ondemand.com",
347
- br10: "https://api.cf.br10.hana.ondemand.com",
348
- br20: "https://api.cf.br20.hana.ondemand.com",
349
- br30: "https://api.cf.br30.hana.ondemand.com",
350
- ca10: "https://api.cf.ca10.hana.ondemand.com",
351
- ca20: "https://api.cf.ca20.hana.ondemand.com",
352
- ch20: "https://api.cf.ch20.hana.ondemand.com",
353
- eu10: "https://api.cf.eu10.hana.ondemand.com",
354
- eu11: "https://api.cf.eu11.hana.ondemand.com",
355
- eu12: "https://api.cf.eu12.hana.ondemand.com",
356
- eu20: "https://api.cf.eu20.hana.ondemand.com",
357
- eu21: "https://api.cf.eu21.hana.ondemand.com",
358
- eu30: "https://api.cf.eu30.hana.ondemand.com",
359
- eu31: "https://api.cf.eu31.hana.ondemand.com",
360
- in30: "https://api.cf.in30.hana.ondemand.com",
361
- jp10: "https://api.cf.jp10.hana.ondemand.com",
362
- jp20: "https://api.cf.jp20.hana.ondemand.com",
363
- jp30: "https://api.cf.jp30.hana.ondemand.com",
364
- kr30: "https://api.cf.kr30.hana.ondemand.com",
365
- us10: "https://api.cf.us10.hana.ondemand.com",
366
- us11: "https://api.cf.us11.hana.ondemand.com",
367
- us20: "https://api.cf.us20.hana.ondemand.com",
368
- us21: "https://api.cf.us21.hana.ondemand.com",
369
- us30: "https://api.cf.us30.hana.ondemand.com",
370
- us31: "https://api.cf.us31.hana.ondemand.com"
371
- };
372
- function resolveApiEndpoint(regionKey, override) {
373
- if (override !== void 0 && override !== "") {
374
- return override;
375
- }
376
- const endpoint = REGION_API_ENDPOINTS[regionKey];
377
- if (endpoint === void 0) {
378
- throw new Error(
379
- `Unknown region key: ${regionKey}. Pass \`apiEndpoint\` explicitly to override.`
380
- );
381
- }
382
- return endpoint;
383
- }
384
- function listKnownRegionKeys() {
385
- return Object.keys(REGION_API_ENDPOINTS);
386
- }
387
-
388
456
  // src/session-state/store.ts
389
457
  import { randomUUID } from "crypto";
390
458
  import { mkdir as mkdir2, readFile, rename, writeFile } from "fs/promises";
@@ -679,7 +747,7 @@ async function removeSession(sessionId) {
679
747
  }
680
748
 
681
749
  // src/debug-session/constants.ts
682
- var DEFAULT_TUNNEL_READY_TIMEOUT_MS = 3e4;
750
+ var DEFAULT_TUNNEL_READY_TIMEOUT_MS = 18e4;
683
751
  var POST_USR1_DELAY_MS = 300;
684
752
  var PORT_CLEANUP_DELAY_MS = 600;
685
753
  var CHILD_SIGTERM_GRACE_MS = 2e3;
@@ -742,6 +810,19 @@ async function killProcessGroupOrProc(child, timeoutMs = CHILD_SIGTERM_GRACE_MS)
742
810
  }
743
811
 
744
812
  // src/debug-session/start.ts
813
+ function signalFailureDetail(result) {
814
+ if (result.timedOutAfterMs !== void 0) {
815
+ return `timed out after ${(result.timedOutAfterMs / 1e3).toString()}s`;
816
+ }
817
+ const stderr = result.stderr.trim();
818
+ if (stderr.length > 0) {
819
+ return stderr;
820
+ }
821
+ if (result.signal !== void 0) {
822
+ return `terminated by signal ${result.signal}`;
823
+ }
824
+ return `exit code ${String(result.exitCode)}`;
825
+ }
745
826
  function checkAbort(signal) {
746
827
  if (signal?.aborted) {
747
828
  throw new CfDebuggerError("ABORTED", "Operation aborted by caller");
@@ -801,10 +882,9 @@ async function signalRemoteNode(options, context, sessionId, emit) {
801
882
  if (signalResult.exitCode === 0) {
802
883
  return;
803
884
  }
804
- const detail = signalResult.stderr.trim().length > 0 ? signalResult.stderr.trim() : `exit code ${String(signalResult.exitCode)}`;
805
885
  throw new CfDebuggerError(
806
886
  "USR1_SIGNAL_FAILED",
807
- `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${detail}`,
887
+ `Failed to send SIGUSR1 to the Node.js process on ${options.app}: ${signalFailureDetail(signalResult)}`,
808
888
  signalResult.stderr
809
889
  );
810
890
  }
@@ -831,10 +911,9 @@ async function retryRemoteSignal(options, context, sessionId, emit) {
831
911
  if (retrySignalResult.exitCode === 0) {
832
912
  return;
833
913
  }
834
- const detail = retrySignalResult.stderr.trim().length > 0 ? retrySignalResult.stderr.trim() : `exit code ${String(retrySignalResult.exitCode)}`;
835
914
  throw new CfDebuggerError(
836
915
  "USR1_SIGNAL_FAILED",
837
- `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${detail}`,
916
+ `Failed to send SIGUSR1 to the Node.js process on ${options.app} after enabling SSH: ${signalFailureDetail(retrySignalResult)}`,
838
917
  retrySignalResult.stderr
839
918
  );
840
919
  }
@@ -1036,6 +1115,9 @@ export {
1036
1115
  getSession,
1037
1116
  listKnownRegionKeys,
1038
1117
  listSessions,
1118
+ parseCurrentCfTarget,
1119
+ readCurrentCfTarget,
1120
+ requireCurrentCfRegion,
1039
1121
  resolveApiEndpoint,
1040
1122
  sessionKeyString,
1041
1123
  startDebugger,