simplemdg-dev-cli 1.5.1 → 2.4.4

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.
Files changed (140) hide show
  1. package/README.md +65 -243
  2. package/USER_GUIDE.md +55 -249
  3. package/dist/commands/cds.command.js +69 -60
  4. package/dist/commands/cds.command.js.map +1 -1
  5. package/dist/commands/cf-db.command.d.ts +2 -0
  6. package/dist/commands/cf-db.command.js +606 -0
  7. package/dist/commands/cf-db.command.js.map +1 -0
  8. package/dist/commands/cf.command.js +1625 -198
  9. package/dist/commands/cf.command.js.map +1 -1
  10. package/dist/commands/gitlab.command.d.ts +2 -0
  11. package/dist/commands/gitlab.command.js +351 -0
  12. package/dist/commands/gitlab.command.js.map +1 -0
  13. package/dist/commands/npmrc.command.js +50 -44
  14. package/dist/commands/npmrc.command.js.map +1 -1
  15. package/dist/core/cache.d.ts +1 -1
  16. package/dist/core/cache.js +58 -31
  17. package/dist/core/cache.js.map +1 -1
  18. package/dist/core/cds.js +32 -22
  19. package/dist/core/cds.js.map +1 -1
  20. package/dist/core/cf-env-parser.d.ts +1 -1
  21. package/dist/core/cf-env-parser.js +4 -1
  22. package/dist/core/cf-env-parser.js.map +1 -1
  23. package/dist/core/cf.d.ts +1 -1
  24. package/dist/core/cf.js +46 -31
  25. package/dist/core/cf.js.map +1 -1
  26. package/dist/core/db/db-btp.d.ts +48 -0
  27. package/dist/core/db/db-btp.js +162 -0
  28. package/dist/core/db/db-btp.js.map +1 -0
  29. package/dist/core/db/db-cache.d.ts +35 -0
  30. package/dist/core/db/db-cache.js +164 -0
  31. package/dist/core/db/db-cache.js.map +1 -0
  32. package/dist/core/db/db-connection.d.ts +22 -0
  33. package/dist/core/db/db-connection.js +73 -0
  34. package/dist/core/db/db-connection.js.map +1 -0
  35. package/dist/core/db/db-crypto.d.ts +3 -0
  36. package/dist/core/db/db-crypto.js +54 -0
  37. package/dist/core/db/db-crypto.js.map +1 -0
  38. package/dist/core/db/db-hana-adapter.d.ts +32 -0
  39. package/dist/core/db/db-hana-adapter.js +243 -0
  40. package/dist/core/db/db-hana-adapter.js.map +1 -0
  41. package/dist/core/db/db-metadata.d.ts +25 -0
  42. package/dist/core/db/db-metadata.js +150 -0
  43. package/dist/core/db/db-metadata.js.map +1 -0
  44. package/dist/core/db/db-postgres-adapter.d.ts +30 -0
  45. package/dist/core/db/db-postgres-adapter.js +245 -0
  46. package/dist/core/db/db-postgres-adapter.js.map +1 -0
  47. package/dist/core/db/db-query-files.d.ts +20 -0
  48. package/dist/core/db/db-query-files.js +106 -0
  49. package/dist/core/db/db-query-files.js.map +1 -0
  50. package/dist/core/db/db-query-history.d.ts +5 -0
  51. package/dist/core/db/db-query-history.js +49 -0
  52. package/dist/core/db/db-query-history.js.map +1 -0
  53. package/dist/core/db/db-row.d.ts +22 -0
  54. package/dist/core/db/db-row.js +70 -0
  55. package/dist/core/db/db-row.js.map +1 -0
  56. package/dist/core/db/db-studio-html.d.ts +4 -0
  57. package/dist/core/db/db-studio-html.js +437 -0
  58. package/dist/core/db/db-studio-html.js.map +1 -0
  59. package/dist/core/db/db-studio-server.d.ts +11 -0
  60. package/dist/core/db/db-studio-server.js +465 -0
  61. package/dist/core/db/db-studio-server.js.map +1 -0
  62. package/dist/core/db/db-types.d.ts +174 -0
  63. package/dist/core/db/db-types.js +3 -0
  64. package/dist/core/db/db-types.js.map +1 -0
  65. package/dist/core/db/db-vcap-parser.d.ts +7 -0
  66. package/dist/core/db/db-vcap-parser.js +137 -0
  67. package/dist/core/db/db-vcap-parser.js.map +1 -0
  68. package/dist/core/doctor.d.ts +1 -1
  69. package/dist/core/doctor.js +14 -8
  70. package/dist/core/doctor.js.map +1 -1
  71. package/dist/core/guide.js +31 -26
  72. package/dist/core/guide.js.map +1 -1
  73. package/dist/core/install.d.ts +1 -1
  74. package/dist/core/install.js +17 -11
  75. package/dist/core/install.js.map +1 -1
  76. package/dist/core/navigator.d.ts +17 -0
  77. package/dist/core/navigator.js +140 -0
  78. package/dist/core/navigator.js.map +1 -0
  79. package/dist/core/npmrc.js +29 -16
  80. package/dist/core/npmrc.js.map +1 -1
  81. package/dist/core/process.js +11 -6
  82. package/dist/core/process.js.map +1 -1
  83. package/dist/core/prompts.js +16 -8
  84. package/dist/core/prompts.js.map +1 -1
  85. package/dist/core/repository.d.ts +1 -1
  86. package/dist/core/repository.js +16 -9
  87. package/dist/core/repository.js.map +1 -1
  88. package/dist/core/scanner.d.ts +1 -1
  89. package/dist/core/scanner.js +13 -7
  90. package/dist/core/scanner.js.map +1 -1
  91. package/dist/core/tooling.d.ts +28 -0
  92. package/dist/core/tooling.js +168 -0
  93. package/dist/core/tooling.js.map +1 -0
  94. package/dist/core/types.js +2 -1
  95. package/dist/core/version-conflict.d.ts +2 -2
  96. package/dist/core/version-conflict.js +11 -6
  97. package/dist/core/version-conflict.js.map +1 -1
  98. package/dist/index.js +65 -48
  99. package/dist/index.js.map +1 -1
  100. package/dist/types-local.js +2 -1
  101. package/package.json +12 -6
  102. package/src/commands/cds.command.ts +529 -0
  103. package/src/commands/cf-db.command.ts +636 -0
  104. package/src/commands/cf.command.ts +3345 -0
  105. package/src/commands/gitlab.command.ts +373 -0
  106. package/src/commands/npmrc.command.ts +581 -0
  107. package/src/core/cache.ts +332 -0
  108. package/src/core/cds.ts +278 -0
  109. package/src/core/cf-env-parser.ts +131 -0
  110. package/src/core/cf.ts +271 -0
  111. package/src/core/db/db-btp.ts +207 -0
  112. package/src/core/db/db-cache.ts +215 -0
  113. package/src/core/db/db-connection.ts +79 -0
  114. package/src/core/db/db-crypto.ts +53 -0
  115. package/src/core/db/db-hana-adapter.ts +294 -0
  116. package/src/core/db/db-metadata.ts +174 -0
  117. package/src/core/db/db-postgres-adapter.ts +275 -0
  118. package/src/core/db/db-query-files.ts +130 -0
  119. package/src/core/db/db-query-history.ts +53 -0
  120. package/src/core/db/db-row.ts +93 -0
  121. package/src/core/db/db-studio-html.ts +439 -0
  122. package/src/core/db/db-studio-server.ts +559 -0
  123. package/src/core/db/db-types.ts +195 -0
  124. package/src/core/db/db-vcap-parser.ts +182 -0
  125. package/src/core/doctor.ts +70 -0
  126. package/src/core/guide.ts +261 -0
  127. package/src/core/install.ts +91 -0
  128. package/src/core/navigator.ts +164 -0
  129. package/src/core/npmrc.ts +171 -0
  130. package/src/core/process.ts +75 -0
  131. package/src/core/prompts.ts +225 -0
  132. package/src/core/repository.ts +36 -0
  133. package/src/core/scanner.ts +41 -0
  134. package/src/core/tooling.ts +207 -0
  135. package/src/core/types.ts +152 -0
  136. package/src/core/version-conflict.ts +46 -0
  137. package/src/index.ts +460 -0
  138. package/src/types/external.d.ts +3 -0
  139. package/src/types-local.ts +11 -0
  140. package/tsconfig.json +17 -0
@@ -1,28 +1,39 @@
1
- import path from "node:path";
2
- import { spawn } from "node:child_process";
3
- import nodeFs from "node:fs";
4
- import fs from "fs-extra";
5
- import chalk from "chalk";
6
- import prompts from "prompts";
7
- import { authenticateCloudFoundry, buildCloudFoundryTargetKey, listCloudFoundryApps, inferCloudFoundryRegionFromApiEndpoint, listCloudFoundryOrganizations, listCloudFoundrySpaces, readCloudFoundryTarget, scanCloudFoundryOrganizationsAcrossRegions, setCloudFoundryApiEndpoint, targetCloudFoundryOrg, targetCloudFoundrySpace, } from "../core/cf.js";
8
- import { parseCloudFoundryEnvironment } from "../core/cf-env-parser.js";
9
- import { readCache, rememberCloudFoundryApps, rememberCloudFoundryLoginProfile, rememberCloudFoundryOrgEntries, rememberEnvironmentFileName, rememberSelectedApp, } from "../core/cache.js";
10
- import { runCommand, runCommandInherit } from "../core/process.js";
11
- import { resolveRepositoryPath } from "../core/repository.js";
12
- import { searchableSelectChoice, selectFromHistoryOrInput } from "../core/prompts.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerCloudFoundryCommands = registerCloudFoundryCommands;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ const node_net_1 = __importDefault(require("node:net"));
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const fs_extra_1 = __importDefault(require("fs-extra"));
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const cf_db_command_1 = require("./cf-db.command");
15
+ const prompts_1 = __importDefault(require("prompts"));
16
+ const cf_1 = require("../core/cf");
17
+ const cf_env_parser_1 = require("../core/cf-env-parser");
18
+ const cache_1 = require("../core/cache");
19
+ const process_1 = require("../core/process");
20
+ const repository_1 = require("../core/repository");
21
+ const prompts_2 = require("../core/prompts");
22
+ const tooling_1 = require("../core/tooling");
13
23
  function validateRequired(value) {
14
24
  return value.trim() ? true : "Value is required";
15
25
  }
16
26
  async function ensureCloudFoundrySessionFromCache() {
17
- const target = await readCloudFoundryTarget();
27
+ await (0, tooling_1.ensureExternalTool)("cf");
28
+ const target = await (0, cf_1.readCloudFoundryTarget)();
18
29
  if (target.apiEndpoint && target.user) {
19
30
  return target;
20
31
  }
21
- const cache = await readCache();
32
+ const cache = await (0, cache_1.readCache)();
22
33
  const profilesWithPassword = cache.cloudFoundry.loginProfiles.filter((profile) => profile.password?.trim());
23
34
  if (!profilesWithPassword.length) {
24
- console.log(chalk.yellow("You are not logged in to Cloud Foundry yet and no cached password was found."));
25
- console.log(chalk.gray("Run smdg cf login once and choose to save the password for automatic re-login."));
35
+ console.log(chalk_1.default.yellow("You are not logged in to Cloud Foundry yet and no cached password was found."));
36
+ console.log(chalk_1.default.gray("Run smdg cf login once and choose to save the password for automatic re-login."));
26
37
  throw new Error("Cloud Foundry login is required");
27
38
  }
28
39
  const preferredProfiles = target.apiEndpoint
@@ -33,22 +44,22 @@ async function ensureCloudFoundrySessionFromCache() {
33
44
  : profilesWithPassword;
34
45
  const selectedProfileIndex = preferredProfiles.length === 1
35
46
  ? "0"
36
- : await searchableSelectChoice({
47
+ : await (0, prompts_2.searchableSelectChoice)({
37
48
  message: "Select cached CF login profile for automatic re-login",
38
49
  choices: preferredProfiles.map((profile, index) => ({
39
- title: `${profile.username} · ${profile.org}${profile.space ? `/${profile.space}` : ""} · ${inferCloudFoundryRegionFromApiEndpoint(profile.apiEndpoint)}`,
50
+ title: `${profile.username} · ${profile.org}${profile.space ? `/${profile.space}` : ""} · ${(0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(profile.apiEndpoint)}`,
40
51
  value: String(index),
41
52
  })),
42
53
  allowCustomValue: false,
43
54
  });
44
55
  const profile = preferredProfiles[Number(selectedProfileIndex)] ?? preferredProfiles[0];
45
- console.log(chalk.gray(`Auto login CF: ${profile.username} · ${inferCloudFoundryRegionFromApiEndpoint(profile.apiEndpoint)} · ${profile.org}${profile.space ? `/${profile.space}` : ""}`));
46
- const apiExitCode = await setCloudFoundryApiEndpoint(profile.apiEndpoint);
56
+ console.log(chalk_1.default.gray(`Auto login CF: ${profile.username} · ${(0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(profile.apiEndpoint)} · ${profile.org}${profile.space ? `/${profile.space}` : ""}`));
57
+ const apiExitCode = await (0, cf_1.setCloudFoundryApiEndpoint)(profile.apiEndpoint);
47
58
  if (apiExitCode !== 0) {
48
59
  process.exitCode = apiExitCode;
49
60
  throw new Error("CF api target failed");
50
61
  }
51
- const authExitCode = await authenticateCloudFoundry({
62
+ const authExitCode = await (0, cf_1.authenticateCloudFoundry)({
52
63
  username: profile.username,
53
64
  password: profile.password,
54
65
  });
@@ -56,23 +67,88 @@ async function ensureCloudFoundrySessionFromCache() {
56
67
  process.exitCode = authExitCode;
57
68
  throw new Error("CF automatic login failed. Run smdg cf login and update the cached password.");
58
69
  }
59
- const orgExitCode = await targetCloudFoundryOrg(profile.org);
70
+ const orgExitCode = await (0, cf_1.targetCloudFoundryOrg)(profile.org);
60
71
  if (orgExitCode !== 0) {
61
72
  process.exitCode = orgExitCode;
62
73
  throw new Error("CF org target failed");
63
74
  }
64
75
  if (profile.space) {
65
- const spaceExitCode = await targetCloudFoundrySpace(profile.space);
76
+ const spaceExitCode = await (0, cf_1.targetCloudFoundrySpace)(profile.space);
66
77
  if (spaceExitCode !== 0) {
67
78
  process.exitCode = spaceExitCode;
68
79
  throw new Error("CF space target failed");
69
80
  }
70
81
  }
71
- await rememberCloudFoundryLoginProfile({
82
+ await (0, cache_1.rememberCloudFoundryLoginProfile)({
72
83
  ...profile,
73
84
  updatedAt: new Date().toISOString(),
74
85
  });
75
- return readCloudFoundryTarget();
86
+ return (0, cf_1.readCloudFoundryTarget)();
87
+ }
88
+ function sortCloudFoundryProfilesForEndpoint(options) {
89
+ const profilesWithPassword = options.profiles.filter((profile) => profile.password?.trim());
90
+ return [
91
+ ...profilesWithPassword.filter((profile) => profile.apiEndpoint === options.apiEndpoint && profile.org === options.preferredOrg),
92
+ ...profilesWithPassword.filter((profile) => profile.apiEndpoint === options.apiEndpoint && profile.org !== options.preferredOrg),
93
+ ...profilesWithPassword.filter((profile) => profile.apiEndpoint !== options.apiEndpoint && profile.org === options.preferredOrg),
94
+ ...profilesWithPassword.filter((profile) => profile.apiEndpoint !== options.apiEndpoint && profile.org !== options.preferredOrg),
95
+ ].filter((profile, index, array) => {
96
+ return array.findIndex((item) => {
97
+ return item.apiEndpoint === profile.apiEndpoint
98
+ && item.username === profile.username
99
+ && item.password === profile.password
100
+ && item.org === profile.org
101
+ && item.space === profile.space;
102
+ }) === index;
103
+ });
104
+ }
105
+ async function ensureCloudFoundryAuthenticatedForApiEndpoint(options) {
106
+ const apiExitCode = await (0, cf_1.setCloudFoundryApiEndpoint)(options.apiEndpoint);
107
+ if (apiExitCode !== 0) {
108
+ process.exitCode = apiExitCode;
109
+ throw new Error(`Cannot set CF API endpoint: ${options.apiEndpoint}`);
110
+ }
111
+ const orgsCheck = await (0, process_1.runCommand)("cf", ["orgs"]);
112
+ if (orgsCheck.exitCode === 0) {
113
+ return undefined;
114
+ }
115
+ const cache = await (0, cache_1.readCache)();
116
+ const profiles = sortCloudFoundryProfilesForEndpoint({
117
+ profiles: cache.cloudFoundry.loginProfiles,
118
+ apiEndpoint: options.apiEndpoint,
119
+ preferredOrg: options.preferredOrg,
120
+ });
121
+ if (!profiles.length) {
122
+ console.log(chalk_1.default.yellow(`Not logged in to ${(0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(options.apiEndpoint)} and no cached password was found for automatic login.`));
123
+ console.log(chalk_1.default.gray("Run smdg cf login once, choose to save password, then retry this command."));
124
+ throw new Error("Cloud Foundry automatic login is required");
125
+ }
126
+ let lastError = orgsCheck.stderr || orgsCheck.stdout || "cf orgs failed";
127
+ for (const profile of profiles) {
128
+ console.log(chalk_1.default.gray(`Auto auth CF ${(0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(options.apiEndpoint)} as ${profile.username}...`));
129
+ const authExitCode = await (0, cf_1.authenticateCloudFoundry)({
130
+ username: profile.username,
131
+ password: profile.password,
132
+ });
133
+ if (authExitCode !== 0) {
134
+ lastError = `cf auth failed for ${profile.username}`;
135
+ continue;
136
+ }
137
+ const nextOrgsCheck = await (0, process_1.runCommand)("cf", ["orgs"]);
138
+ if (nextOrgsCheck.exitCode === 0) {
139
+ const updatedProfile = {
140
+ ...profile,
141
+ apiEndpoint: options.apiEndpoint,
142
+ org: options.preferredOrg || profile.org,
143
+ space: options.preferredSpace || profile.space,
144
+ updatedAt: new Date().toISOString(),
145
+ };
146
+ await (0, cache_1.rememberCloudFoundryLoginProfile)(updatedProfile);
147
+ return updatedProfile;
148
+ }
149
+ lastError = nextOrgsCheck.stderr || nextOrgsCheck.stdout || lastError;
150
+ }
151
+ throw new Error(`CF automatic login failed for ${options.apiEndpoint}. ${lastError}`);
76
152
  }
77
153
  function buildCloudFoundryLogsArgs(options) {
78
154
  const args = ["logs", options.appName];
@@ -100,13 +176,20 @@ function buildNodeInspectorRemoteCommand(remotePort) {
100
176
  `exit 2`,
101
177
  ].join("; ");
102
178
  }
103
- return [
104
- `PID=$(pgrep -xo node 2>/dev/null || pgrep -x node 2>/dev/null | head -n 1 || ps -eo pid,args 2>/dev/null | awk '/[n]ode|[c]ds/ {print $1; exit}')`,
105
- `if [ -z "$PID" ]; then echo "No Node.js PID found in app container. The app may not be a Node.js app, or Node is hidden behind a launcher. Use prepare mode: Set NODE_OPTIONS and restart app." >&2; ps -eo pid,args 2>/dev/null | head -n 30 >&2; exit 1; fi`,
106
- `if command -v ss >/dev/null 2>&1 && ss -H -ntl "sport = :9229" | grep -q .; then echo "Node inspector already listening on 127.0.0.1:9229"`,
107
- `else kill -s SIGUSR1 "$PID" && echo "Started Node inspector for PID $PID on 127.0.0.1:9229"; fi`,
179
+ const detectNodePidScript = [
180
+ `PID=""`,
181
+ `if command -v pgrep >/dev/null 2>&1; then PID=$(pgrep -f "(^|/| )node( |$)" 2>/dev/null | head -n 1 || true); fi`,
182
+ `if [ -z "$PID" ]; then PID=$(ps -eo pid=,args= 2>/dev/null | awk '/[n]ode/ && $0 !~ /awk/ && $0 !~ /pgrep/ {print $1; exit}'); fi`,
183
+ `if [ -z "$PID" ]; then echo "No Node.js PID found in app container. Use prepare mode: Set NODE_OPTIONS and restart app." >&2; ps -eo pid,args 2>/dev/null | head -n 40 >&2; exit 1; fi`,
184
+ `echo "Detected Node.js PID: $PID"`,
185
+ `if command -v ss >/dev/null 2>&1 && ss -H -ntl "sport = :9229" | grep -q .; then echo "Node inspector already listening on 127.0.0.1:9229"; tail -f /dev/null; fi`,
186
+ `if command -v netstat >/dev/null 2>&1 && netstat -ntl 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)9229$'; then echo "Node inspector already listening on 127.0.0.1:9229"; tail -f /dev/null; fi`,
187
+ `kill -USR1 "$PID" || { echo "Cannot send SIGUSR1 to Node.js PID $PID. Use prepare mode: Set NODE_OPTIONS and restart app." >&2; exit 1; }`,
188
+ `echo "Requested Node inspector for PID $PID on 127.0.0.1:9229"`,
189
+ `COUNT=0; while [ "$COUNT" -lt 20 ]; do if command -v ss >/dev/null 2>&1 && ss -H -ntl "sport = :9229" | grep -q .; then echo "Node inspector is listening on 127.0.0.1:9229"; break; fi; if command -v netstat >/dev/null 2>&1 && netstat -ntl 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)9229$'; then echo "Node inspector is listening on 127.0.0.1:9229"; break; fi; COUNT=$((COUNT + 1)); sleep 1; done`,
108
190
  `tail -f /dev/null`,
109
- ].join("; ");
191
+ ];
192
+ return detectNodePidScript.join("; ");
110
193
  }
111
194
  function buildKeepAliveRemoteCommand() {
112
195
  return [
@@ -131,7 +214,7 @@ function buildCloudFoundryDebugSshArgs(options) {
131
214
  return args;
132
215
  }
133
216
  async function selectNodeInspectorPrepareMode(options) {
134
- return searchableSelectChoice({
217
+ return (0, prompts_2.searchableSelectChoice)({
135
218
  message: "Prepare Node.js inspector",
136
219
  choices: [
137
220
  {
@@ -154,14 +237,14 @@ async function selectNodeInspectorPrepareMode(options) {
154
237
  });
155
238
  }
156
239
  async function ensureSshEnabledForDebug(appName) {
157
- const sshEnabledResult = await runCommand("cf", ["ssh-enabled", appName]);
240
+ const sshEnabledResult = await (0, process_1.runCommand)("cf", ["ssh-enabled", appName]);
158
241
  const combinedOutput = `${sshEnabledResult.stdout}
159
242
  ${sshEnabledResult.stderr}`;
160
243
  if (sshEnabledResult.exitCode === 0 && /enabled/i.test(combinedOutput) && !/not enabled/i.test(combinedOutput)) {
161
244
  return;
162
245
  }
163
- console.log(chalk.yellow("SSH is not enabled for this app. Enabling SSH..."));
164
- const enableResult = await runCommand("cf", ["enable-ssh", appName]);
246
+ console.log(chalk_1.default.yellow("SSH is not enabled for this app. Enabling SSH..."));
247
+ const enableResult = await (0, process_1.runCommand)("cf", ["enable-ssh", appName]);
165
248
  if (enableResult.stdout)
166
249
  console.log(enableResult.stdout);
167
250
  if (enableResult.stderr)
@@ -172,8 +255,8 @@ ${sshEnabledResult.stderr}`;
172
255
  }
173
256
  async function setNodeInspectorEnvironmentAndRestart(options) {
174
257
  const nodeOptions = `--inspect=0.0.0.0:${options.remotePort} --enable-source-maps`;
175
- console.log(chalk.gray(`Setting NODE_OPTIONS for ${options.appName}: ${nodeOptions}`));
176
- const setEnvResult = await runCommand("cf", ["set-env", options.appName, "NODE_OPTIONS", nodeOptions]);
258
+ console.log(chalk_1.default.gray(`Setting NODE_OPTIONS for ${options.appName}: ${nodeOptions}`));
259
+ const setEnvResult = await (0, process_1.runCommand)("cf", ["set-env", options.appName, "NODE_OPTIONS", nodeOptions]);
177
260
  if (setEnvResult.stdout)
178
261
  console.log(setEnvResult.stdout);
179
262
  if (setEnvResult.stderr)
@@ -181,8 +264,8 @@ async function setNodeInspectorEnvironmentAndRestart(options) {
181
264
  if (setEnvResult.exitCode !== 0) {
182
265
  throw new Error("cf set-env NODE_OPTIONS failed");
183
266
  }
184
- console.log(chalk.yellow("Restarting app so NODE_OPTIONS takes effect..."));
185
- const restartExitCode = await runCommandInherit("cf", ["restart", options.appName]);
267
+ console.log(chalk_1.default.yellow("Restarting app so NODE_OPTIONS takes effect..."));
268
+ const restartExitCode = await (0, process_1.runCommandInherit)("cf", ["restart", options.appName]);
186
269
  if (restartExitCode !== 0) {
187
270
  throw new Error(`cf restart ${options.appName} failed`);
188
271
  }
@@ -216,23 +299,23 @@ async function waitForNodeInspectorDebugUrl(localPort, timeoutMs = 10000) {
216
299
  await new Promise((resolve) => setTimeout(resolve, 500));
217
300
  }
218
301
  if (lastError instanceof Error) {
219
- console.log(chalk.gray(`Could not read inspector JSON yet: ${lastError.message}`));
302
+ console.log(chalk_1.default.gray(`Could not read inspector JSON yet: ${lastError.message}`));
220
303
  }
221
304
  return undefined;
222
305
  }
223
306
  function printNodeInspectorAttachInfo(options) {
224
307
  console.log("");
225
- console.log(chalk.green(`Debug tunnel is ready for ${options.appName} instance ${options.instanceIndex}.`));
226
- console.log(`Chrome inspect: ${chalk.cyan("chrome://inspect")}`);
227
- console.log(`Local inspector JSON: ${chalk.cyan(`http://127.0.0.1:${options.localPort}/json/list`)}`);
308
+ console.log(chalk_1.default.green(`Debug tunnel is ready for ${options.appName} instance ${options.instanceIndex}.`));
309
+ console.log(`Chrome inspect: ${chalk_1.default.cyan("chrome://inspect")}`);
310
+ console.log(`Local inspector JSON: ${chalk_1.default.cyan(`http://127.0.0.1:${options.localPort}/json/list`)}`);
228
311
  if (options.debugUrl) {
229
- console.log(`Direct DevTools link: ${chalk.cyan(options.debugUrl)}`);
312
+ console.log(`Direct DevTools link: ${chalk_1.default.cyan(options.debugUrl)}`);
230
313
  }
231
314
  else {
232
- console.log(chalk.yellow("Direct DevTools link was not detected yet. Open chrome://inspect and configure localhost target."));
315
+ console.log(chalk_1.default.yellow("Direct DevTools link was not detected yet. Open chrome://inspect and configure localhost target."));
233
316
  }
234
317
  console.log("");
235
- console.log(chalk.gray("VS Code attach config:"));
318
+ console.log(chalk_1.default.gray("VS Code attach config:"));
236
319
  console.log(JSON.stringify({
237
320
  type: "node",
238
321
  request: "attach",
@@ -244,7 +327,7 @@ function printNodeInspectorAttachInfo(options) {
244
327
  skipFiles: ["<node_internals>/**"],
245
328
  }, null, 2));
246
329
  console.log("");
247
- console.log(chalk.gray("Keep this terminal open. Press Ctrl+C to close the debug tunnel."));
330
+ console.log(chalk_1.default.gray("Keep this terminal open. Press Ctrl+C to close the debug tunnel."));
248
331
  }
249
332
  function buildVscodeNodeAttachConfiguration(options) {
250
333
  return {
@@ -266,21 +349,21 @@ function buildVscodeNodeAttachConfiguration(options) {
266
349
  };
267
350
  }
268
351
  async function writeVscodeLaunchConfiguration(options) {
269
- const vscodeDirectoryPath = path.resolve(options.cwd, ".vscode");
270
- const launchJsonPath = path.join(vscodeDirectoryPath, "launch.json");
352
+ const vscodeDirectoryPath = node_path_1.default.resolve(options.cwd, ".vscode");
353
+ const launchJsonPath = node_path_1.default.join(vscodeDirectoryPath, "launch.json");
271
354
  const configuration = buildVscodeNodeAttachConfiguration({
272
355
  appName: options.appName,
273
356
  localPort: options.localPort,
274
357
  remoteRoot: options.remoteRoot ?? "/home/vcap/app",
275
358
  });
276
- await fs.ensureDir(vscodeDirectoryPath);
359
+ await fs_extra_1.default.ensureDir(vscodeDirectoryPath);
277
360
  let launchJson = {
278
361
  version: "0.2.0",
279
362
  configurations: [],
280
363
  };
281
- if (await fs.pathExists(launchJsonPath)) {
364
+ if (await fs_extra_1.default.pathExists(launchJsonPath)) {
282
365
  try {
283
- const currentContent = await fs.readFile(launchJsonPath, "utf8");
366
+ const currentContent = await fs_extra_1.default.readFile(launchJsonPath, "utf8");
284
367
  const parsed = JSON.parse(currentContent);
285
368
  launchJson = {
286
369
  version: typeof parsed.version === "string" ? parsed.version : "0.2.0",
@@ -289,8 +372,8 @@ async function writeVscodeLaunchConfiguration(options) {
289
372
  }
290
373
  catch {
291
374
  const backupPath = `${launchJsonPath}.backup-${Date.now()}`;
292
- await fs.copyFile(launchJsonPath, backupPath);
293
- console.log(chalk.yellow(`Existing launch.json is not valid JSON. Backup created: ${backupPath}`));
375
+ await fs_extra_1.default.copyFile(launchJsonPath, backupPath);
376
+ console.log(chalk_1.default.yellow(`Existing launch.json is not valid JSON. Backup created: ${backupPath}`));
294
377
  }
295
378
  }
296
379
  const configurationName = String(configuration.name);
@@ -301,41 +384,41 @@ async function writeVscodeLaunchConfiguration(options) {
301
384
  else {
302
385
  launchJson.configurations.unshift(configuration);
303
386
  }
304
- await fs.writeFile(launchJsonPath, `${JSON.stringify(launchJson, null, 2)}\n`, "utf8");
387
+ await fs_extra_1.default.writeFile(launchJsonPath, `${JSON.stringify(launchJson, null, 2)}\n`, "utf8");
305
388
  return launchJsonPath;
306
389
  }
307
390
  function printVscodeAttachInstructions(options) {
308
391
  console.log("");
309
392
  if (options.inspectorReady === false) {
310
- console.log(chalk.yellow(`VS Code config was created, but the Node inspector is not reachable yet for ${options.appName} instance ${options.instanceIndex}.`));
311
- console.log(chalk.yellow("The VS Code debug toolbar appears only after the inspector is reachable and you start the attach config."));
393
+ console.log(chalk_1.default.yellow(`VS Code config was created, but the Node inspector is not reachable yet for ${options.appName} instance ${options.instanceIndex}.`));
394
+ console.log(chalk_1.default.yellow("The VS Code debug toolbar appears only after the inspector is reachable and you start the attach config."));
312
395
  }
313
396
  else {
314
- console.log(chalk.green(`VS Code debug config is ready for ${options.appName} instance ${options.instanceIndex}.`));
397
+ console.log(chalk_1.default.green(`VS Code debug config is ready for ${options.appName} instance ${options.instanceIndex}.`));
315
398
  }
316
- console.log(`Launch file: ${chalk.cyan(options.launchJsonPath)}`);
317
- console.log(`Attach config: ${chalk.cyan(`Attach BTP ${options.appName}`)}`);
318
- console.log(`Inspector: ${chalk.cyan(`127.0.0.1:${options.localPort}`)}`);
399
+ console.log(`Launch file: ${chalk_1.default.cyan(options.launchJsonPath)}`);
400
+ console.log(`Attach config: ${chalk_1.default.cyan(`Attach BTP ${options.appName}`)}`);
401
+ console.log(`Inspector: ${chalk_1.default.cyan(`127.0.0.1:${options.localPort}`)}`);
319
402
  console.log("");
320
- console.log(chalk.cyan("How to start debugging in VS Code:"));
403
+ console.log(chalk_1.default.cyan("How to start debugging in VS Code:"));
321
404
  console.log("1. Keep this terminal open. It owns the CF SSH tunnel.");
322
405
  console.log("2. Open VS Code Run and Debug panel with Ctrl+Shift+D.");
323
- console.log(`3. Select ${chalk.cyan(`Attach BTP ${options.appName}`)}.`);
406
+ console.log(`3. Select ${chalk_1.default.cyan(`Attach BTP ${options.appName}`)}.`);
324
407
  console.log("4. Press F5 or the green Start Debugging button.");
325
408
  console.log("5. Debug buttons such as pause, step over, step into, restart, and stop appear only after attach succeeds.");
326
409
  console.log("");
327
- console.log(chalk.gray("Press Ctrl+C in this terminal to close the tunnel."));
410
+ console.log(chalk_1.default.gray("Press Ctrl+C in this terminal to close the tunnel."));
328
411
  }
329
412
  async function openVisualStudioCode(options) {
330
413
  const args = options.debugPanel
331
414
  ? ["--reuse-window", options.cwd, "--command", "workbench.view.debug"]
332
415
  : [options.cwd];
333
- const result = await runCommand("code", args);
416
+ const result = await (0, process_1.runCommand)("code", args);
334
417
  if (result.exitCode !== 0) {
335
- console.log(chalk.yellow("Could not open VS Code automatically. Open this folder manually in VS Code."));
336
- console.log(chalk.gray("Then open Run and Debug with Ctrl+Shift+D."));
418
+ console.log(chalk_1.default.yellow("Could not open VS Code automatically. Open this folder manually in VS Code."));
419
+ console.log(chalk_1.default.gray("Then open Run and Debug with Ctrl+Shift+D."));
337
420
  if (result.stderr)
338
- console.log(chalk.gray(result.stderr));
421
+ console.log(chalk_1.default.gray(result.stderr));
339
422
  }
340
423
  }
341
424
  async function selectDebugMode(options) {
@@ -351,7 +434,7 @@ async function selectDebugMode(options) {
351
434
  return "chrome";
352
435
  if (options.vscode)
353
436
  return "vscode";
354
- return searchableSelectChoice({
437
+ return (0, prompts_2.searchableSelectChoice)({
355
438
  message: "Select debug mode",
356
439
  choices: [
357
440
  {
@@ -396,9 +479,9 @@ async function maybeSwitchCloudFoundryTargetForDebug(options) {
396
479
  const currentTargetLabel = [
397
480
  target.org ? `org: ${target.org}` : "org: N/A",
398
481
  target.space ? `space: ${target.space}` : "space: N/A",
399
- target.apiEndpoint ? inferCloudFoundryRegionFromApiEndpoint(target.apiEndpoint) : "current region",
482
+ target.apiEndpoint ? (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(target.apiEndpoint) : "current region",
400
483
  ].join(" · ");
401
- const action = await searchableSelectChoice({
484
+ const action = await (0, prompts_2.searchableSelectChoice)({
402
485
  message: "Select BTP target for debug",
403
486
  choices: [
404
487
  { title: `Use current target (${currentTargetLabel})`, value: "current" },
@@ -414,7 +497,7 @@ async function selectDebugInstance(options) {
414
497
  if (options.instance?.trim()) {
415
498
  return options.instance.trim();
416
499
  }
417
- return searchableSelectChoice({
500
+ return (0, prompts_2.searchableSelectChoice)({
418
501
  message: "Select app instance index",
419
502
  choices: [
420
503
  { title: "0", value: "0" },
@@ -430,7 +513,7 @@ async function selectDebugPort(options) {
430
513
  if (options.value?.trim()) {
431
514
  return parsePositivePort(options.value, options.defaultPort);
432
515
  }
433
- const portValue = await searchableSelectChoice({
516
+ const portValue = await (0, prompts_2.searchableSelectChoice)({
434
517
  message: options.message,
435
518
  choices: [
436
519
  { title: `${options.defaultPort} recommended`, value: String(options.defaultPort) },
@@ -493,10 +576,10 @@ function writeFilteredLogChunk(chunk, options) {
493
576
  options.outputStream?.write(outputText);
494
577
  }
495
578
  async function refreshAppsCacheForCurrentTarget() {
496
- const target = await readCloudFoundryTarget();
497
- const targetKey = buildCloudFoundryTargetKey(target);
498
- const apps = await listCloudFoundryApps();
499
- await rememberCloudFoundryApps(targetKey, apps);
579
+ const target = await (0, cf_1.readCloudFoundryTarget)();
580
+ const targetKey = (0, cf_1.buildCloudFoundryTargetKey)(target);
581
+ const apps = await (0, cf_1.listCloudFoundryApps)();
582
+ await (0, cache_1.rememberCloudFoundryApps)(targetKey, apps);
500
583
  return apps;
501
584
  }
502
585
  function refreshAppsCacheInDetachedProcess() {
@@ -504,7 +587,7 @@ function refreshAppsCacheInDetachedProcess() {
504
587
  if (!entryFilePath) {
505
588
  return;
506
589
  }
507
- const childProcess = spawn(process.execPath, [entryFilePath, "cf", "apps-cache-refresh"], {
590
+ const childProcess = (0, node_child_process_1.spawn)(process.execPath, [entryFilePath, "cf", "apps-cache-refresh"], {
508
591
  detached: true,
509
592
  stdio: "ignore",
510
593
  windowsHide: true,
@@ -512,12 +595,13 @@ function refreshAppsCacheInDetachedProcess() {
512
595
  childProcess.unref();
513
596
  }
514
597
  async function getAppsWithCache(options) {
598
+ await ensureCloudFoundrySessionFromCache();
515
599
  if (options.refresh) {
516
600
  return refreshAppsCacheForCurrentTarget();
517
601
  }
518
- const target = await readCloudFoundryTarget();
519
- const targetKey = buildCloudFoundryTargetKey(target);
520
- const cache = await readCache();
602
+ const target = await (0, cf_1.readCloudFoundryTarget)();
603
+ const targetKey = (0, cf_1.buildCloudFoundryTargetKey)(target);
604
+ const cache = await (0, cache_1.readCache)();
521
605
  const cachedEntry = cache.cloudFoundry.appListsByTarget[targetKey];
522
606
  if (cachedEntry?.apps.length) {
523
607
  if (options.startBackgroundRefresh) {
@@ -529,16 +613,16 @@ async function getAppsWithCache(options) {
529
613
  }
530
614
  async function resolveAppSelection(options) {
531
615
  if (options.app?.trim()) {
532
- await rememberSelectedApp(options.app.trim());
616
+ await (0, cache_1.rememberSelectedApp)(options.app.trim());
533
617
  return options.app.trim();
534
618
  }
535
619
  const apps = await getAppsWithCache({ refresh: options.refresh, startBackgroundRefresh: !options.refresh });
536
- const cache = await readCache();
620
+ const cache = await (0, cache_1.readCache)();
537
621
  const cachedSelectedAppNames = cache.cloudFoundry.selectedApps;
538
622
  const cachedSelectedApps = cachedSelectedAppNames
539
623
  .filter((appName) => !apps.some((app) => app.name === appName))
540
- .map((appName) => ({ title: `${appName} ${chalk.gray("cached selected")}`, value: appName }));
541
- const appName = await searchableSelectChoice({
624
+ .map((appName) => ({ title: `${appName} ${chalk_1.default.gray("cached selected")}`, value: appName }));
625
+ const appName = await (0, prompts_2.searchableSelectChoice)({
542
626
  message: options.message,
543
627
  choices: [
544
628
  ...apps.map((app) => ({
@@ -550,7 +634,7 @@ async function resolveAppSelection(options) {
550
634
  validateCustomValue: validateRequired,
551
635
  customValueTitle: (value) => `Use typed app name: ${value}`,
552
636
  });
553
- await rememberSelectedApp(appName);
637
+ await (0, cache_1.rememberSelectedApp)(appName);
554
638
  return appName;
555
639
  }
556
640
  function printTarget(target) {
@@ -562,15 +646,24 @@ function printTarget(target) {
562
646
  const DEFAULT_CLOUD_FOUNDRY_API_ENDPOINTS = [
563
647
  "https://api.cf.br10.hana.ondemand.com",
564
648
  "https://api.cf.eu10.hana.ondemand.com",
649
+ "https://api.cf.eu10-004.hana.ondemand.com",
650
+ "https://api.cf.eu10-005.hana.ondemand.com",
651
+ "https://api.cf.eu20.hana.ondemand.com",
652
+ "https://api.cf.eu20-001.hana.ondemand.com",
653
+ "https://api.cf.eu20-002.hana.ondemand.com",
565
654
  "https://api.cf.us10.hana.ondemand.com",
655
+ "https://api.cf.us10-001.hana.ondemand.com",
656
+ "https://api.cf.us11.hana.ondemand.com",
657
+ "https://api.cf.us20.hana.ondemand.com",
658
+ "https://api.cf.us21.hana.ondemand.com",
566
659
  "https://api.cf.ap10.hana.ondemand.com",
567
660
  "https://api.cf.ap11.hana.ondemand.com",
661
+ "https://api.cf.ap20.hana.ondemand.com",
568
662
  "https://api.cf.ap21.hana.ondemand.com",
569
663
  "https://api.cf.jp10.hana.ondemand.com",
570
664
  "https://api.cf.ca10.hana.ondemand.com",
571
665
  "https://api.cf.ch20.hana.ondemand.com",
572
- "https://api.cf.eu20.hana.ondemand.com",
573
- "https://api.cf.us20.hana.ondemand.com",
666
+ "https://api.cf.sa10.hana.ondemand.com",
574
667
  ];
575
668
  function uniqueValues(values) {
576
669
  return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
@@ -585,7 +678,7 @@ async function selectCloudFoundryApiEndpoint(options) {
585
678
  }
586
679
  const choices = [
587
680
  ...cachedApiEndpoints.map((apiEndpoint) => ({
588
- title: `${apiEndpoint} ${chalk.gray("cached")}`,
681
+ title: `${apiEndpoint} ${chalk_1.default.gray("cached")}`,
589
682
  value: apiEndpoint,
590
683
  })),
591
684
  ...DEFAULT_CLOUD_FOUNDRY_API_ENDPOINTS
@@ -593,7 +686,7 @@ async function selectCloudFoundryApiEndpoint(options) {
593
686
  .map((apiEndpoint) => ({ title: apiEndpoint, value: apiEndpoint })),
594
687
  { title: "Enter CF API endpoint manually", value: "__ENTER_MANUAL__" },
595
688
  ];
596
- return searchableSelectChoice({
689
+ return (0, prompts_2.searchableSelectChoice)({
597
690
  message: "Select CF API endpoint",
598
691
  choices: choices.filter((choice) => choice.value !== "__ENTER_MANUAL__"),
599
692
  validateCustomValue: validateRequired,
@@ -604,16 +697,16 @@ async function selectCloudFoundryOrganization(options) {
604
697
  if (options.org?.trim()) {
605
698
  return options.org.trim();
606
699
  }
607
- const organizations = await listCloudFoundryOrganizations();
700
+ const organizations = await (0, cf_1.listCloudFoundryOrganizations)();
608
701
  if (organizations.length === 0) {
609
- return selectFromHistoryOrInput({
702
+ return (0, prompts_2.selectFromHistoryOrInput)({
610
703
  message: "Select CF org",
611
704
  values: options.cachedOrganizations,
612
705
  initialValue: options.cachedOrganizations[0],
613
706
  validate: validateRequired,
614
707
  });
615
708
  }
616
- return searchableSelectChoice({
709
+ return (0, prompts_2.searchableSelectChoice)({
617
710
  message: "Select CF org",
618
711
  choices: organizations.map((organization) => ({ title: organization, value: organization })),
619
712
  validateCustomValue: validateRequired,
@@ -624,16 +717,16 @@ async function selectCloudFoundrySpace(options) {
624
717
  if (options.space?.trim()) {
625
718
  return options.space.trim();
626
719
  }
627
- const spaces = await listCloudFoundrySpaces();
720
+ const spaces = await (0, cf_1.listCloudFoundrySpaces)();
628
721
  if (spaces.length === 0) {
629
- return selectFromHistoryOrInput({
722
+ return (0, prompts_2.selectFromHistoryOrInput)({
630
723
  message: "Select CF space",
631
724
  values: options.cachedSpaces,
632
725
  initialValue: options.cachedSpaces[0] ?? "app",
633
726
  });
634
727
  }
635
728
  const initialSpace = spaces.includes("app") ? "app" : spaces[0];
636
- return searchableSelectChoice({
729
+ return (0, prompts_2.searchableSelectChoice)({
637
730
  message: "Select CF space",
638
731
  choices: [
639
732
  ...spaces
@@ -648,13 +741,14 @@ async function selectCloudFoundrySpace(options) {
648
741
  });
649
742
  }
650
743
  async function runLoginCommand(options) {
651
- const cache = await readCache();
744
+ await (0, tooling_1.ensureExternalTool)("cf");
745
+ const cache = await (0, cache_1.readCache)();
652
746
  const lastProfile = cache.cloudFoundry.loginProfiles[0];
653
747
  const apiEndpoint = await selectCloudFoundryApiEndpoint({
654
748
  api: options.api,
655
749
  cachedApiEndpoints: cache.cloudFoundry.loginProfiles.map((item) => item.apiEndpoint),
656
750
  });
657
- const username = options.username ?? await selectFromHistoryOrInput({
751
+ const username = options.username ?? await (0, prompts_2.selectFromHistoryOrInput)({
658
752
  message: "Select CF username",
659
753
  values: cache.cloudFoundry.loginProfiles.map((item) => item.username),
660
754
  initialValue: lastProfile?.username,
@@ -663,7 +757,7 @@ async function runLoginCommand(options) {
663
757
  let password = options.password ?? lastProfile?.password;
664
758
  let shouldSavePassword = options.savePassword ?? false;
665
759
  if (!password) {
666
- const response = await prompts({
760
+ const response = await (0, prompts_1.default)({
667
761
  type: "password",
668
762
  name: "password",
669
763
  message: "Enter CF password",
@@ -675,7 +769,7 @@ async function runLoginCommand(options) {
675
769
  password = response.password;
676
770
  }
677
771
  else {
678
- const response = await prompts({
772
+ const response = await (0, prompts_1.default)({
679
773
  type: "select",
680
774
  name: "useCachedPassword",
681
775
  message: "Use cached password?",
@@ -686,7 +780,7 @@ async function runLoginCommand(options) {
686
780
  initial: 0,
687
781
  });
688
782
  if (!response.useCachedPassword) {
689
- const passwordResponse = await prompts({
783
+ const passwordResponse = await (0, prompts_1.default)({
690
784
  type: "password",
691
785
  name: "password",
692
786
  message: "Enter CF password",
@@ -699,7 +793,7 @@ async function runLoginCommand(options) {
699
793
  }
700
794
  }
701
795
  if (!shouldSavePassword) {
702
- const savePasswordResponse = await prompts({
796
+ const savePasswordResponse = await (0, prompts_1.default)({
703
797
  type: "select",
704
798
  name: "savePassword",
705
799
  message: "Save password for automatic re-login and region scan?",
@@ -711,12 +805,12 @@ async function runLoginCommand(options) {
711
805
  });
712
806
  shouldSavePassword = Boolean(savePasswordResponse.savePassword);
713
807
  }
714
- const apiExitCode = await setCloudFoundryApiEndpoint(apiEndpoint);
808
+ const apiExitCode = await (0, cf_1.setCloudFoundryApiEndpoint)(apiEndpoint);
715
809
  if (apiExitCode !== 0) {
716
810
  process.exitCode = apiExitCode;
717
811
  return;
718
812
  }
719
- const authExitCode = await authenticateCloudFoundry({ username, password });
813
+ const authExitCode = await (0, cf_1.authenticateCloudFoundry)({ username, password });
720
814
  if (authExitCode !== 0) {
721
815
  process.exitCode = authExitCode;
722
816
  return;
@@ -727,7 +821,7 @@ async function runLoginCommand(options) {
727
821
  .filter((item) => item.apiEndpoint === apiEndpoint && item.username === username)
728
822
  .map((item) => item.org),
729
823
  });
730
- const orgExitCode = await targetCloudFoundryOrg(org);
824
+ const orgExitCode = await (0, cf_1.targetCloudFoundryOrg)(org);
731
825
  if (orgExitCode !== 0) {
732
826
  process.exitCode = orgExitCode;
733
827
  return;
@@ -740,13 +834,13 @@ async function runLoginCommand(options) {
740
834
  .filter(Boolean),
741
835
  });
742
836
  if (space) {
743
- const spaceExitCode = await targetCloudFoundrySpace(space);
837
+ const spaceExitCode = await (0, cf_1.targetCloudFoundrySpace)(space);
744
838
  if (spaceExitCode !== 0) {
745
839
  process.exitCode = spaceExitCode;
746
840
  return;
747
841
  }
748
842
  }
749
- await rememberCloudFoundryLoginProfile({
843
+ await (0, cache_1.rememberCloudFoundryLoginProfile)({
750
844
  apiEndpoint,
751
845
  username,
752
846
  org,
@@ -755,28 +849,29 @@ async function runLoginCommand(options) {
755
849
  updatedAt: new Date().toISOString(),
756
850
  });
757
851
  if (shouldSavePassword) {
758
- console.log(chalk.yellow("Password was cached in ~/.simplemdg/cache.json for automatic re-login. Do not use this on shared machines."));
852
+ console.log(chalk_1.default.yellow("Password was cached in ~/.simplemdg/cache.json for automatic re-login. Do not use this on shared machines."));
759
853
  }
760
- console.log(chalk.green("CF login completed."));
854
+ console.log(chalk_1.default.green("CF login completed."));
761
855
  }
762
856
  function formatCloudFoundryOrgEntry(entry, target) {
763
857
  const isCurrent = entry.apiEndpoint === target.apiEndpoint && entry.org === target.org;
764
858
  const spaceText = typeof entry.spaceCount === "number"
765
859
  ? `${entry.spaceCount} ${entry.spaceCount === 1 ? "space" : "spaces"}`
766
860
  : "spaces unknown";
767
- return `${entry.org} ${chalk.gray(`${entry.region} · ${spaceText}${isCurrent ? " · current" : ""}`)}`;
861
+ return `${entry.org} ${chalk_1.default.gray(`${entry.region} · ${spaceText}${isCurrent ? " · current" : ""}`)}`;
768
862
  }
769
863
  function getCloudFoundryApiEndpointsForOrgSearch(options, target, cache) {
770
864
  return uniqueValues([
771
865
  options.api ?? "",
772
866
  target.apiEndpoint ?? "",
773
867
  ...cache.cloudFoundry.loginProfiles.map((item) => item.apiEndpoint),
868
+ ...cache.cloudFoundry.orgsAcrossRegions.map((item) => item.apiEndpoint),
774
869
  ...DEFAULT_CLOUD_FOUNDRY_API_ENDPOINTS,
775
870
  ]);
776
871
  }
777
872
  async function getCloudFoundryOrganizationsAcrossRegions(options) {
778
- const target = await readCloudFoundryTarget();
779
- const cache = await readCache();
873
+ const target = await (0, cf_1.readCloudFoundryTarget)();
874
+ const cache = await (0, cache_1.readCache)();
780
875
  const cachedEntries = cache.cloudFoundry.orgsAcrossRegions ?? [];
781
876
  const cachedRegionCount = new Set(cachedEntries.map((entry) => entry.region)).size;
782
877
  if (!options.refresh && cachedEntries.length && cachedRegionCount > 1) {
@@ -786,21 +881,21 @@ async function getCloudFoundryOrganizationsAcrossRegions(options) {
786
881
  });
787
882
  }
788
883
  const apiEndpoints = getCloudFoundryApiEndpointsForOrgSearch(options, target, cache);
789
- console.log(chalk.gray(`Searching CF orgs across ${apiEndpoints.length} region endpoint(s)...`));
884
+ console.log(chalk_1.default.gray(`Searching CF orgs across ${apiEndpoints.length} region endpoint(s)...`));
790
885
  const credentials = cache.cloudFoundry.loginProfiles.map((profile) => ({
791
886
  apiEndpoint: profile.apiEndpoint,
792
887
  username: profile.username,
793
888
  password: profile.password,
794
889
  }));
795
- const entries = await scanCloudFoundryOrganizationsAcrossRegions(apiEndpoints, credentials);
890
+ const entries = await (0, cf_1.scanCloudFoundryOrganizationsAcrossRegions)(apiEndpoints, credentials);
796
891
  if (entries.length) {
797
892
  const regionCount = new Set(entries.map((entry) => entry.region)).size;
798
- console.log(chalk.green(`Found ${entries.length} org(s) across ${regionCount} region(s).`));
799
- await rememberCloudFoundryOrgEntries(entries);
893
+ console.log(chalk_1.default.green(`Found ${entries.length} org(s) across ${regionCount} region(s).`));
894
+ await (0, cache_1.rememberCloudFoundryOrgEntries)(entries);
800
895
  return entries;
801
896
  }
802
- const currentOrganizations = await listCloudFoundryOrganizations().catch(() => []);
803
- const currentRegion = target.apiEndpoint ? inferCloudFoundryRegionFromApiEndpoint(target.apiEndpoint) : "current";
897
+ const currentOrganizations = await (0, cf_1.listCloudFoundryOrganizations)().catch(() => []);
898
+ const currentRegion = target.apiEndpoint ? (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(target.apiEndpoint) : "current";
804
899
  const fallbackEntries = currentOrganizations.map((organization) => ({
805
900
  apiEndpoint: target.apiEndpoint ?? "",
806
901
  region: currentRegion,
@@ -808,7 +903,7 @@ async function getCloudFoundryOrganizationsAcrossRegions(options) {
808
903
  updatedAt: new Date().toISOString(),
809
904
  }));
810
905
  if (fallbackEntries.length) {
811
- await rememberCloudFoundryOrgEntries(fallbackEntries);
906
+ await (0, cache_1.rememberCloudFoundryOrgEntries)(fallbackEntries);
812
907
  }
813
908
  return fallbackEntries;
814
909
  }
@@ -818,7 +913,7 @@ async function runOrgCommand(options) {
818
913
  ? "list"
819
914
  : options.switch
820
915
  ? "switch"
821
- : await searchableSelectChoice({
916
+ : await (0, prompts_2.searchableSelectChoice)({
822
917
  message: "What do you want to do with CF org?",
823
918
  choices: [
824
919
  { title: "List orgs across regions", value: "list" },
@@ -836,18 +931,18 @@ async function runOrgCommand(options) {
836
931
  refresh: options.refresh,
837
932
  });
838
933
  const organizationRegionCount = new Set(organizationEntries.map((entry) => entry.region)).size;
839
- const latestTarget = await readCloudFoundryTarget();
934
+ const latestTarget = await (0, cf_1.readCloudFoundryTarget)();
840
935
  if (action === "list") {
841
936
  printTarget(latestTarget);
842
937
  console.log("");
843
938
  if (!organizationEntries.length) {
844
- console.log(chalk.yellow("No orgs found for current CF user. Run smdg cf login, save the password, then run smdg cf org again."));
939
+ console.log(chalk_1.default.yellow("No orgs found for current CF user. Run smdg cf login, save the password, then run smdg cf org again."));
845
940
  return;
846
941
  }
847
- console.log(chalk.gray(`Showing ${organizationEntries.length} org(s) across ${organizationRegionCount} region(s).`));
942
+ console.log(chalk_1.default.gray(`Showing ${organizationEntries.length} org(s) across ${organizationRegionCount} region(s).`));
848
943
  console.log("");
849
944
  for (const entry of organizationEntries) {
850
- const marker = entry.apiEndpoint === latestTarget.apiEndpoint && entry.org === latestTarget.org ? chalk.green("*") : " ";
945
+ const marker = entry.apiEndpoint === latestTarget.apiEndpoint && entry.org === latestTarget.org ? chalk_1.default.green("*") : " ";
851
946
  console.log(`${marker} ${formatCloudFoundryOrgEntry(entry, latestTarget)}`);
852
947
  }
853
948
  return;
@@ -859,19 +954,19 @@ async function runOrgCommand(options) {
859
954
  }) ?? {
860
955
  apiEndpoint: options.api?.trim() || latestTarget.apiEndpoint || "",
861
956
  region: options.api?.trim()
862
- ? inferCloudFoundryRegionFromApiEndpoint(options.api.trim())
863
- : inferCloudFoundryRegionFromApiEndpoint(latestTarget.apiEndpoint ?? "current"),
957
+ ? (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(options.api.trim())
958
+ : (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(latestTarget.apiEndpoint ?? "current"),
864
959
  org: options.org.trim(),
865
960
  updatedAt: new Date().toISOString(),
866
961
  };
867
962
  }
868
963
  else {
869
964
  if (!organizationEntries.length) {
870
- console.log(chalk.yellow("No orgs were found across regions."));
871
- console.log(chalk.gray("Run smdg cf login and save the password, then run smdg cf org --list --refresh."));
965
+ console.log(chalk_1.default.yellow("No orgs were found across regions."));
966
+ console.log(chalk_1.default.gray("Run smdg cf login and save the password, then run smdg cf org --list --refresh."));
872
967
  return;
873
968
  }
874
- const selectedIndex = await searchableSelectChoice({
969
+ const selectedIndex = await (0, prompts_2.searchableSelectChoice)({
875
970
  message: `Search CF org across ${organizationRegionCount} region(s)`,
876
971
  choices: organizationEntries.map((entry, index) => ({
877
972
  title: formatCloudFoundryOrgEntry(entry, latestTarget),
@@ -882,7 +977,7 @@ async function runOrgCommand(options) {
882
977
  });
883
978
  selectedEntry = organizationEntries[Number(selectedIndex)] ?? {
884
979
  apiEndpoint: latestTarget.apiEndpoint ?? "",
885
- region: inferCloudFoundryRegionFromApiEndpoint(latestTarget.apiEndpoint ?? "current"),
980
+ region: (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(latestTarget.apiEndpoint ?? "current"),
886
981
  org: selectedIndex,
887
982
  updatedAt: new Date().toISOString(),
888
983
  };
@@ -890,26 +985,28 @@ async function runOrgCommand(options) {
890
985
  if (!selectedEntry.apiEndpoint) {
891
986
  throw new Error("Cannot determine CF API endpoint for selected org.");
892
987
  }
893
- const apiExitCode = await setCloudFoundryApiEndpoint(selectedEntry.apiEndpoint);
894
- if (apiExitCode !== 0) {
895
- process.exitCode = apiExitCode;
896
- return;
897
- }
898
- const orgExitCode = await targetCloudFoundryOrg(selectedEntry.org);
988
+ const authenticatedProfile = await ensureCloudFoundryAuthenticatedForApiEndpoint({
989
+ apiEndpoint: selectedEntry.apiEndpoint,
990
+ preferredOrg: selectedEntry.org,
991
+ preferredSpace: options.space,
992
+ reason: "switch-org",
993
+ });
994
+ const orgExitCode = await (0, cf_1.targetCloudFoundryOrg)(selectedEntry.org);
899
995
  if (orgExitCode !== 0) {
900
- console.log(chalk.yellow("Cannot switch to this org with current CF session. Run smdg cf login for this region, then try again."));
996
+ console.log(chalk_1.default.yellow("Cannot switch to this org after automatic authentication."));
997
+ console.log(chalk_1.default.gray("Run smdg cf login, save the password, then try again."));
901
998
  process.exitCode = orgExitCode;
902
999
  return;
903
1000
  }
904
- const spaces = selectedEntry.spaces?.length ? selectedEntry.spaces : await listCloudFoundrySpaces();
905
- const currentTargetAfterOrgSwitch = await readCloudFoundryTarget();
1001
+ const spaces = selectedEntry.spaces?.length ? selectedEntry.spaces : await (0, cf_1.listCloudFoundrySpaces)();
1002
+ const currentTargetAfterOrgSwitch = await (0, cf_1.readCloudFoundryTarget)();
906
1003
  const preferredSpace = options.space?.trim() || currentTargetAfterOrgSwitch.space || (spaces.includes("app") ? "app" : spaces[0]);
907
- const space = options.space?.trim() || await searchableSelectChoice({
1004
+ const space = options.space?.trim() || await (0, prompts_2.searchableSelectChoice)({
908
1005
  message: "Select CF space",
909
1006
  choices: [
910
1007
  ...spaces
911
1008
  .filter((spaceName) => spaceName === preferredSpace)
912
- .map((spaceName) => ({ title: `${spaceName} ${spaceName === currentTargetAfterOrgSwitch.space ? chalk.gray("current") : chalk.gray("suggested")}`, value: spaceName })),
1009
+ .map((spaceName) => ({ title: `${spaceName} ${spaceName === currentTargetAfterOrgSwitch.space ? chalk_1.default.gray("current") : chalk_1.default.gray("suggested")}`, value: spaceName })),
913
1010
  ...spaces
914
1011
  .filter((spaceName) => spaceName !== preferredSpace)
915
1012
  .map((spaceName) => ({ title: spaceName, value: spaceName })),
@@ -918,20 +1015,29 @@ async function runOrgCommand(options) {
918
1015
  customValueTitle: (value) => `Use typed CF space: ${value}`,
919
1016
  });
920
1017
  if (space) {
921
- const spaceExitCode = await targetCloudFoundrySpace(space);
1018
+ const spaceExitCode = await (0, cf_1.targetCloudFoundrySpace)(space);
922
1019
  if (spaceExitCode !== 0) {
923
1020
  process.exitCode = spaceExitCode;
924
1021
  return;
925
1022
  }
926
1023
  }
927
- const switchedTarget = await readCloudFoundryTarget();
928
- console.log(chalk.green("CF org/space switched."));
1024
+ if (authenticatedProfile?.password) {
1025
+ await (0, cache_1.rememberCloudFoundryLoginProfile)({
1026
+ ...authenticatedProfile,
1027
+ apiEndpoint: selectedEntry.apiEndpoint,
1028
+ org: selectedEntry.org,
1029
+ space,
1030
+ updatedAt: new Date().toISOString(),
1031
+ });
1032
+ }
1033
+ const switchedTarget = await (0, cf_1.readCloudFoundryTarget)();
1034
+ console.log(chalk_1.default.green("CF org/space switched."));
929
1035
  printTarget(switchedTarget);
930
1036
  }
931
1037
  async function runAppsCommand(options) {
932
- const target = await readCloudFoundryTarget();
933
- const targetKey = buildCloudFoundryTargetKey(target);
934
- const cache = await readCache();
1038
+ const target = await (0, cf_1.readCloudFoundryTarget)();
1039
+ const targetKey = (0, cf_1.buildCloudFoundryTargetKey)(target);
1040
+ const cache = await (0, cache_1.readCache)();
935
1041
  const cachedEntry = cache.cloudFoundry.appListsByTarget[targetKey];
936
1042
  printTarget(target);
937
1043
  console.log("");
@@ -941,12 +1047,12 @@ async function runAppsCommand(options) {
941
1047
  startBackgroundRefresh: shouldUseCache,
942
1048
  });
943
1049
  if (shouldUseCache && cachedEntry) {
944
- console.log(chalk.gray(`Using cached cf apps: ${cachedEntry.apps.length} apps, updated at ${cachedEntry.updatedAt}`));
945
- console.log(chalk.gray("Refreshing cf apps cache in background. Use --refresh when you want to wait for fresh data."));
1050
+ console.log(chalk_1.default.gray(`Using cached cf apps: ${cachedEntry.apps.length} apps, updated at ${cachedEntry.updatedAt}`));
1051
+ console.log(chalk_1.default.gray("Refreshing cf apps cache in background. Use --refresh when you want to wait for fresh data."));
946
1052
  console.log("");
947
1053
  }
948
1054
  else if (options.refresh) {
949
- console.log(chalk.green(`Refreshed cf apps cache for ${targetKey}.`));
1055
+ console.log(chalk_1.default.green(`Refreshed cf apps cache for ${targetKey}.`));
950
1056
  console.log("");
951
1057
  }
952
1058
  if (options.select) {
@@ -962,55 +1068,55 @@ async function runAppsCacheRefreshCommand() {
962
1068
  await refreshAppsCacheForCurrentTarget();
963
1069
  }
964
1070
  async function runBindCommand(options) {
965
- const repositoryPath = await resolveRepositoryPath(options.cwd ?? process.cwd());
1071
+ const repositoryPath = await (0, repository_1.resolveRepositoryPath)(options.cwd ?? process.cwd());
966
1072
  const appName = await resolveAppSelection({ app: options.app, refresh: options.refresh, message: "Select app to cds bind" });
967
- const exitCode = await runCommandInherit("cds", ["bind", "--to-app-services", appName], { cwd: repositoryPath });
1073
+ const exitCode = await (0, process_1.runCommandInherit)("cds", ["bind", "--to-app-services", appName], { cwd: repositoryPath });
968
1074
  process.exitCode = exitCode;
969
1075
  }
970
1076
  async function runEnvCommand(options) {
971
- const repositoryPath = await resolveRepositoryPath(options.cwd ?? process.cwd());
1077
+ const repositoryPath = await (0, repository_1.resolveRepositoryPath)(options.cwd ?? process.cwd());
972
1078
  const appName = await resolveAppSelection({ app: options.app, refresh: options.refresh, message: "Select app to export cf env" });
973
- const cache = await readCache();
974
- const outputFileName = options.out ?? await selectFromHistoryOrInput({
1079
+ const cache = await (0, cache_1.readCache)();
1080
+ const outputFileName = options.out ?? await (0, prompts_2.selectFromHistoryOrInput)({
975
1081
  message: "Select output env file name",
976
1082
  values: cache.cloudFoundry.envFileNames,
977
1083
  initialValue: cache.cloudFoundry.envFileNames[0] ?? "default-env.json",
978
1084
  validate: validateRequired,
979
1085
  });
980
- const result = await runCommand("cf", ["env", appName]);
1086
+ const result = await (0, process_1.runCommand)("cf", ["env", appName]);
981
1087
  if (result.exitCode !== 0) {
982
1088
  throw new Error(result.stderr || result.stdout || "cf env failed");
983
1089
  }
984
- const outputPath = path.resolve(repositoryPath, outputFileName);
1090
+ const outputPath = node_path_1.default.resolve(repositoryPath, outputFileName);
985
1091
  if (options.raw) {
986
- await fs.writeFile(outputPath, result.stdout, "utf8");
1092
+ await fs_extra_1.default.writeFile(outputPath, result.stdout, "utf8");
987
1093
  }
988
1094
  else {
989
- const parsedEnvironment = parseCloudFoundryEnvironment(result.stdout);
990
- await fs.writeJson(outputPath, parsedEnvironment, { spaces: 2 });
1095
+ const parsedEnvironment = (0, cf_env_parser_1.parseCloudFoundryEnvironment)(result.stdout);
1096
+ await fs_extra_1.default.writeJson(outputPath, parsedEnvironment, { spaces: 2 });
991
1097
  }
992
- await rememberSelectedApp(appName);
993
- await rememberEnvironmentFileName(outputFileName);
994
- console.log(chalk.green(`Exported ${options.raw ? "raw env" : "clean JSON env"} to ${outputPath}`));
1098
+ await (0, cache_1.rememberSelectedApp)(appName);
1099
+ await (0, cache_1.rememberEnvironmentFileName)(outputFileName);
1100
+ console.log(chalk_1.default.green(`Exported ${options.raw ? "raw env" : "clean JSON env"} to ${outputPath}`));
995
1101
  }
996
1102
  async function runLogsCommand(options) {
997
1103
  const appName = await resolveAppSelection({ app: options.app, refresh: options.refresh, message: "Select app to view logs" });
998
1104
  const shouldFollow = options.follow || !options.recent;
999
1105
  const shouldReadRecent = options.recent || !shouldFollow;
1000
- const outputPath = options.out ? path.resolve(process.cwd(), options.out) : undefined;
1106
+ const outputPath = options.out ? node_path_1.default.resolve(process.cwd(), options.out) : undefined;
1001
1107
  if (outputPath) {
1002
- await fs.ensureDir(path.dirname(outputPath));
1108
+ await fs_extra_1.default.ensureDir(node_path_1.default.dirname(outputPath));
1003
1109
  }
1004
1110
  if (shouldReadRecent && !shouldFollow) {
1005
- const result = await runCommand("cf", buildCloudFoundryLogsArgs({ appName, recent: true }));
1111
+ const result = await (0, process_1.runCommand)("cf", buildCloudFoundryLogsArgs({ appName, recent: true }));
1006
1112
  const combinedOutput = [result.stdout, result.stderr].filter(Boolean).join("\n");
1007
1113
  const filteredOutput = filterCloudFoundryLogsOutput(combinedOutput, {
1008
1114
  instance: options.instance,
1009
1115
  process: options.process,
1010
1116
  });
1011
1117
  if (outputPath) {
1012
- await fs.writeFile(outputPath, filteredOutput.endsWith("\n") ? filteredOutput : `${filteredOutput}\n`, "utf8");
1013
- console.log(chalk.green(`Exported recent logs to ${outputPath}`));
1118
+ await fs_extra_1.default.writeFile(outputPath, filteredOutput.endsWith("\n") ? filteredOutput : `${filteredOutput}\n`, "utf8");
1119
+ console.log(chalk_1.default.green(`Exported recent logs to ${outputPath}`));
1014
1120
  }
1015
1121
  else {
1016
1122
  console.log(filteredOutput);
@@ -1018,12 +1124,12 @@ async function runLogsCommand(options) {
1018
1124
  process.exitCode = result.exitCode;
1019
1125
  return;
1020
1126
  }
1021
- const outputStream = outputPath ? nodeFs.createWriteStream(outputPath, { flags: "a" }) : undefined;
1127
+ const outputStream = outputPath ? node_fs_1.default.createWriteStream(outputPath, { flags: "a" }) : undefined;
1022
1128
  if (outputPath) {
1023
- console.log(chalk.gray(`Streaming logs and appending to ${outputPath}`));
1129
+ console.log(chalk_1.default.gray(`Streaming logs and appending to ${outputPath}`));
1024
1130
  }
1025
- console.log(chalk.gray("Press Ctrl+C to stop realtime logs."));
1026
- const childProcess = spawn("cf", buildCloudFoundryLogsArgs({ appName, recent: false }), {
1131
+ console.log(chalk_1.default.gray("Press Ctrl+C to stop realtime logs."));
1132
+ const childProcess = (0, node_child_process_1.spawn)("cf", buildCloudFoundryLogsArgs({ appName, recent: false }), {
1027
1133
  stdio: ["ignore", "pipe", "pipe"],
1028
1134
  shell: false,
1029
1135
  windowsHide: true,
@@ -1051,8 +1157,1291 @@ async function runLogsCommand(options) {
1051
1157
  childProcess.on("close", () => resolve());
1052
1158
  });
1053
1159
  }
1160
+ function parseWebSocketUrl(value) {
1161
+ const url = new URL(value);
1162
+ return {
1163
+ host: url.hostname,
1164
+ port: Number(url.port || 80),
1165
+ path: `${url.pathname}${url.search}`,
1166
+ };
1167
+ }
1168
+ async function getNodeInspectorWebSocketUrl(localPort) {
1169
+ const response = await fetch(`http://127.0.0.1:${localPort}/json/list`);
1170
+ if (!response.ok) {
1171
+ return undefined;
1172
+ }
1173
+ const targets = await response.json();
1174
+ return targets.find((target) => target.webSocketDebuggerUrl)?.webSocketDebuggerUrl;
1175
+ }
1176
+ async function waitForNodeInspectorWebSocketUrl(localPort, timeoutMs = 15000) {
1177
+ const startedAt = Date.now();
1178
+ let lastError;
1179
+ while (Date.now() - startedAt < timeoutMs) {
1180
+ try {
1181
+ const webSocketUrl = await getNodeInspectorWebSocketUrl(localPort);
1182
+ if (webSocketUrl) {
1183
+ return webSocketUrl;
1184
+ }
1185
+ }
1186
+ catch (error) {
1187
+ lastError = error;
1188
+ }
1189
+ await new Promise((resolve) => setTimeout(resolve, 500));
1190
+ }
1191
+ if (lastError instanceof Error) {
1192
+ console.log(chalk_1.default.gray(`Could not read inspector WebSocket yet: ${lastError.message}`));
1193
+ }
1194
+ return undefined;
1195
+ }
1196
+ function encodeWebSocketFrame(payload) {
1197
+ const payloadBuffer = Buffer.from(payload, "utf8");
1198
+ const maskKey = node_crypto_1.default.randomBytes(4);
1199
+ let header;
1200
+ if (payloadBuffer.length < 126) {
1201
+ header = Buffer.alloc(2);
1202
+ header[0] = 0x81;
1203
+ header[1] = 0x80 | payloadBuffer.length;
1204
+ }
1205
+ else if (payloadBuffer.length <= 0xffff) {
1206
+ header = Buffer.alloc(4);
1207
+ header[0] = 0x81;
1208
+ header[1] = 0x80 | 126;
1209
+ header.writeUInt16BE(payloadBuffer.length, 2);
1210
+ }
1211
+ else {
1212
+ header = Buffer.alloc(10);
1213
+ header[0] = 0x81;
1214
+ header[1] = 0x80 | 127;
1215
+ header.writeBigUInt64BE(BigInt(payloadBuffer.length), 2);
1216
+ }
1217
+ const maskedPayload = Buffer.alloc(payloadBuffer.length);
1218
+ for (let index = 0; index < payloadBuffer.length; index += 1) {
1219
+ maskedPayload[index] = payloadBuffer[index] ^ maskKey[index % 4];
1220
+ }
1221
+ return Buffer.concat([header, maskKey, maskedPayload]);
1222
+ }
1223
+ function decodeWebSocketFrames(buffer) {
1224
+ const messages = [];
1225
+ let offset = 0;
1226
+ while (offset + 2 <= buffer.length) {
1227
+ const firstByte = buffer[offset];
1228
+ const secondByte = buffer[offset + 1];
1229
+ const opcode = firstByte & 0x0f;
1230
+ const isMasked = Boolean(secondByte & 0x80);
1231
+ let payloadLength = secondByte & 0x7f;
1232
+ let headerLength = 2;
1233
+ if (payloadLength === 126) {
1234
+ if (offset + 4 > buffer.length)
1235
+ break;
1236
+ payloadLength = buffer.readUInt16BE(offset + 2);
1237
+ headerLength = 4;
1238
+ }
1239
+ else if (payloadLength === 127) {
1240
+ if (offset + 10 > buffer.length)
1241
+ break;
1242
+ const longLength = buffer.readBigUInt64BE(offset + 2);
1243
+ if (longLength > BigInt(Number.MAX_SAFE_INTEGER)) {
1244
+ throw new Error("WebSocket frame is too large");
1245
+ }
1246
+ payloadLength = Number(longLength);
1247
+ headerLength = 10;
1248
+ }
1249
+ const maskLength = isMasked ? 4 : 0;
1250
+ const frameLength = headerLength + maskLength + payloadLength;
1251
+ if (offset + frameLength > buffer.length) {
1252
+ break;
1253
+ }
1254
+ let payload = buffer.subarray(offset + headerLength + maskLength, offset + frameLength);
1255
+ if (isMasked) {
1256
+ const maskKey = buffer.subarray(offset + headerLength, offset + headerLength + 4);
1257
+ const unmaskedPayload = Buffer.alloc(payload.length);
1258
+ for (let index = 0; index < payload.length; index += 1) {
1259
+ unmaskedPayload[index] = payload[index] ^ maskKey[index % 4];
1260
+ }
1261
+ payload = unmaskedPayload;
1262
+ }
1263
+ if (opcode === 0x1) {
1264
+ messages.push(payload.toString("utf8"));
1265
+ }
1266
+ offset += frameLength;
1267
+ }
1268
+ return { messages, remaining: buffer.subarray(offset) };
1269
+ }
1270
+ async function sendInspectorEvaluateCommand(options) {
1271
+ const connection = parseWebSocketUrl(options.webSocketUrl);
1272
+ const timeoutMs = options.timeoutMs ?? 10000;
1273
+ const key = node_crypto_1.default.randomBytes(16).toString("base64");
1274
+ const request = [
1275
+ `GET ${connection.path} HTTP/1.1`,
1276
+ `Host: ${connection.host}:${connection.port}`,
1277
+ "Upgrade: websocket",
1278
+ "Connection: Upgrade",
1279
+ `Sec-WebSocket-Key: ${key}`,
1280
+ "Sec-WebSocket-Version: 13",
1281
+ "",
1282
+ "",
1283
+ ].join("\r\n");
1284
+ await new Promise((resolve, reject) => {
1285
+ const socket = node_net_1.default.createConnection({ host: connection.host, port: connection.port });
1286
+ const commandId = 1;
1287
+ let isHandshakeComplete = false;
1288
+ let handshakeBuffer = Buffer.alloc(0);
1289
+ let frameBuffer = Buffer.alloc(0);
1290
+ const timer = setTimeout(() => {
1291
+ socket.destroy();
1292
+ reject(new Error("Inspector Runtime.evaluate timed out"));
1293
+ }, timeoutMs);
1294
+ const cleanup = () => {
1295
+ clearTimeout(timer);
1296
+ socket.removeAllListeners();
1297
+ socket.end();
1298
+ socket.destroy();
1299
+ };
1300
+ socket.on("connect", () => {
1301
+ socket.write(request);
1302
+ });
1303
+ socket.on("error", (error) => {
1304
+ cleanup();
1305
+ reject(error);
1306
+ });
1307
+ socket.on("data", (chunk) => {
1308
+ try {
1309
+ if (!isHandshakeComplete) {
1310
+ handshakeBuffer = Buffer.concat([handshakeBuffer, chunk]);
1311
+ const headerEndIndex = handshakeBuffer.indexOf("\r\n\r\n");
1312
+ if (headerEndIndex < 0) {
1313
+ return;
1314
+ }
1315
+ const headerText = handshakeBuffer.subarray(0, headerEndIndex).toString("utf8");
1316
+ if (!/^HTTP\/1\.1 101/i.test(headerText)) {
1317
+ throw new Error(`Inspector WebSocket upgrade failed: ${headerText.split("\r\n")[0]}`);
1318
+ }
1319
+ isHandshakeComplete = true;
1320
+ const rest = handshakeBuffer.subarray(headerEndIndex + 4);
1321
+ frameBuffer = rest.length ? Buffer.concat([frameBuffer, rest]) : frameBuffer;
1322
+ const payload = JSON.stringify({
1323
+ id: commandId,
1324
+ method: "Runtime.evaluate",
1325
+ params: {
1326
+ expression: options.expression,
1327
+ awaitPromise: false,
1328
+ returnByValue: true,
1329
+ },
1330
+ });
1331
+ socket.write(encodeWebSocketFrame(payload));
1332
+ }
1333
+ else {
1334
+ frameBuffer = Buffer.concat([frameBuffer, chunk]);
1335
+ }
1336
+ const decoded = decodeWebSocketFrames(frameBuffer);
1337
+ frameBuffer = Buffer.from(decoded.remaining);
1338
+ for (const message of decoded.messages) {
1339
+ const parsed = JSON.parse(message);
1340
+ if (parsed.id === commandId) {
1341
+ cleanup();
1342
+ if (parsed.error) {
1343
+ reject(new Error(`Inspector Runtime.evaluate failed: ${JSON.stringify(parsed.error)}`));
1344
+ return;
1345
+ }
1346
+ resolve();
1347
+ return;
1348
+ }
1349
+ }
1350
+ }
1351
+ catch (error) {
1352
+ cleanup();
1353
+ reject(error);
1354
+ }
1355
+ });
1356
+ });
1357
+ }
1358
+ function extractJsonFromCloudFoundryLogLine(line) {
1359
+ const jsonStart = line.indexOf("{");
1360
+ if (jsonStart < 0)
1361
+ return undefined;
1362
+ try {
1363
+ return JSON.parse(line.slice(jsonStart));
1364
+ }
1365
+ catch {
1366
+ return undefined;
1367
+ }
1368
+ }
1369
+ function parseHttpWatchAppLine(line) {
1370
+ if (!line.includes("[APP/") || !line.includes("OUT"))
1371
+ return undefined;
1372
+ const payload = extractJsonFromCloudFoundryLogLine(line);
1373
+ if (!payload)
1374
+ return undefined;
1375
+ const msg = String(payload.msg ?? "");
1376
+ const methodMatch = msg.match(/\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+([^\s{]+)/);
1377
+ if (!methodMatch)
1378
+ return undefined;
1379
+ return {
1380
+ source: "APP",
1381
+ method: methodMatch[1],
1382
+ url: methodMatch[2],
1383
+ requestId: String(payload.request_id ?? payload.x_vcap_request_id ?? payload.x_request_id ?? ""),
1384
+ correlationId: String(payload.correlation_id ?? payload.x_correlationid ?? payload.x_correlation_id ?? ""),
1385
+ instance: String(payload.x_cf_instanceindex ?? payload.component_instance ?? ""),
1386
+ user: String(payload.remote_user ?? ""),
1387
+ tenant: String(payload.tenant_subdomain ?? payload.tenantid ?? payload.tenant_id ?? ""),
1388
+ userAgent: String(payload.user_agent ?? ""),
1389
+ contentLength: String(payload.content_length ?? payload.request_size_b ?? ""),
1390
+ authorization: String(payload.authorization ?? ""),
1391
+ message: msg,
1392
+ };
1393
+ }
1394
+ function parseKeyValueFromRouterLine(line, key) {
1395
+ const regex = new RegExp(`${key}:"([^"]*)"`);
1396
+ return line.match(regex)?.[1];
1397
+ }
1398
+ function parseHttpWatchRouterLine(line) {
1399
+ if (!line.includes("[RTR/") || !line.includes("HTTP/"))
1400
+ return undefined;
1401
+ const requestMatch = line.match(/"(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+([^\s]+)\s+HTTP\/[^"]+"\s+(\d{3})\s+(\d+)\s+(\d+)/);
1402
+ if (!requestMatch)
1403
+ return undefined;
1404
+ const responseTimeSeconds = Number(line.match(/response_time:([0-9.]+)/)?.[1] ?? "");
1405
+ return {
1406
+ source: "RTR",
1407
+ method: requestMatch[1],
1408
+ url: requestMatch[2],
1409
+ status: requestMatch[3],
1410
+ requestBytes: requestMatch[4],
1411
+ responseBytes: requestMatch[5],
1412
+ durationMs: Number.isFinite(responseTimeSeconds) ? Math.round(responseTimeSeconds * 1000) : undefined,
1413
+ requestId: parseKeyValueFromRouterLine(line, "vcap_request_id"),
1414
+ correlationId: parseKeyValueFromRouterLine(line, "x_correlationid"),
1415
+ instance: line.match(/app_index:"([^"]*)"/)?.[1],
1416
+ tenant: parseKeyValueFromRouterLine(line, "tenantid"),
1417
+ userAgent: line.match(/"\s+"([^"]*)"\s+"[^\"]+:\d+"/)?.[1],
1418
+ };
1419
+ }
1420
+ function parseHttpWatchLine(line) {
1421
+ return parseHttpWatchAppLine(line) ?? parseHttpWatchRouterLine(line);
1422
+ }
1423
+ function formatHttpWatchEvent(appName, event) {
1424
+ const status = event.status ? chalk_1.default.green(String(event.status)) : chalk_1.default.gray("APP");
1425
+ const duration = event.durationMs !== undefined ? chalk_1.default.gray(`${event.durationMs}ms`) : "";
1426
+ const source = event.source === "RTR" ? chalk_1.default.magenta("RTR") : chalk_1.default.blue("APP");
1427
+ const requestId = event.requestId ? chalk_1.default.gray(` req=${event.requestId}`) : "";
1428
+ const instance = event.instance ? chalk_1.default.gray(` i=${event.instance}`) : "";
1429
+ const user = event.user ? chalk_1.default.gray(` user=${event.user}`) : "";
1430
+ const tenant = event.tenant ? chalk_1.default.gray(` tenant=${event.tenant}`) : "";
1431
+ const size = event.contentLength || event.requestBytes ? chalk_1.default.gray(` bytes=${event.contentLength || event.requestBytes}`) : "";
1432
+ const auth = event.authorization ? chalk_1.default.gray(` auth=${event.authorization}`) : "";
1433
+ return `${source} ${chalk_1.default.cyan(`[${appName}]`)} ${status} ${chalk_1.default.bold(event.method ?? "")} ${event.url ?? ""} ${duration}${instance}${user}${tenant}${size}${auth}${requestId}`.trim();
1434
+ }
1435
+ function printHttpWatchLine(appName, line, outputFile) {
1436
+ const event = parseHttpWatchLine(line);
1437
+ if (!event)
1438
+ return;
1439
+ const formatted = formatHttpWatchEvent(appName, event);
1440
+ console.log(formatted);
1441
+ if (outputFile) {
1442
+ const plain = formatted.replace(/\u001b\[[0-9;]*m/g, "");
1443
+ fs_extra_1.default.appendFileSync(outputFile, `${plain}\n`, "utf8");
1444
+ }
1445
+ }
1446
+ async function resolveHttpWatchApps(options) {
1447
+ if (options.app?.trim()) {
1448
+ return uniqueValues(options.app.split(","));
1449
+ }
1450
+ return resolveRequestTraceApps({ app: options.app, refresh: options.refresh });
1451
+ }
1452
+ async function runHttpWatchForApps(options) {
1453
+ if (!options.appNames.length)
1454
+ throw new Error("No app selected for HTTP watch");
1455
+ if (options.out) {
1456
+ await fs_extra_1.default.ensureDir(node_path_1.default.dirname(node_path_1.default.resolve(options.out)));
1457
+ await fs_extra_1.default.writeFile(options.out, "", "utf8");
1458
+ }
1459
+ if (options.recent) {
1460
+ for (const appName of options.appNames) {
1461
+ const result = await (0, process_1.runCommand)("cf", ["logs", appName, "--recent"]);
1462
+ const text = `${result.stdout}\n${result.stderr}`;
1463
+ for (const line of text.split(/\r?\n/)) {
1464
+ printHttpWatchLine(appName, line, options.out);
1465
+ }
1466
+ }
1467
+ return;
1468
+ }
1469
+ const children = [];
1470
+ const stopAll = () => {
1471
+ for (const child of children) {
1472
+ if (!child.killed)
1473
+ child.kill();
1474
+ }
1475
+ };
1476
+ process.once("SIGINT", () => {
1477
+ console.log(chalk_1.default.gray("\nStopping HTTP watch..."));
1478
+ stopAll();
1479
+ process.exit(0);
1480
+ });
1481
+ for (const appName of options.appNames) {
1482
+ const child = (0, node_child_process_1.spawn)("cf", ["logs", appName], {
1483
+ stdio: ["ignore", "pipe", "pipe"],
1484
+ shell: false,
1485
+ windowsHide: true,
1486
+ });
1487
+ children.push(child);
1488
+ child.stdout.on("data", (chunk) => {
1489
+ for (const line of chunk.toString("utf8").split(/\r?\n/)) {
1490
+ printHttpWatchLine(appName, line, options.out);
1491
+ }
1492
+ });
1493
+ child.stderr.on("data", (chunk) => {
1494
+ for (const line of chunk.toString("utf8").split(/\r?\n/)) {
1495
+ printHttpWatchLine(appName, line, options.out);
1496
+ }
1497
+ });
1498
+ }
1499
+ console.log(chalk_1.default.green(`HTTP watch is watching ${options.appNames.length} app(s).`));
1500
+ console.log(chalk_1.default.gray("This uses existing CF/CDS/RTR logs. It shows method/path/status/user/tenant/size, but not full request body or full token."));
1501
+ console.log(chalk_1.default.gray("Press Ctrl+C to stop."));
1502
+ await new Promise((resolve) => {
1503
+ let closedCount = 0;
1504
+ for (const child of children) {
1505
+ child.on("close", () => {
1506
+ closedCount += 1;
1507
+ if (closedCount >= children.length)
1508
+ resolve();
1509
+ });
1510
+ }
1511
+ });
1512
+ }
1513
+ async function runHttpWatchCommand(options) {
1514
+ if (!options.skipOrgSelect) {
1515
+ await maybeSwitchCloudFoundryTargetForDebug({ app: options.app, refresh: options.refresh, skipOrgSelect: false });
1516
+ }
1517
+ await ensureCloudFoundrySessionFromCache();
1518
+ const appNames = await resolveHttpWatchApps(options);
1519
+ await runHttpWatchForApps({ appNames, recent: options.recent, out: options.out });
1520
+ }
1521
+ async function runRequestTraceDoctorCommand(options) {
1522
+ await maybeSwitchCloudFoundryTargetForDebug({
1523
+ app: options.app,
1524
+ refresh: options.refresh,
1525
+ instance: options.instance,
1526
+ process: options.process,
1527
+ localPort: options.localPort,
1528
+ remotePort: options.remotePort,
1529
+ skipOrgSelect: options.skipOrgSelect,
1530
+ });
1531
+ await ensureCloudFoundrySessionFromCache();
1532
+ const appNames = await resolveRequestTraceApps({ app: options.app, refresh: options.refresh });
1533
+ const instanceIndex = await selectDebugInstance({ instance: options.instance });
1534
+ for (const appName of appNames) {
1535
+ console.log(chalk_1.default.cyan(`\nRequest trace doctor for ${appName} instance ${instanceIndex}`));
1536
+ console.log(chalk_1.default.gray("Recent router/app HTTP traffic:"));
1537
+ const result = await (0, process_1.runCommand)("cf", ["logs", appName, "--recent"]);
1538
+ const text = `${result.stdout}\n${result.stderr}`;
1539
+ let count = 0;
1540
+ for (const line of text.split(/\r?\n/)) {
1541
+ const event = parseHttpWatchLine(line);
1542
+ if (event) {
1543
+ count += 1;
1544
+ console.log(formatHttpWatchEvent(appName, event));
1545
+ if (count >= 10)
1546
+ break;
1547
+ }
1548
+ }
1549
+ if (!count) {
1550
+ console.log(chalk_1.default.yellow("No recent HTTP traffic found in CF logs for this app."));
1551
+ }
1552
+ console.log(chalk_1.default.gray("\nRemote process list:"));
1553
+ const processList = await (0, process_1.runCommand)("cf", ["ssh", appName, "-i", instanceIndex, "-T", "-c", "ps -eo pid,args 2>/dev/null | head -n 40"]);
1554
+ if (processList.stdout)
1555
+ console.log(processList.stdout);
1556
+ if (processList.stderr)
1557
+ console.error(processList.stderr);
1558
+ }
1559
+ console.log(chalk_1.default.yellow("\nDoctor summary:"));
1560
+ console.log("- If HTTP traffic appears above, the app is receiving requests.");
1561
+ console.log("- Full body/token are not available from CF/CDS logs because they are intentionally omitted or masked.");
1562
+ console.log("- Use smdg cf http-watch for stable live tracking.");
1563
+ console.log("- Use deep request-trace only when you accept Inspector/preload limitations in dev/test.");
1564
+ }
1565
+ function buildRequestTraceInjectionExpression(options) {
1566
+ const traceOptions = JSON.stringify(options);
1567
+ const source = `(() => {
1568
+ const options = ${traceOptions};
1569
+ const globalKey = "__SMDG_NETWORK_SPY__";
1570
+
1571
+ const state = globalThis[globalKey] || {
1572
+ installed: false,
1573
+ requestSeq: 0,
1574
+ options,
1575
+ patchedRequests: new WeakSet(),
1576
+ patchedResponses: new WeakSet(),
1577
+ patchedServers: new WeakSet(),
1578
+ activeRequests: new WeakMap(),
1579
+ };
1580
+
1581
+ state.options = options;
1582
+ globalThis[globalKey] = state;
1583
+
1584
+ function write(event) {
1585
+ try {
1586
+ console.log("SMDG_REQUEST_TRACE " + JSON.stringify(event));
1587
+ } catch (error) {
1588
+ console.log("SMDG_REQUEST_TRACE " + JSON.stringify({
1589
+ type: "smdg-request-trace-error",
1590
+ app: options.appName,
1591
+ message: error && error.message ? error.message : String(error),
1592
+ }));
1593
+ }
1594
+ }
1595
+
1596
+ function currentOptions() {
1597
+ return globalThis[globalKey] && globalThis[globalKey].options ? globalThis[globalKey].options : options;
1598
+ }
1599
+
1600
+ function shouldCaptureBody() {
1601
+ const mode = currentOptions().mode;
1602
+ return mode === "body" || mode === "response";
1603
+ }
1604
+
1605
+ function shouldCaptureResponse() {
1606
+ return currentOptions().mode === "response";
1607
+ }
1608
+
1609
+ function maxBodyBytes() {
1610
+ return Number(currentOptions().maxBodyBytes || 20000);
1611
+ }
1612
+
1613
+ function maskAuthorization(value) {
1614
+ if (!value) return undefined;
1615
+ const authMode = currentOptions().authMode;
1616
+ if (authMode === "omit") return undefined;
1617
+ if (authMode === "full") return String(value);
1618
+ const text = String(value);
1619
+ return text.length <= 24 ? "***" : text.slice(0, 16) + "..." + text.slice(-8);
1620
+ }
1621
+
1622
+ function normalizeHeaders(headers) {
1623
+ if (currentOptions().mode === "path") return undefined;
1624
+ const output = {};
1625
+ for (const [key, value] of Object.entries(headers || {})) {
1626
+ const lower = key.toLowerCase();
1627
+ if (lower === "authorization") {
1628
+ const auth = maskAuthorization(value);
1629
+ if (auth !== undefined) output[key] = auth;
1630
+ continue;
1631
+ }
1632
+ if (lower === "cookie" || lower === "set-cookie") {
1633
+ output[key] = "***";
1634
+ continue;
1635
+ }
1636
+ output[key] = value;
1637
+ }
1638
+ return output;
1639
+ }
1640
+
1641
+ function appendChunk(record, chunk) {
1642
+ if (!chunk || !shouldCaptureBody()) return;
1643
+ try {
1644
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
1645
+ record.requestBytes += buffer.length;
1646
+ const currentBytes = record.requestChunks.reduce((sum, item) => sum + item.length, 0);
1647
+ const limit = maxBodyBytes();
1648
+ if (currentBytes < limit) {
1649
+ record.requestChunks.push(buffer.subarray(0, Math.max(0, limit - currentBytes)));
1650
+ }
1651
+ } catch {}
1652
+ }
1653
+
1654
+ function appendResponseChunk(record, chunk) {
1655
+ if (!chunk || !shouldCaptureResponse()) return;
1656
+ try {
1657
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
1658
+ record.responseBytes += buffer.length;
1659
+ const currentBytes = record.responseChunks.reduce((sum, item) => sum + item.length, 0);
1660
+ const limit = maxBodyBytes();
1661
+ if (currentBytes < limit) {
1662
+ record.responseChunks.push(buffer.subarray(0, Math.max(0, limit - currentBytes)));
1663
+ }
1664
+ } catch {}
1665
+ }
1666
+
1667
+ function chunksToText(chunks) {
1668
+ try {
1669
+ if (!chunks || !chunks.length) return undefined;
1670
+ return Buffer.concat(chunks).toString("utf8");
1671
+ } catch {
1672
+ return undefined;
1673
+ }
1674
+ }
1675
+
1676
+ function tryParseContent(text, headers) {
1677
+ if (text === undefined) return undefined;
1678
+ if (!currentOptions().parseBodyJson) return text;
1679
+ const contentType = String((headers && (headers["content-type"] || headers["Content-Type"])) || "");
1680
+ if (contentType.includes("application/json") || /^[\\s]*[\\{\\[]/.test(text)) {
1681
+ try { return JSON.parse(text); } catch { return text; }
1682
+ }
1683
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1684
+ try { return Object.fromEntries(new URLSearchParams(text)); } catch { return text; }
1685
+ }
1686
+ return text;
1687
+ }
1688
+
1689
+ function getRequestUrl(req) {
1690
+ return req.originalUrl || req.url || req.path || "";
1691
+ }
1692
+
1693
+ function patchRequestAndResponse(req, res, source) {
1694
+ if (!req || !res || state.patchedRequests.has(req)) return false;
1695
+
1696
+ state.patchedRequests.add(req);
1697
+ const record = {
1698
+ id: ++state.requestSeq,
1699
+ source,
1700
+ startedAt: Date.now(),
1701
+ requestChunks: [],
1702
+ responseChunks: [],
1703
+ requestBytes: 0,
1704
+ responseBytes: 0,
1705
+ };
1706
+ state.activeRequests.set(req, record);
1707
+
1708
+ try {
1709
+ if (!req.__SMDG_NETWORK_SPY_PUSH_PATCHED__) {
1710
+ const originalPush = req.push;
1711
+ if (typeof originalPush === "function") {
1712
+ req.push = function smdgNetworkTraceRequestPush(chunk, encoding) {
1713
+ appendChunk(record, chunk);
1714
+ return originalPush.call(this, chunk, encoding);
1715
+ };
1716
+ Object.defineProperty(req, "__SMDG_NETWORK_SPY_PUSH_PATCHED__", { value: true, enumerable: false });
1717
+ }
1718
+ }
1719
+ } catch {}
1720
+
1721
+ try {
1722
+ const originalEmit = req.emit;
1723
+ if (typeof originalEmit === "function" && !req.__SMDG_NETWORK_SPY_EMIT_PATCHED__) {
1724
+ req.emit = function smdgNetworkTraceRequestEmit(eventName, chunk, ...args) {
1725
+ if (eventName === "data") appendChunk(record, chunk);
1726
+ return originalEmit.call(this, eventName, chunk, ...args);
1727
+ };
1728
+ Object.defineProperty(req, "__SMDG_NETWORK_SPY_EMIT_PATCHED__", { value: true, enumerable: false });
1729
+ }
1730
+ } catch {}
1731
+
1732
+ try {
1733
+ if (!state.patchedResponses.has(res)) {
1734
+ state.patchedResponses.add(res);
1735
+ const originalWrite = res.write;
1736
+ const originalEnd = res.end;
1737
+
1738
+ if (typeof originalWrite === "function") {
1739
+ res.write = function smdgNetworkTraceResponseWrite(chunk, ...args) {
1740
+ appendResponseChunk(record, chunk);
1741
+ return originalWrite.call(this, chunk, ...args);
1742
+ };
1743
+ }
1744
+
1745
+ if (typeof originalEnd === "function") {
1746
+ res.end = function smdgNetworkTraceResponseEnd(chunk, ...args) {
1747
+ appendResponseChunk(record, chunk);
1748
+ return originalEnd.call(this, chunk, ...args);
1749
+ };
1750
+ }
1751
+ }
1752
+ } catch {}
1753
+
1754
+ const finish = () => {
1755
+ try {
1756
+ const requestBodyText = chunksToText(record.requestChunks);
1757
+ const responseBodyText = chunksToText(record.responseChunks);
1758
+ const headers = req.headers || {};
1759
+ const event = {
1760
+ type: "smdg-request-trace",
1761
+ app: currentOptions().appName,
1762
+ source: record.source,
1763
+ id: record.id,
1764
+ timestamp: new Date(record.startedAt).toISOString(),
1765
+ method: req.method,
1766
+ url: getRequestUrl(req),
1767
+ status: res.statusCode,
1768
+ durationMs: Date.now() - record.startedAt,
1769
+ requestBytes: record.requestBytes,
1770
+ responseBytes: record.responseBytes,
1771
+ headers: normalizeHeaders(headers),
1772
+ body: shouldCaptureBody() ? tryParseContent(requestBodyText, headers) : undefined,
1773
+ responseBody: shouldCaptureResponse() ? tryParseContent(responseBodyText, res.getHeaders ? res.getHeaders() : {}) : undefined,
1774
+ };
1775
+ write(event);
1776
+ } catch (error) {
1777
+ write({
1778
+ type: "smdg-request-trace-error",
1779
+ app: currentOptions().appName,
1780
+ message: error && error.message ? error.message : String(error),
1781
+ });
1782
+ }
1783
+ };
1784
+
1785
+ if (typeof res.once === "function") {
1786
+ res.once("finish", finish);
1787
+ res.once("close", () => {
1788
+ if (!res.writableEnded) finish();
1789
+ });
1790
+ }
1791
+
1792
+ return true;
1793
+ }
1794
+
1795
+ function installDiagnosticsChannelHook() {
1796
+ try {
1797
+ const diagnostics = require("diagnostics_channel");
1798
+ if (!diagnostics || diagnostics.__SMDG_NETWORK_SPY_PATCHED__) return false;
1799
+ const requestStart = diagnostics.channel("http.server.request.start");
1800
+ requestStart.subscribe((message) => {
1801
+ const req = message && (message.request || message.req);
1802
+ const res = message && (message.response || message.res);
1803
+ patchRequestAndResponse(req, res, "diagnostics_channel:http.server.request.start");
1804
+ });
1805
+ Object.defineProperty(diagnostics, "__SMDG_NETWORK_SPY_PATCHED__", { value: true, enumerable: false });
1806
+ return true;
1807
+ } catch {
1808
+ return false;
1809
+ }
1810
+ }
1811
+
1812
+ function installServerEmitHook() {
1813
+ try {
1814
+ const http = require("http");
1815
+ const Server = http && http.Server;
1816
+ if (!Server || !Server.prototype || Server.prototype.__SMDG_NETWORK_SPY_EMIT_PATCHED__) return false;
1817
+ const originalEmit = Server.prototype.emit;
1818
+ Server.prototype.emit = function smdgNetworkTraceServerEmit(eventName, req, res, ...args) {
1819
+ if (eventName === "request") patchRequestAndResponse(req, res, "http.Server.emit");
1820
+ return originalEmit.call(this, eventName, req, res, ...args);
1821
+ };
1822
+ Object.defineProperty(Server.prototype, "__SMDG_NETWORK_SPY_EMIT_PATCHED__", { value: true, enumerable: false });
1823
+ return true;
1824
+ } catch {
1825
+ return false;
1826
+ }
1827
+ }
1828
+
1829
+ function installCreateServerHook(moduleName) {
1830
+ try {
1831
+ const mod = require(moduleName);
1832
+ if (!mod || mod.__SMDG_NETWORK_SPY_CREATE_SERVER_PATCHED__) return false;
1833
+ const originalCreateServer = mod.createServer;
1834
+ if (typeof originalCreateServer !== "function") return false;
1835
+ mod.createServer = function smdgNetworkTraceCreateServer(...args) {
1836
+ const server = originalCreateServer.apply(this, args);
1837
+ hookServer(server, moduleName + ".createServer");
1838
+ return server;
1839
+ };
1840
+ Object.defineProperty(mod, "__SMDG_NETWORK_SPY_CREATE_SERVER_PATCHED__", { value: true, enumerable: false });
1841
+ return true;
1842
+ } catch {
1843
+ return false;
1844
+ }
1845
+ }
1846
+
1847
+ function hookServer(server, source) {
1848
+ try {
1849
+ if (!server || state.patchedServers.has(server)) return false;
1850
+ if (typeof server.prependListener === "function") {
1851
+ server.prependListener("request", (req, res) => patchRequestAndResponse(req, res, source));
1852
+ state.patchedServers.add(server);
1853
+ return true;
1854
+ }
1855
+ } catch {}
1856
+ return false;
1857
+ }
1858
+
1859
+ function hookActiveServers() {
1860
+ let count = 0;
1861
+ try {
1862
+ const handles = typeof process._getActiveHandles === "function" ? process._getActiveHandles() : [];
1863
+ for (const handle of handles) {
1864
+ if (handle && typeof handle.on === "function" && typeof handle.address === "function") {
1865
+ if (hookServer(handle, "active-handle")) count += 1;
1866
+ }
1867
+ }
1868
+ } catch {}
1869
+ return count;
1870
+ }
1871
+
1872
+ const diagnosticsHooked = installDiagnosticsChannelHook();
1873
+ const serverEmitHooked = installServerEmitHook();
1874
+ const httpCreateHooked = installCreateServerHook("http");
1875
+ const httpsCreateHooked = installCreateServerHook("https");
1876
+ const activeServers = hookActiveServers();
1877
+
1878
+ state.installed = true;
1879
+ state.installedAt = state.installedAt || new Date().toISOString();
1880
+
1881
+ write({
1882
+ type: "smdg-request-trace-status",
1883
+ app: options.appName,
1884
+ status: "installed",
1885
+ engine: "network-trace-v4",
1886
+ diagnosticsHooked,
1887
+ serverEmitHooked,
1888
+ httpCreateHooked,
1889
+ httpsCreateHooked,
1890
+ activeServers,
1891
+ mode: options.mode,
1892
+ authMode: options.authMode,
1893
+ maxBodyBytes: options.maxBodyBytes,
1894
+ });
1895
+
1896
+ return "installed:network-trace-v4:" + activeServers;
1897
+ })();`;
1898
+ return source;
1899
+ }
1900
+ async function selectRequestTraceMode() {
1901
+ return (0, prompts_2.searchableSelectChoice)({
1902
+ message: "Select request trace mode",
1903
+ choices: [
1904
+ { title: "Path only", value: "path", description: "method, URL, status, duration" },
1905
+ { title: "Headers", value: "headers", description: "include request headers, mask sensitive values" },
1906
+ { title: "Headers + body", value: "body", description: "include request body up to a safe size limit" },
1907
+ { title: "Headers + body + response", value: "response", description: "include request body and response body" },
1908
+ ],
1909
+ allowCustomValue: false,
1910
+ });
1911
+ }
1912
+ async function selectRequestTraceAuthMode() {
1913
+ return (0, prompts_2.searchableSelectChoice)({
1914
+ message: "Authorization header handling",
1915
+ choices: [
1916
+ { title: "Mask token (recommended)", value: "mask" },
1917
+ { title: "Show full token (dev/test only)", value: "full" },
1918
+ { title: "Omit Authorization header", value: "omit" },
1919
+ ],
1920
+ allowCustomValue: false,
1921
+ });
1922
+ }
1923
+ async function selectRequestTraceDisplayOptions(options) {
1924
+ const headerPreset = await (0, prompts_2.searchableSelectChoice)({
1925
+ message: "Headers to display in terminal",
1926
+ choices: [
1927
+ { title: "Minimal headers", value: "minimal", description: "host, content-type, authorization, request/correlation ids" },
1928
+ { title: "Common debug headers", value: "common", description: "minimal + user-agent, origin, forwarded, CF/B3 headers" },
1929
+ { title: "All captured headers", value: "all", description: "large output" },
1930
+ { title: "Custom header list", value: "custom", description: "enter comma-separated headers" },
1931
+ ],
1932
+ allowCustomValue: false,
1933
+ });
1934
+ let headerNames = [];
1935
+ if (headerPreset === "minimal")
1936
+ headerNames = getMinimalTraceHeaderNames();
1937
+ if (headerPreset === "common")
1938
+ headerNames = getCommonTraceHeaderNames();
1939
+ if (headerPreset === "custom") {
1940
+ const response = await (0, prompts_1.default)({
1941
+ type: "text",
1942
+ name: "headers",
1943
+ message: "Header names to display",
1944
+ initial: "authorization,content-type,content-length,x-correlationid,x-vcap-request-id,tenantid,user-agent",
1945
+ validate: (value) => value.trim() ? true : "At least one header is required",
1946
+ });
1947
+ headerNames = String(response.headers ?? "").split(",").map((item) => item.trim()).filter(Boolean);
1948
+ }
1949
+ const parseResponse = await (0, prompts_1.default)({
1950
+ type: "select",
1951
+ name: "parseBodyJson",
1952
+ message: "Try parse request/response body as JSON when possible?",
1953
+ choices: [
1954
+ { title: "Yes, parse JSON/form body when possible", value: true },
1955
+ { title: "No, keep raw body string", value: false },
1956
+ ],
1957
+ initial: 0,
1958
+ });
1959
+ let outputFile = options.out;
1960
+ if (!outputFile) {
1961
+ const outResponse = await (0, prompts_1.default)({
1962
+ type: "select",
1963
+ name: "export",
1964
+ message: "Export captured trace events to JSONL file?",
1965
+ choices: [
1966
+ { title: "No", value: false },
1967
+ { title: "Yes", value: true },
1968
+ ],
1969
+ initial: 0,
1970
+ });
1971
+ if (outResponse.export) {
1972
+ const fileResponse = await (0, prompts_1.default)({
1973
+ type: "text",
1974
+ name: "file",
1975
+ message: "Trace output file",
1976
+ initial: `smdg-request-trace-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`,
1977
+ validate: (value) => value.trim() ? true : "Output file is required",
1978
+ });
1979
+ outputFile = String(fileResponse.file ?? "").trim();
1980
+ }
1981
+ }
1982
+ if (outputFile) {
1983
+ await fs_extra_1.default.ensureDir(node_path_1.default.dirname(node_path_1.default.resolve(outputFile)));
1984
+ await fs_extra_1.default.writeFile(outputFile, "", "utf8");
1985
+ console.log(chalk_1.default.green(`Trace events will be exported to ${node_path_1.default.resolve(outputFile)}`));
1986
+ }
1987
+ return {
1988
+ headerPreset,
1989
+ headerNames,
1990
+ parseBodyJson: Boolean(parseResponse.parseBodyJson),
1991
+ outputFile,
1992
+ };
1993
+ }
1994
+ async function resolveRequestTraceApps(options) {
1995
+ if (options.app?.trim()) {
1996
+ return uniqueValues(options.app.split(","));
1997
+ }
1998
+ const apps = await getAppsWithCache({ refresh: options.refresh, startBackgroundRefresh: !options.refresh });
1999
+ const selectedApps = [];
2000
+ while (true) {
2001
+ const appName = await (0, prompts_2.searchableSelectChoice)({
2002
+ message: selectedApps.length ? "Add another BTP app to trace, or finish" : "Search/select BTP app to trace",
2003
+ choices: [
2004
+ ...apps
2005
+ .filter((app) => !selectedApps.includes(app.name))
2006
+ .map((app) => ({
2007
+ title: [app.name, app.requestedState, app.routes].filter(Boolean).join(" | "),
2008
+ value: app.name,
2009
+ })),
2010
+ ...(selectedApps.length ? [{ title: "Done", value: "__DONE__" }] : []),
2011
+ ],
2012
+ validateCustomValue: validateRequired,
2013
+ customValueTitle: (value) => `Use typed app name: ${value}`,
2014
+ });
2015
+ if (appName === "__DONE__") {
2016
+ break;
2017
+ }
2018
+ selectedApps.push(appName);
2019
+ await (0, cache_1.rememberSelectedApp)(appName);
2020
+ const moreResponse = await (0, prompts_1.default)({
2021
+ type: "select",
2022
+ name: "more",
2023
+ message: "Trace another app at the same time?",
2024
+ choices: [
2025
+ { title: "No, start tracing now", value: false },
2026
+ { title: "Yes, add another app", value: true },
2027
+ ],
2028
+ initial: 0,
2029
+ });
2030
+ if (!moreResponse.more) {
2031
+ break;
2032
+ }
2033
+ }
2034
+ return selectedApps;
2035
+ }
2036
+ function getMinimalTraceHeaderNames() {
2037
+ return [
2038
+ "host",
2039
+ "content-type",
2040
+ "content-length",
2041
+ "authorization",
2042
+ "x-correlation-id",
2043
+ "x-correlationid",
2044
+ "x-vcap-request-id",
2045
+ "tenantid",
2046
+ ];
2047
+ }
2048
+ function getCommonTraceHeaderNames() {
2049
+ return [
2050
+ ...getMinimalTraceHeaderNames(),
2051
+ "user-agent",
2052
+ "origin",
2053
+ "referer",
2054
+ "x-forwarded-for",
2055
+ "x-forwarded-host",
2056
+ "x-forwarded-path",
2057
+ "x-forwarded-proto",
2058
+ "x-cf-applicationid",
2059
+ "x-cf-instanceindex",
2060
+ "x-cf-true-client-ip",
2061
+ "x-b3-traceid",
2062
+ "x-b3-spanid",
2063
+ "b3",
2064
+ ];
2065
+ }
2066
+ function normalizeHeaderName(value) {
2067
+ return value.trim().toLowerCase();
2068
+ }
2069
+ function filterTraceHeaders(headers, display) {
2070
+ if (!headers || typeof headers !== "object")
2071
+ return undefined;
2072
+ const source = headers;
2073
+ if (display.headerPreset === "all")
2074
+ return source;
2075
+ const names = new Set(display.headerNames.map(normalizeHeaderName));
2076
+ const output = {};
2077
+ for (const [key, value] of Object.entries(source)) {
2078
+ if (names.has(normalizeHeaderName(key)))
2079
+ output[key] = value;
2080
+ }
2081
+ return output;
2082
+ }
2083
+ function stringifyTraceValue(value) {
2084
+ if (value === undefined || value === null)
2085
+ return "";
2086
+ if (typeof value === "string")
2087
+ return value;
2088
+ try {
2089
+ return JSON.stringify(value);
2090
+ }
2091
+ catch {
2092
+ return String(value);
2093
+ }
2094
+ }
2095
+ function traceEventMatchesFilters(event, filters) {
2096
+ if (filters.paused)
2097
+ return false;
2098
+ const method = String(event.method ?? "").toLowerCase();
2099
+ const url = String(event.url ?? "").toLowerCase();
2100
+ const status = String(event.status ?? "").toLowerCase();
2101
+ const body = stringifyTraceValue(event.body).toLowerCase();
2102
+ const responseBody = stringifyTraceValue(event.responseBody).toLowerCase();
2103
+ const all = stringifyTraceValue(event).toLowerCase();
2104
+ if (filters.method && method !== filters.method.toLowerCase())
2105
+ return false;
2106
+ if (filters.path && !url.includes(filters.path.toLowerCase()))
2107
+ return false;
2108
+ if (filters.status && !status.includes(filters.status.toLowerCase()))
2109
+ return false;
2110
+ if (filters.body && !body.includes(filters.body.toLowerCase()) && !responseBody.includes(filters.body.toLowerCase()))
2111
+ return false;
2112
+ if (filters.text && !all.includes(filters.text.toLowerCase()))
2113
+ return false;
2114
+ return true;
2115
+ }
2116
+ function buildPrintableTracePayload(event, display) {
2117
+ const output = {
2118
+ type: event.type,
2119
+ app: event.app,
2120
+ source: event.source,
2121
+ id: event.id,
2122
+ timestamp: event.timestamp,
2123
+ method: event.method,
2124
+ url: event.url,
2125
+ status: event.status,
2126
+ durationMs: event.durationMs,
2127
+ requestBytes: event.requestBytes,
2128
+ responseBytes: event.responseBytes,
2129
+ };
2130
+ const headers = filterTraceHeaders(event.headers, display);
2131
+ if (headers && Object.keys(headers).length > 0)
2132
+ output.headers = headers;
2133
+ if (event.body !== undefined)
2134
+ output.body = event.body;
2135
+ if (event.responseBody !== undefined)
2136
+ output.responseBody = event.responseBody;
2137
+ return output;
2138
+ }
2139
+ function writeTraceEventToFile(outputFile, event) {
2140
+ if (!outputFile)
2141
+ return;
2142
+ try {
2143
+ fs_extra_1.default.appendFileSync(outputFile, `${JSON.stringify(event)}\n`, "utf8");
2144
+ }
2145
+ catch (error) {
2146
+ console.error(chalk_1.default.yellow(`Failed to write trace event to file: ${error instanceof Error ? error.message : String(error)}`));
2147
+ }
2148
+ }
2149
+ function printRequestTraceEvent(appName, payload, runtime) {
2150
+ const type = String(payload.type ?? "smdg-request-trace");
2151
+ if (type === "smdg-request-trace-status") {
2152
+ console.log(chalk_1.default.green(`[${appName}] ${String(payload.status ?? "trace-status")}`));
2153
+ console.log(chalk_1.default.gray(`engine=${String(payload.engine ?? "unknown")} activeServers=${String(payload.activeServers ?? "?")} mode=${String(payload.mode ?? "")}`));
2154
+ return;
2155
+ }
2156
+ if (type === "smdg-request-trace-error") {
2157
+ console.log(chalk_1.default.red(`[${appName}] trace error: ${String(payload.message ?? "unknown")}`));
2158
+ return;
2159
+ }
2160
+ runtime.events.push(payload);
2161
+ writeTraceEventToFile(runtime.display.outputFile, payload);
2162
+ if (!traceEventMatchesFilters(payload, runtime.filters))
2163
+ return;
2164
+ const time = String(payload.timestamp ?? new Date().toISOString());
2165
+ const method = String(payload.method ?? "");
2166
+ const url = String(payload.url ?? "");
2167
+ const status = String(payload.status ?? "");
2168
+ const duration = String(payload.durationMs ?? "");
2169
+ console.log(chalk_1.default.cyan(`\n[${time}] [${appName}] ${method} ${url} → ${status} ${duration}ms`));
2170
+ console.log(JSON.stringify(buildPrintableTracePayload(payload, runtime.display), null, 2));
2171
+ }
2172
+ function printRequestTraceLine(appName, line, runtime) {
2173
+ const marker = line.includes("SMDG_REQUEST_TRACE ") ? "SMDG_REQUEST_TRACE " : line.includes("SMDG_REQUEST_SPY ") ? "SMDG_REQUEST_SPY " : undefined;
2174
+ if (!marker)
2175
+ return;
2176
+ const markerIndex = line.indexOf(marker);
2177
+ const payloadText = line.slice(markerIndex + marker.length).trim();
2178
+ try {
2179
+ const payload = JSON.parse(payloadText);
2180
+ printRequestTraceEvent(appName, payload, runtime);
2181
+ }
2182
+ catch {
2183
+ console.log(`[${appName}] ${payloadText}`);
2184
+ }
2185
+ }
2186
+ function printTraceRuntimeHelp() {
2187
+ console.log(chalk_1.default.gray("\nRuntime trace commands:"));
2188
+ console.log(chalk_1.default.gray(" /method POST show only one method"));
2189
+ console.log(chalk_1.default.gray(" /path text show only URLs containing text"));
2190
+ console.log(chalk_1.default.gray(" /body text show only request/response body containing text"));
2191
+ console.log(chalk_1.default.gray(" /status 500 show only status containing value"));
2192
+ console.log(chalk_1.default.gray(" /text value search anywhere in the event"));
2193
+ console.log(chalk_1.default.gray(" /headers a,b,c change displayed headers while running"));
2194
+ console.log(chalk_1.default.gray(" /headers all display all captured headers"));
2195
+ console.log(chalk_1.default.gray(" /clear clear active filters"));
2196
+ console.log(chalk_1.default.gray(" /show show active filters"));
2197
+ console.log(chalk_1.default.gray(" /replay print matching events already captured"));
2198
+ console.log(chalk_1.default.gray(" /pause or /resume pause/resume terminal display"));
2199
+ console.log(chalk_1.default.gray(" /help show this help"));
2200
+ }
2201
+ function applyTraceRuntimeCommand(input, runtime) {
2202
+ const trimmed = input.trim();
2203
+ if (!trimmed)
2204
+ return;
2205
+ if (!trimmed.startsWith("/")) {
2206
+ runtime.filters.text = trimmed;
2207
+ console.log(chalk_1.default.yellow(`Search text filter: ${trimmed}`));
2208
+ return;
2209
+ }
2210
+ const [commandRaw, ...restParts] = trimmed.slice(1).split(" ");
2211
+ const command = commandRaw.toLowerCase();
2212
+ const value = restParts.join(" ").trim();
2213
+ if (command === "method")
2214
+ runtime.filters.method = value || undefined;
2215
+ else if (command === "path")
2216
+ runtime.filters.path = value || undefined;
2217
+ else if (command === "body")
2218
+ runtime.filters.body = value || undefined;
2219
+ else if (command === "status")
2220
+ runtime.filters.status = value || undefined;
2221
+ else if (command === "text")
2222
+ runtime.filters.text = value || undefined;
2223
+ else if (command === "pause")
2224
+ runtime.filters.paused = true;
2225
+ else if (command === "resume")
2226
+ runtime.filters.paused = false;
2227
+ else if (command === "clear") {
2228
+ runtime.filters.method = undefined;
2229
+ runtime.filters.path = undefined;
2230
+ runtime.filters.body = undefined;
2231
+ runtime.filters.status = undefined;
2232
+ runtime.filters.text = undefined;
2233
+ runtime.filters.paused = false;
2234
+ }
2235
+ else if (command === "headers") {
2236
+ if (!value || value.toLowerCase() === "common") {
2237
+ runtime.display.headerPreset = "common";
2238
+ runtime.display.headerNames = getCommonTraceHeaderNames();
2239
+ }
2240
+ else if (value.toLowerCase() === "minimal") {
2241
+ runtime.display.headerPreset = "minimal";
2242
+ runtime.display.headerNames = getMinimalTraceHeaderNames();
2243
+ }
2244
+ else if (value.toLowerCase() === "all") {
2245
+ runtime.display.headerPreset = "all";
2246
+ runtime.display.headerNames = [];
2247
+ }
2248
+ else {
2249
+ runtime.display.headerPreset = "custom";
2250
+ runtime.display.headerNames = value.split(",").map((item) => item.trim()).filter(Boolean);
2251
+ }
2252
+ }
2253
+ else if (command === "show") {
2254
+ console.log(chalk_1.default.gray(JSON.stringify({ filters: runtime.filters, display: runtime.display, captured: runtime.events.length }, null, 2)));
2255
+ return;
2256
+ }
2257
+ else if (command === "replay") {
2258
+ console.log(chalk_1.default.gray(`Replaying ${runtime.events.length} captured event(s) with current filters...`));
2259
+ for (const event of runtime.events) {
2260
+ if (traceEventMatchesFilters(event, runtime.filters)) {
2261
+ const appName = String(event.app ?? "app");
2262
+ const time = String(event.timestamp ?? "");
2263
+ console.log(chalk_1.default.cyan(`\n[${time}] [${appName}] ${String(event.method ?? "")} ${String(event.url ?? "")} → ${String(event.status ?? "")} ${String(event.durationMs ?? "")}ms`));
2264
+ console.log(JSON.stringify(buildPrintableTracePayload(event, runtime.display), null, 2));
2265
+ }
2266
+ }
2267
+ return;
2268
+ }
2269
+ else if (command === "help" || command === "?") {
2270
+ printTraceRuntimeHelp();
2271
+ return;
2272
+ }
2273
+ else {
2274
+ console.log(chalk_1.default.yellow(`Unknown runtime command: ${command}`));
2275
+ printTraceRuntimeHelp();
2276
+ return;
2277
+ }
2278
+ console.log(chalk_1.default.yellow(`Trace runtime updated: ${trimmed}`));
2279
+ }
2280
+ function attachTraceRuntimeCommands(runtime) {
2281
+ printTraceRuntimeHelp();
2282
+ process.stdin.setEncoding("utf8");
2283
+ process.stdin.resume();
2284
+ process.stdin.on("data", (chunk) => {
2285
+ for (const line of chunk.split(/\r?\n/)) {
2286
+ applyTraceRuntimeCommand(line, runtime);
2287
+ }
2288
+ });
2289
+ }
2290
+ function startRequestTraceLogStream(appName, runtime) {
2291
+ const childProcess = (0, node_child_process_1.spawn)("cf", ["logs", appName], {
2292
+ stdio: ["ignore", "pipe", "pipe"],
2293
+ shell: false,
2294
+ windowsHide: true,
2295
+ });
2296
+ childProcess.stdout.on("data", (chunk) => {
2297
+ const lines = chunk.toString("utf8").split(/\r?\n/);
2298
+ for (const line of lines)
2299
+ printRequestTraceLine(appName, line, runtime);
2300
+ });
2301
+ childProcess.stderr.on("data", (chunk) => {
2302
+ const text = chunk.toString("utf8");
2303
+ if (/SMDG_REQUEST_(TRACE|SPY)/.test(text)) {
2304
+ for (const line of text.split(/\r?\n/))
2305
+ printRequestTraceLine(appName, line, runtime);
2306
+ }
2307
+ });
2308
+ return childProcess;
2309
+ }
2310
+ async function runRequestTraceCommand(options) {
2311
+ await maybeSwitchCloudFoundryTargetForDebug({
2312
+ app: options.app,
2313
+ refresh: options.refresh,
2314
+ instance: options.instance,
2315
+ process: options.process,
2316
+ localPort: options.localPort,
2317
+ remotePort: options.remotePort,
2318
+ skipOrgSelect: options.skipOrgSelect,
2319
+ });
2320
+ await ensureCloudFoundrySessionFromCache();
2321
+ const appNames = await resolveRequestTraceApps(options);
2322
+ if (!appNames.length) {
2323
+ throw new Error("No app selected for request trace");
2324
+ }
2325
+ const engine = await (0, prompts_2.searchableSelectChoice)({
2326
+ message: "Select request trace engine",
2327
+ choices: [
2328
+ {
2329
+ title: "HTTP watch from existing CF/CDS logs (recommended, stable)",
2330
+ value: "http-watch",
2331
+ description: "Shows method/path/status/user/tenant/size. No restart and no source-code change.",
2332
+ },
2333
+ {
2334
+ title: "Deep Node Inspector trace (experimental body capture)",
2335
+ value: "inspector-trace",
2336
+ description: "Attempts runtime injection. May not work for every CAP runtime. Dev/test only.",
2337
+ },
2338
+ {
2339
+ title: "Doctor: verify traffic, process, and limits",
2340
+ value: "doctor",
2341
+ },
2342
+ ],
2343
+ allowCustomValue: false,
2344
+ });
2345
+ if (engine === "http-watch") {
2346
+ await runHttpWatchForApps({ appNames, recent: false, out: undefined });
2347
+ return;
2348
+ }
2349
+ if (engine === "doctor") {
2350
+ await runRequestTraceDoctorCommand({ ...options, app: appNames.join(",") });
2351
+ return;
2352
+ }
2353
+ const traceMode = await selectRequestTraceMode();
2354
+ const authMode = await selectRequestTraceAuthMode();
2355
+ const displayOptions = await selectRequestTraceDisplayOptions(options);
2356
+ const runtime = {
2357
+ display: displayOptions,
2358
+ filters: { paused: false },
2359
+ events: [],
2360
+ };
2361
+ const instanceIndex = await selectDebugInstance({ instance: options.instance });
2362
+ const baseLocalPort = await selectDebugPort({
2363
+ value: options.localPort,
2364
+ message: "Select first local inspector port for request trace",
2365
+ defaultPort: 9329,
2366
+ });
2367
+ const remotePort = parsePositivePort(options.remotePort, 9229);
2368
+ const maxBodyBytes = parsePositivePort(options.maxBodyBytes, 20000);
2369
+ console.log("");
2370
+ console.log(chalk_1.default.yellow("Request trace attaches to the running Node.js app through Node Inspector."));
2371
+ console.log(chalk_1.default.gray("It does not modify your repository source code. It is temporary and disappears after app restart."));
2372
+ const prepareMode = await selectNodeInspectorPrepareMode({ appName: appNames.join(", "), remotePort });
2373
+ const tunnelProcesses = [];
2374
+ const logProcesses = [];
2375
+ const stopAll = () => {
2376
+ for (const child of [...tunnelProcesses, ...logProcesses]) {
2377
+ if (!child.killed)
2378
+ child.kill();
2379
+ }
2380
+ };
2381
+ process.once("SIGINT", () => {
2382
+ console.log(chalk_1.default.gray("\nStopping request trace..."));
2383
+ stopAll();
2384
+ process.exit(0);
2385
+ });
2386
+ for (const [index, appName] of appNames.entries()) {
2387
+ const localPort = baseLocalPort + index;
2388
+ await ensureSshEnabledForDebug(appName);
2389
+ if (prepareMode === "set-env-restart") {
2390
+ await setNodeInspectorEnvironmentAndRestart({ appName, remotePort });
2391
+ }
2392
+ console.log(chalk_1.default.gray(`Opening inspector tunnel for ${appName}: localhost:${localPort} -> 127.0.0.1:${remotePort}`));
2393
+ const tunnelProcess = (0, node_child_process_1.spawn)("cf", buildCloudFoundryDebugSshArgs({
2394
+ appName,
2395
+ instanceIndex,
2396
+ processName: options.process,
2397
+ localPort,
2398
+ remotePort,
2399
+ prepareMode,
2400
+ }), {
2401
+ stdio: ["ignore", "pipe", "pipe"],
2402
+ shell: false,
2403
+ windowsHide: true,
2404
+ });
2405
+ tunnelProcesses.push(tunnelProcess);
2406
+ tunnelProcess.stdout.on("data", (chunk) => process.stdout.write(chalk_1.default.gray(`[${appName}:ssh] ${chunk.toString("utf8")}`)));
2407
+ tunnelProcess.stderr.on("data", (chunk) => process.stderr.write(chalk_1.default.yellow(`[${appName}:ssh] ${chunk.toString("utf8")}`)));
2408
+ const webSocketUrl = await waitForNodeInspectorWebSocketUrl(localPort, 20000);
2409
+ if (!webSocketUrl) {
2410
+ console.log(chalk_1.default.red(`Cannot reach Node Inspector for ${appName} on localhost:${localPort}.`));
2411
+ console.log(chalk_1.default.yellow("Try again and choose: Set NODE_OPTIONS and restart app."));
2412
+ continue;
2413
+ }
2414
+ const expression = buildRequestTraceInjectionExpression({
2415
+ appName,
2416
+ mode: traceMode,
2417
+ authMode,
2418
+ maxBodyBytes,
2419
+ parseBodyJson: displayOptions.parseBodyJson,
2420
+ });
2421
+ await sendInspectorEvaluateCommand({ webSocketUrl, expression });
2422
+ console.log(chalk_1.default.green(`Request trace injected into ${appName}.`));
2423
+ const logProcess = startRequestTraceLogStream(appName, runtime);
2424
+ logProcesses.push(logProcess);
2425
+ }
2426
+ console.log("");
2427
+ console.log(chalk_1.default.green(`Request trace is watching ${appNames.length} app(s).`));
2428
+ console.log(chalk_1.default.gray("Send requests to your services. Type /help for runtime search commands. Press Ctrl+C to stop tunnels and log streams."));
2429
+ attachTraceRuntimeCommands(runtime);
2430
+ await new Promise((resolve) => {
2431
+ const watchedProcesses = [...tunnelProcesses, ...logProcesses];
2432
+ let closedCount = 0;
2433
+ for (const child of watchedProcesses) {
2434
+ child.on("close", () => {
2435
+ closedCount += 1;
2436
+ if (closedCount >= watchedProcesses.length)
2437
+ resolve();
2438
+ });
2439
+ }
2440
+ });
2441
+ }
1054
2442
  async function runDebugCommand(options) {
1055
2443
  await maybeSwitchCloudFoundryTargetForDebug(options);
2444
+ await ensureCloudFoundrySessionFromCache();
1056
2445
  const appName = await resolveAppSelection({
1057
2446
  app: options.app,
1058
2447
  refresh: options.refresh,
@@ -1066,9 +2455,9 @@ async function runDebugCommand(options) {
1066
2455
  defaultPort: 9229,
1067
2456
  });
1068
2457
  const remotePort = parsePositivePort(options.remotePort, 9229);
1069
- const repositoryPath = await resolveRepositoryPath(process.cwd()).catch(() => process.cwd());
2458
+ const repositoryPath = await (0, repository_1.resolveRepositoryPath)(process.cwd()).catch(() => process.cwd());
1070
2459
  if (debugMode === "check-ssh") {
1071
- const result = await runCommand("cf", ["ssh-enabled", appName]);
2460
+ const result = await (0, process_1.runCommand)("cf", ["ssh-enabled", appName]);
1072
2461
  if (result.stdout)
1073
2462
  console.log(result.stdout);
1074
2463
  if (result.stderr)
@@ -1077,7 +2466,7 @@ async function runDebugCommand(options) {
1077
2466
  return;
1078
2467
  }
1079
2468
  if (debugMode === "enable-ssh") {
1080
- const result = await runCommand("cf", ["enable-ssh", appName]);
2469
+ const result = await (0, process_1.runCommand)("cf", ["enable-ssh", appName]);
1081
2470
  if (result.stdout)
1082
2471
  console.log(result.stdout);
1083
2472
  if (result.stderr)
@@ -1086,7 +2475,7 @@ async function runDebugCommand(options) {
1086
2475
  process.exitCode = result.exitCode;
1087
2476
  return;
1088
2477
  }
1089
- const restartResponse = await prompts({
2478
+ const restartResponse = await (0, prompts_1.default)({
1090
2479
  type: "select",
1091
2480
  name: "restart",
1092
2481
  message: "Restart app now so SSH setting takes effect?",
@@ -1097,11 +2486,11 @@ async function runDebugCommand(options) {
1097
2486
  initial: 0,
1098
2487
  });
1099
2488
  if (restartResponse.restart) {
1100
- const restartExitCode = await runCommandInherit("cf", ["restart", appName]);
2489
+ const restartExitCode = await (0, process_1.runCommandInherit)("cf", ["restart", appName]);
1101
2490
  process.exitCode = restartExitCode;
1102
2491
  return;
1103
2492
  }
1104
- console.log(chalk.yellow(`SSH was enabled. Restart the app before debugging: cf restart ${appName}`));
2493
+ console.log(chalk_1.default.yellow(`SSH was enabled. Restart the app before debugging: cf restart ${appName}`));
1105
2494
  return;
1106
2495
  }
1107
2496
  let launchJsonPath;
@@ -1112,8 +2501,8 @@ async function runDebugCommand(options) {
1112
2501
  localPort,
1113
2502
  remoteRoot: "/home/vcap/app",
1114
2503
  });
1115
- console.log(chalk.green(`Updated VS Code launch config: ${launchJsonPath}`));
1116
- const openResponse = await prompts({
2504
+ console.log(chalk_1.default.green(`Updated VS Code launch config: ${launchJsonPath}`));
2505
+ const openResponse = await (0, prompts_1.default)({
1117
2506
  type: "select",
1118
2507
  name: "open",
1119
2508
  message: "Open current folder in VS Code?",
@@ -1132,7 +2521,7 @@ async function runDebugCommand(options) {
1132
2521
  appName,
1133
2522
  instanceIndex,
1134
2523
  localPort,
1135
- launchJsonPath: launchJsonPath ?? path.resolve(repositoryPath, ".vscode", "launch.json"),
2524
+ launchJsonPath: launchJsonPath ?? node_path_1.default.resolve(repositoryPath, ".vscode", "launch.json"),
1136
2525
  });
1137
2526
  return;
1138
2527
  }
@@ -1142,7 +2531,7 @@ async function runDebugCommand(options) {
1142
2531
  return;
1143
2532
  }
1144
2533
  if (options.enableSsh) {
1145
- const result = await runCommand("cf", ["enable-ssh", appName]);
2534
+ const result = await (0, process_1.runCommand)("cf", ["enable-ssh", appName]);
1146
2535
  if (result.stdout)
1147
2536
  console.log(result.stdout);
1148
2537
  if (result.stderr)
@@ -1152,28 +2541,28 @@ async function runDebugCommand(options) {
1152
2541
  return;
1153
2542
  }
1154
2543
  if (options.restart) {
1155
- const restartExitCode = await runCommandInherit("cf", ["restart", appName]);
2544
+ const restartExitCode = await (0, process_1.runCommandInherit)("cf", ["restart", appName]);
1156
2545
  if (restartExitCode !== 0) {
1157
2546
  process.exitCode = restartExitCode;
1158
2547
  return;
1159
2548
  }
1160
2549
  }
1161
2550
  else {
1162
- console.log(chalk.yellow("SSH was enabled. If cf ssh still fails, restart the app or run: cf restart " + appName));
2551
+ console.log(chalk_1.default.yellow("SSH was enabled. If cf ssh still fails, restart the app or run: cf restart " + appName));
1163
2552
  }
1164
2553
  }
1165
2554
  console.log("");
1166
- console.log(chalk.cyan("BTP debug works by opening a CF SSH tunnel to the Node.js inspector."));
1167
- console.log(chalk.gray("If this is the first time debugging this app, choose: Set NODE_OPTIONS and restart app."));
1168
- console.log(chalk.gray("If the app was already restarted with NODE_OPTIONS=--inspect, choose: Inspector is already enabled."));
2555
+ console.log(chalk_1.default.cyan("BTP debug works by opening a CF SSH tunnel to the Node.js inspector."));
2556
+ console.log(chalk_1.default.gray("If this is the first time debugging this app, choose: Set NODE_OPTIONS and restart app."));
2557
+ console.log(chalk_1.default.gray("If the app was already restarted with NODE_OPTIONS=--inspect, choose: Inspector is already enabled."));
1169
2558
  const prepareMode = await selectNodeInspectorPrepareMode({ appName, remotePort });
1170
2559
  await ensureSshEnabledForDebug(appName);
1171
2560
  if (prepareMode === "set-env-restart") {
1172
2561
  await setNodeInspectorEnvironmentAndRestart({ appName, remotePort });
1173
2562
  }
1174
- console.log(chalk.gray(`Starting Node.js inspector tunnel for ${appName} instance ${instanceIndex}...`));
1175
- console.log(chalk.gray(`Forwarding localhost:${localPort} -> app container 127.0.0.1:${remotePort}`));
1176
- const childProcess = spawn("cf", buildCloudFoundryDebugSshArgs({
2563
+ console.log(chalk_1.default.gray(`Starting Node.js inspector tunnel for ${appName} instance ${instanceIndex}...`));
2564
+ console.log(chalk_1.default.gray(`Forwarding localhost:${localPort} -> app container 127.0.0.1:${remotePort}`));
2565
+ const childProcess = (0, node_child_process_1.spawn)("cf", buildCloudFoundryDebugSshArgs({
1177
2566
  appName,
1178
2567
  instanceIndex,
1179
2568
  processName: options.process,
@@ -1194,13 +2583,13 @@ async function runDebugCommand(options) {
1194
2583
  const debugUrl = await waitForNodeInspectorDebugUrl(localPort);
1195
2584
  if (debugMode === "vscode") {
1196
2585
  if (!debugUrl) {
1197
- console.log(chalk.yellow("Inspector is not reachable yet on localhost. If you selected running-process mode and see a Node PID error, run debug again and choose 'Set NODE_OPTIONS and restart app'."));
2586
+ console.log(chalk_1.default.yellow("Inspector is not reachable yet on localhost. If you selected running-process mode and see a Node PID error, run debug again and choose 'Set NODE_OPTIONS and restart app'."));
1198
2587
  }
1199
2588
  printVscodeAttachInstructions({
1200
2589
  appName,
1201
2590
  instanceIndex,
1202
2591
  localPort,
1203
- launchJsonPath: launchJsonPath ?? path.resolve(repositoryPath, ".vscode", "launch.json"),
2592
+ launchJsonPath: launchJsonPath ?? node_path_1.default.resolve(repositoryPath, ".vscode", "launch.json"),
1204
2593
  inspectorReady: Boolean(debugUrl),
1205
2594
  });
1206
2595
  return;
@@ -1224,9 +2613,9 @@ async function runDebugCommand(options) {
1224
2613
  clearTimeout(fallbackTimer);
1225
2614
  if (!hasPrintedAttachInfo || (exitCode ?? 0) !== 0) {
1226
2615
  console.log("");
1227
- console.log(chalk.red("Debug tunnel stopped before a working inspector connection was confirmed."));
1228
- console.log(chalk.yellow("Run smdg cf debug again and choose 'Set NODE_OPTIONS and restart app' when asked to prepare Node.js inspector."));
1229
- console.log(chalk.gray("After the app restarts, choose VS Code guided debugging and start the attach config from VS Code Run and Debug."));
2616
+ console.log(chalk_1.default.red("Debug tunnel stopped before a working inspector connection was confirmed."));
2617
+ console.log(chalk_1.default.yellow("Run smdg cf debug again and choose 'Set NODE_OPTIONS and restart app' when asked to prepare Node.js inspector."));
2618
+ console.log(chalk_1.default.gray("After the app restarts, choose VS Code guided debugging and start the attach config from VS Code Run and Debug."));
1230
2619
  }
1231
2620
  process.exitCode = exitCode ?? 0;
1232
2621
  });
@@ -1235,14 +2624,14 @@ async function runDebugCommand(options) {
1235
2624
  });
1236
2625
  }
1237
2626
  async function runTargetCommand() {
1238
- const target = await readCloudFoundryTarget();
2627
+ const target = await (0, cf_1.readCloudFoundryTarget)();
1239
2628
  printTarget(target);
1240
2629
  }
1241
2630
  async function runCacheCommand() {
1242
- const cache = await readCache();
2631
+ const cache = await (0, cache_1.readCache)();
1243
2632
  console.log(JSON.stringify(cache.cloudFoundry, null, 2));
1244
2633
  }
1245
- export function registerCloudFoundryCommands(program) {
2634
+ function registerCloudFoundryCommands(program) {
1246
2635
  const cfCommand = program.command("cf").description("Cloud Foundry helper commands for SimpleMDG");
1247
2636
  cfCommand
1248
2637
  .command("login")
@@ -1317,10 +2706,48 @@ export function registerCloudFoundryCommands(program) {
1317
2706
  .option("--open", "Open current folder in VS Code after creating launch.json")
1318
2707
  .option("--skip-org-select", "Use current CF org/space without asking")
1319
2708
  .action(runDebugCommand);
2709
+ cfCommand
2710
+ .command("http-watch")
2711
+ .alias("watch-http")
2712
+ .description("Watch incoming HTTP requests using existing CF/CDS/RTR logs. Stable and does not modify apps.")
2713
+ .option("--app <appName>", "BTP app name. Use comma-separated names to watch multiple apps")
2714
+ .option("--refresh", "Refresh app list before selecting")
2715
+ .option("--recent", "Parse recent logs and exit")
2716
+ .option("--out <fileName>", "Write parsed HTTP events to a file")
2717
+ .option("--skip-org-select", "Use current CF org/space without asking")
2718
+ .action(runHttpWatchCommand);
2719
+ cfCommand
2720
+ .command("request-trace-doctor")
2721
+ .description("Diagnose why deep request-trace may not capture body/header in a BTP Node.js app")
2722
+ .option("--app <appName>", "BTP app name. Use comma-separated names")
2723
+ .option("--refresh", "Refresh app list before selecting")
2724
+ .option("--instance <index>", "App instance index", "0")
2725
+ .option("--process <processName>", "CF process name for multi-process apps")
2726
+ .option("--local-port <port>", "First local inspector port", "9329")
2727
+ .option("--remote-port <port>", "Remote inspector port in app container", "9229")
2728
+ .option("--max-body-bytes <bytes>", "Maximum request/response body bytes to print", "20000")
2729
+ .option("--skip-org-select", "Use current CF org/space without asking")
2730
+ .action(runRequestTraceDoctorCommand);
2731
+ cfCommand
2732
+ .command("request-trace")
2733
+ .alias("network-trace")
2734
+ .alias("traffic")
2735
+ .description("Watch incoming HTTP requests from BTP Node.js apps without editing backend source code")
2736
+ .option("--app <appName>", "BTP app name. Use comma-separated names to trace multiple apps")
2737
+ .option("--refresh", "Refresh app list before selecting")
2738
+ .option("--instance <index>", "App instance index", "0")
2739
+ .option("--process <processName>", "CF process name for multi-process apps")
2740
+ .option("--local-port <port>", "First local inspector port", "9329")
2741
+ .option("--remote-port <port>", "Remote inspector port in app container", "9229")
2742
+ .option("--max-body-bytes <bytes>", "Maximum request/response body bytes to print", "20000")
2743
+ .option("--out <fileName>", "Export captured trace events to a JSONL file")
2744
+ .option("--skip-org-select", "Use current CF org/space without asking")
2745
+ .action(runRequestTraceCommand);
1320
2746
  cfCommand
1321
2747
  .command("apps-cache-refresh")
1322
2748
  .description("Refresh cached cf apps for current target. Internal command used by smdg cf apps.")
1323
2749
  .action(runAppsCacheRefreshCommand);
2750
+ (0, cf_db_command_1.registerCloudFoundryDbCommands)(cfCommand);
1324
2751
  cfCommand.command("cache").description("Print cached Cloud Foundry values").action(runCacheCommand);
1325
2752
  }
1326
2753
  //# sourceMappingURL=cf.command.js.map