@saptools/cf-debugger 0.1.8 → 0.1.10
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 +211 -89
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +129 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,60 @@
|
|
|
4
4
|
import process6 from "process";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
7
|
+
// src/cloud-foundry/commands.ts
|
|
8
|
+
import { execFile as execFile2 } from "child_process";
|
|
9
|
+
import { promisify as promisify2 } from "util";
|
|
10
|
+
|
|
11
|
+
// src/regions.ts
|
|
12
|
+
var REGION_API_ENDPOINTS = {
|
|
13
|
+
ae01: "https://api.cf.ae01.hana.ondemand.com",
|
|
14
|
+
ap01: "https://api.cf.ap01.hana.ondemand.com",
|
|
15
|
+
ap10: "https://api.cf.ap10.hana.ondemand.com",
|
|
16
|
+
ap11: "https://api.cf.ap11.hana.ondemand.com",
|
|
17
|
+
ap12: "https://api.cf.ap12.hana.ondemand.com",
|
|
18
|
+
ap20: "https://api.cf.ap20.hana.ondemand.com",
|
|
19
|
+
ap21: "https://api.cf.ap21.hana.ondemand.com",
|
|
20
|
+
ap30: "https://api.cf.ap30.hana.ondemand.com",
|
|
21
|
+
br10: "https://api.cf.br10.hana.ondemand.com",
|
|
22
|
+
br20: "https://api.cf.br20.hana.ondemand.com",
|
|
23
|
+
br30: "https://api.cf.br30.hana.ondemand.com",
|
|
24
|
+
ca10: "https://api.cf.ca10.hana.ondemand.com",
|
|
25
|
+
ca20: "https://api.cf.ca20.hana.ondemand.com",
|
|
26
|
+
ch20: "https://api.cf.ch20.hana.ondemand.com",
|
|
27
|
+
eu10: "https://api.cf.eu10.hana.ondemand.com",
|
|
28
|
+
eu11: "https://api.cf.eu11.hana.ondemand.com",
|
|
29
|
+
eu12: "https://api.cf.eu12.hana.ondemand.com",
|
|
30
|
+
eu20: "https://api.cf.eu20.hana.ondemand.com",
|
|
31
|
+
eu21: "https://api.cf.eu21.hana.ondemand.com",
|
|
32
|
+
eu30: "https://api.cf.eu30.hana.ondemand.com",
|
|
33
|
+
eu31: "https://api.cf.eu31.hana.ondemand.com",
|
|
34
|
+
in30: "https://api.cf.in30.hana.ondemand.com",
|
|
35
|
+
jp10: "https://api.cf.jp10.hana.ondemand.com",
|
|
36
|
+
jp20: "https://api.cf.jp20.hana.ondemand.com",
|
|
37
|
+
jp30: "https://api.cf.jp30.hana.ondemand.com",
|
|
38
|
+
kr30: "https://api.cf.kr30.hana.ondemand.com",
|
|
39
|
+
us10: "https://api.cf.us10.hana.ondemand.com",
|
|
40
|
+
us11: "https://api.cf.us11.hana.ondemand.com",
|
|
41
|
+
us20: "https://api.cf.us20.hana.ondemand.com",
|
|
42
|
+
us21: "https://api.cf.us21.hana.ondemand.com",
|
|
43
|
+
us30: "https://api.cf.us30.hana.ondemand.com",
|
|
44
|
+
us31: "https://api.cf.us31.hana.ondemand.com"
|
|
45
|
+
};
|
|
46
|
+
function resolveApiEndpoint(regionKey, override) {
|
|
47
|
+
if (override !== void 0 && override !== "") {
|
|
48
|
+
return override;
|
|
49
|
+
}
|
|
50
|
+
const endpoint = REGION_API_ENDPOINTS[regionKey];
|
|
51
|
+
if (endpoint === void 0) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Unknown region key: ${regionKey}. Pass \`apiEndpoint\` explicitly to override.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return endpoint;
|
|
57
|
+
}
|
|
58
|
+
function listKnownRegionKeys() {
|
|
59
|
+
return Object.keys(REGION_API_ENDPOINTS);
|
|
60
|
+
}
|
|
10
61
|
|
|
11
62
|
// src/types.ts
|
|
12
63
|
var CfDebuggerError = class extends Error {
|
|
@@ -72,7 +123,9 @@ async function runCf(args, context, timeoutMs = DEFAULT_CF_COMMAND_TIMEOUT_MS) {
|
|
|
72
123
|
}
|
|
73
124
|
|
|
74
125
|
// src/cloud-foundry/commands.ts
|
|
126
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
75
127
|
var CF_AUTH_MAX_ATTEMPTS = 3;
|
|
128
|
+
var CURRENT_TARGET_TIMEOUT_MS = 3e4;
|
|
76
129
|
async function cfApi(apiEndpoint, context) {
|
|
77
130
|
await runCf(["api", apiEndpoint], context);
|
|
78
131
|
}
|
|
@@ -138,6 +191,68 @@ async function cfEnableSsh(appName, context) {
|
|
|
138
191
|
async function cfRestartApp(appName, context) {
|
|
139
192
|
await runCf(["restart", appName], context);
|
|
140
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;
|
|
255
|
+
}
|
|
141
256
|
|
|
142
257
|
// src/cloud-foundry/ssh.ts
|
|
143
258
|
import { spawn } from "child_process";
|
|
@@ -197,6 +312,10 @@ function spawnSshTunnel(appName, localPort, remotePort, context) {
|
|
|
197
312
|
});
|
|
198
313
|
}
|
|
199
314
|
|
|
315
|
+
// src/debug-session/start.ts
|
|
316
|
+
import { mkdir as mkdir3, rm } from "fs/promises";
|
|
317
|
+
import process4 from "process";
|
|
318
|
+
|
|
200
319
|
// src/paths.ts
|
|
201
320
|
import { homedir } from "os";
|
|
202
321
|
import { join } from "path";
|
|
@@ -218,13 +337,13 @@ function sessionCfHomeDir(sessionId) {
|
|
|
218
337
|
}
|
|
219
338
|
|
|
220
339
|
// src/network/ports.ts
|
|
221
|
-
import { execFile as
|
|
340
|
+
import { execFile as execFile3 } from "child_process";
|
|
222
341
|
import { createConnection, createServer } from "net";
|
|
223
|
-
import { promisify as
|
|
224
|
-
var
|
|
342
|
+
import { promisify as promisify3 } from "util";
|
|
343
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
225
344
|
async function findListeningPidsWithNetstat(port) {
|
|
226
345
|
try {
|
|
227
|
-
const { stdout } = await
|
|
346
|
+
const { stdout } = await execFileAsync3("netstat", ["-ano"]);
|
|
228
347
|
const pids = /* @__PURE__ */ new Set();
|
|
229
348
|
for (const line of stdout.split("\n")) {
|
|
230
349
|
if (!line.includes(`:${port.toString()}`) || !line.includes("LISTENING")) {
|
|
@@ -247,7 +366,7 @@ async function findListeningPidsWithNetstat(port) {
|
|
|
247
366
|
}
|
|
248
367
|
async function findListeningPidsWithLsof(port) {
|
|
249
368
|
try {
|
|
250
|
-
const { stdout } = await
|
|
369
|
+
const { stdout } = await execFileAsync3("lsof", ["-nP", "-t", "-i", `tcp:${port.toString()}`, "-sTCP:LISTEN"]);
|
|
251
370
|
return stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => Number.parseInt(line, 10)).filter((pid) => !Number.isNaN(pid));
|
|
252
371
|
} catch {
|
|
253
372
|
return [];
|
|
@@ -313,7 +432,7 @@ async function killProcessOnPort(port) {
|
|
|
313
432
|
const pids = await findListeningPidsWithNetstat(port);
|
|
314
433
|
for (const pid of pids) {
|
|
315
434
|
try {
|
|
316
|
-
await
|
|
435
|
+
await execFileAsync3("taskkill", ["/F", "/PID", pid.toString()]);
|
|
317
436
|
} catch {
|
|
318
437
|
}
|
|
319
438
|
}
|
|
@@ -322,7 +441,7 @@ async function killProcessOnPort(port) {
|
|
|
322
441
|
return;
|
|
323
442
|
}
|
|
324
443
|
try {
|
|
325
|
-
const { stdout } = await
|
|
444
|
+
const { stdout } = await execFileAsync3("lsof", ["-t", "-i", `tcp:${portStr}`]);
|
|
326
445
|
const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
|
|
327
446
|
for (const line of lines) {
|
|
328
447
|
const pid = Number.parseInt(line, 10);
|
|
@@ -338,54 +457,6 @@ async function killProcessOnPort(port) {
|
|
|
338
457
|
}
|
|
339
458
|
}
|
|
340
459
|
|
|
341
|
-
// src/regions.ts
|
|
342
|
-
var REGION_API_ENDPOINTS = {
|
|
343
|
-
ae01: "https://api.cf.ae01.hana.ondemand.com",
|
|
344
|
-
ap01: "https://api.cf.ap01.hana.ondemand.com",
|
|
345
|
-
ap10: "https://api.cf.ap10.hana.ondemand.com",
|
|
346
|
-
ap11: "https://api.cf.ap11.hana.ondemand.com",
|
|
347
|
-
ap12: "https://api.cf.ap12.hana.ondemand.com",
|
|
348
|
-
ap20: "https://api.cf.ap20.hana.ondemand.com",
|
|
349
|
-
ap21: "https://api.cf.ap21.hana.ondemand.com",
|
|
350
|
-
ap30: "https://api.cf.ap30.hana.ondemand.com",
|
|
351
|
-
br10: "https://api.cf.br10.hana.ondemand.com",
|
|
352
|
-
br20: "https://api.cf.br20.hana.ondemand.com",
|
|
353
|
-
br30: "https://api.cf.br30.hana.ondemand.com",
|
|
354
|
-
ca10: "https://api.cf.ca10.hana.ondemand.com",
|
|
355
|
-
ca20: "https://api.cf.ca20.hana.ondemand.com",
|
|
356
|
-
ch20: "https://api.cf.ch20.hana.ondemand.com",
|
|
357
|
-
eu10: "https://api.cf.eu10.hana.ondemand.com",
|
|
358
|
-
eu11: "https://api.cf.eu11.hana.ondemand.com",
|
|
359
|
-
eu12: "https://api.cf.eu12.hana.ondemand.com",
|
|
360
|
-
eu20: "https://api.cf.eu20.hana.ondemand.com",
|
|
361
|
-
eu21: "https://api.cf.eu21.hana.ondemand.com",
|
|
362
|
-
eu30: "https://api.cf.eu30.hana.ondemand.com",
|
|
363
|
-
eu31: "https://api.cf.eu31.hana.ondemand.com",
|
|
364
|
-
in30: "https://api.cf.in30.hana.ondemand.com",
|
|
365
|
-
jp10: "https://api.cf.jp10.hana.ondemand.com",
|
|
366
|
-
jp20: "https://api.cf.jp20.hana.ondemand.com",
|
|
367
|
-
jp30: "https://api.cf.jp30.hana.ondemand.com",
|
|
368
|
-
kr30: "https://api.cf.kr30.hana.ondemand.com",
|
|
369
|
-
us10: "https://api.cf.us10.hana.ondemand.com",
|
|
370
|
-
us11: "https://api.cf.us11.hana.ondemand.com",
|
|
371
|
-
us20: "https://api.cf.us20.hana.ondemand.com",
|
|
372
|
-
us21: "https://api.cf.us21.hana.ondemand.com",
|
|
373
|
-
us30: "https://api.cf.us30.hana.ondemand.com",
|
|
374
|
-
us31: "https://api.cf.us31.hana.ondemand.com"
|
|
375
|
-
};
|
|
376
|
-
function resolveApiEndpoint(regionKey, override) {
|
|
377
|
-
if (override !== void 0 && override !== "") {
|
|
378
|
-
return override;
|
|
379
|
-
}
|
|
380
|
-
const endpoint = REGION_API_ENDPOINTS[regionKey];
|
|
381
|
-
if (endpoint === void 0) {
|
|
382
|
-
throw new Error(
|
|
383
|
-
`Unknown region key: ${regionKey}. Pass \`apiEndpoint\` explicitly to override.`
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
return endpoint;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
460
|
// src/session-state/store.ts
|
|
390
461
|
import { randomUUID } from "crypto";
|
|
391
462
|
import { mkdir as mkdir2, readFile, rename, writeFile } from "fs/promises";
|
|
@@ -1084,11 +1155,23 @@ function logStatus(verbose, status, message) {
|
|
|
1084
1155
|
`);
|
|
1085
1156
|
}
|
|
1086
1157
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1158
|
+
function mergeSelector(selector, opts) {
|
|
1159
|
+
if (selector === void 0) {
|
|
1160
|
+
return opts;
|
|
1161
|
+
}
|
|
1162
|
+
const parts = selector.split("/");
|
|
1163
|
+
if (parts.length === 4) {
|
|
1164
|
+
return { ...opts, region: opts.region ?? parts[0], org: opts.org ?? parts[1], space: opts.space ?? parts[2], app: opts.app ?? parts[3] };
|
|
1165
|
+
}
|
|
1166
|
+
if (parts.length === 1) {
|
|
1167
|
+
return { ...opts, app: opts.app ?? parts[0] };
|
|
1168
|
+
}
|
|
1169
|
+
throw new CfDebuggerError("UNSAFE_INPUT", "Invalid app selector format. Expected <app> or <region>/<org>/<space>/<app>.");
|
|
1170
|
+
}
|
|
1171
|
+
async function handleStart(selector, rawOpts) {
|
|
1172
|
+
const opts = mergeSelector(selector, rawOpts);
|
|
1173
|
+
const app = readRequiredOption(opts.app, "--app or selector");
|
|
1174
|
+
const key = await resolveSessionKey({ ...opts, app });
|
|
1092
1175
|
const verbose = opts.verbose ?? false;
|
|
1093
1176
|
const preferredPort = parseOptionalPort(opts.port);
|
|
1094
1177
|
const tunnelReadyTimeoutMs = parseOptionalTimeout(opts.timeout);
|
|
@@ -1109,9 +1192,9 @@ Aborting startup for ${app}...
|
|
|
1109
1192
|
let handle;
|
|
1110
1193
|
try {
|
|
1111
1194
|
handle = await startDebugger({
|
|
1112
|
-
region,
|
|
1113
|
-
org,
|
|
1114
|
-
space,
|
|
1195
|
+
region: key.region,
|
|
1196
|
+
org: key.org,
|
|
1197
|
+
space: key.space,
|
|
1115
1198
|
app,
|
|
1116
1199
|
verbose,
|
|
1117
1200
|
signal: abortController.signal,
|
|
@@ -1126,7 +1209,7 @@ Aborting startup for ${app}...
|
|
|
1126
1209
|
process6.off("SIGTERM", startupSigterm);
|
|
1127
1210
|
}
|
|
1128
1211
|
process6.stdout.write(
|
|
1129
|
-
`Debugger ready for ${app} (${region}/${org}/${space}).
|
|
1212
|
+
`Debugger ready for ${app} (${key.region}/${key.org}/${key.space}).
|
|
1130
1213
|
Local port: ${handle.session.localPort.toString()}
|
|
1131
1214
|
Remote port: ${handle.session.remotePort.toString()}
|
|
1132
1215
|
Session id: ${handle.session.sessionId}
|
|
@@ -1164,25 +1247,68 @@ Stopping debugger for ${app}...
|
|
|
1164
1247
|
await dispose();
|
|
1165
1248
|
process6.exit(code ?? 0);
|
|
1166
1249
|
}
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1250
|
+
function hasText(value) {
|
|
1251
|
+
return optionalText(value) !== void 0;
|
|
1252
|
+
}
|
|
1253
|
+
function optionalText(value) {
|
|
1254
|
+
const trimmed = value?.trim();
|
|
1255
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
1256
|
+
}
|
|
1257
|
+
function currentCfOptions() {
|
|
1258
|
+
const command = process6.env["CF_DEBUGGER_CF_BIN"];
|
|
1259
|
+
return command === void 0 ? void 0 : { command };
|
|
1260
|
+
}
|
|
1261
|
+
async function resolveSessionKey(opts) {
|
|
1262
|
+
const app = readRequiredOption(opts.app, "--app or selector");
|
|
1263
|
+
const region = optionalText(opts.region);
|
|
1264
|
+
const org = optionalText(opts.org);
|
|
1265
|
+
const space = optionalText(opts.space);
|
|
1266
|
+
if (region !== void 0 && org !== void 0 && space !== void 0) {
|
|
1169
1267
|
return {
|
|
1170
|
-
region
|
|
1171
|
-
org
|
|
1172
|
-
space
|
|
1173
|
-
app
|
|
1268
|
+
region,
|
|
1269
|
+
org,
|
|
1270
|
+
space,
|
|
1271
|
+
app
|
|
1174
1272
|
};
|
|
1175
1273
|
}
|
|
1176
|
-
|
|
1274
|
+
const current = await readCurrentCfTarget(currentCfOptions()).catch((error) => {
|
|
1275
|
+
throw new CfDebuggerError(
|
|
1276
|
+
"CF_TARGET_FAILED",
|
|
1277
|
+
"No current CF target found. Run `cf target -o <org> -s <space>` or pass --region/--org/--space.",
|
|
1278
|
+
error instanceof Error ? error.message : String(error)
|
|
1279
|
+
);
|
|
1280
|
+
});
|
|
1281
|
+
if (current === void 0) {
|
|
1282
|
+
throw new CfDebuggerError(
|
|
1283
|
+
"CF_TARGET_FAILED",
|
|
1284
|
+
"No current CF target found. Run `cf target -o <org> -s <space>` or pass --region/--org/--space."
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
return {
|
|
1288
|
+
region: region ?? requireCurrentCfRegion(current),
|
|
1289
|
+
org: org ?? current.org,
|
|
1290
|
+
space: space ?? current.space,
|
|
1291
|
+
app
|
|
1292
|
+
};
|
|
1177
1293
|
}
|
|
1178
|
-
async function
|
|
1294
|
+
async function resolveOptionalSessionKey(opts) {
|
|
1295
|
+
if (!hasText(opts.app)) {
|
|
1296
|
+
if (hasText(opts.region) || hasText(opts.org) || hasText(opts.space)) {
|
|
1297
|
+
readRequiredOption(opts.app, "--app");
|
|
1298
|
+
}
|
|
1299
|
+
return void 0;
|
|
1300
|
+
}
|
|
1301
|
+
return await resolveSessionKey(opts);
|
|
1302
|
+
}
|
|
1303
|
+
async function handleStop(selector, rawOpts) {
|
|
1304
|
+
const opts = mergeSelector(selector, rawOpts);
|
|
1179
1305
|
if (opts.all === true) {
|
|
1180
1306
|
const count = await stopAllDebuggers();
|
|
1181
1307
|
process6.stdout.write(`Stopped ${count.toString()} session(s).
|
|
1182
1308
|
`);
|
|
1183
1309
|
return;
|
|
1184
1310
|
}
|
|
1185
|
-
const key =
|
|
1311
|
+
const key = await resolveOptionalSessionKey(opts);
|
|
1186
1312
|
const result = await stopDebugger({
|
|
1187
1313
|
...opts.sessionId === void 0 ? {} : { sessionId: opts.sessionId },
|
|
1188
1314
|
...key === void 0 ? {} : { key }
|
|
@@ -1201,30 +1327,26 @@ async function handleList() {
|
|
|
1201
1327
|
process6.stdout.write(`${JSON.stringify(sessions, null, 2)}
|
|
1202
1328
|
`);
|
|
1203
1329
|
}
|
|
1204
|
-
async function handleStatus(
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
org: opts.org,
|
|
1208
|
-
space: opts.space,
|
|
1209
|
-
app: opts.app
|
|
1210
|
-
});
|
|
1330
|
+
async function handleStatus(selector, rawOpts) {
|
|
1331
|
+
const opts = mergeSelector(selector, rawOpts);
|
|
1332
|
+
const session = await getSession(await resolveSessionKey(opts));
|
|
1211
1333
|
process6.stdout.write(`${JSON.stringify(session ?? null, null, 2)}
|
|
1212
1334
|
`);
|
|
1213
1335
|
}
|
|
1214
1336
|
async function main(argv) {
|
|
1215
1337
|
const program = new Command();
|
|
1216
1338
|
program.name("cf-debugger").description("Open an SSH debug tunnel to a SAP BTP Cloud Foundry app's Node.js inspector");
|
|
1217
|
-
program.command("start").description("Open a debug tunnel for one app").
|
|
1218
|
-
await handleStart(opts);
|
|
1339
|
+
program.command("start").description("Open a debug tunnel for one app").argument("[selector]", "Optional app selector: `<app>` or `region/org/space/app`").option("--region <key>", "CF region key (default: current cf target)").option("--org <name>", "CF org name (default: current cf target)").option("--space <name>", "CF space name (default: current cf target)").option("--app <name>", "CF app name").option("--port <number>", "Preferred local port (auto-assigned if omitted)").option("--timeout <seconds>", "Tunnel-ready timeout in seconds (default: 180)").option("--verbose", "Print status transitions", false).action(async (selector, opts) => {
|
|
1340
|
+
await handleStart(selector, opts);
|
|
1219
1341
|
});
|
|
1220
|
-
program.command("stop").description("Stop one session (by key or id) or all sessions with --all").option("--region <key>").option("--org <name>").option("--space <name>").option("--app <name>").option("--session-id <id>").option("--all", "Stop every active session", false).action(async (opts) => {
|
|
1221
|
-
await handleStop(opts);
|
|
1342
|
+
program.command("stop").description("Stop one session (by key or id) or all sessions with --all").argument("[selector]", "Optional app selector: `<app>` or `region/org/space/app`").option("--region <key>").option("--org <name>").option("--space <name>").option("--app <name>").option("--session-id <id>").option("--all", "Stop every active session", false).action(async (selector, opts) => {
|
|
1343
|
+
await handleStop(selector, opts);
|
|
1222
1344
|
});
|
|
1223
1345
|
program.command("list").description("Print every active debugger session as JSON").action(async () => {
|
|
1224
1346
|
await handleList();
|
|
1225
1347
|
});
|
|
1226
|
-
program.command("status").description("Print one session by key as JSON (null if not active)").
|
|
1227
|
-
await handleStatus(opts);
|
|
1348
|
+
program.command("status").description("Print one session by key as JSON (null if not active)").argument("[selector]", "Optional app selector: `<app>` or `region/org/space/app`").option("--region <key>").option("--org <name>").option("--space <name>").option("--app <name>").action(async (selector, opts) => {
|
|
1349
|
+
await handleStatus(selector, opts);
|
|
1228
1350
|
});
|
|
1229
1351
|
await program.parseAsync([...argv]);
|
|
1230
1352
|
}
|