simplemdg-dev-cli 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -127
- package/USER_GUIDE.md +181 -127
- package/dist/commands/cf.command.js +231 -25
- package/dist/commands/cf.command.js.map +1 -1
- package/dist/core/cf.js +10 -1
- package/dist/core/cf.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -74,6 +74,71 @@ async function ensureCloudFoundrySessionFromCache() {
|
|
|
74
74
|
});
|
|
75
75
|
return readCloudFoundryTarget();
|
|
76
76
|
}
|
|
77
|
+
function sortCloudFoundryProfilesForEndpoint(options) {
|
|
78
|
+
const profilesWithPassword = options.profiles.filter((profile) => profile.password?.trim());
|
|
79
|
+
return [
|
|
80
|
+
...profilesWithPassword.filter((profile) => profile.apiEndpoint === options.apiEndpoint && profile.org === options.preferredOrg),
|
|
81
|
+
...profilesWithPassword.filter((profile) => profile.apiEndpoint === options.apiEndpoint && profile.org !== options.preferredOrg),
|
|
82
|
+
...profilesWithPassword.filter((profile) => profile.apiEndpoint !== options.apiEndpoint && profile.org === options.preferredOrg),
|
|
83
|
+
...profilesWithPassword.filter((profile) => profile.apiEndpoint !== options.apiEndpoint && profile.org !== options.preferredOrg),
|
|
84
|
+
].filter((profile, index, array) => {
|
|
85
|
+
return array.findIndex((item) => {
|
|
86
|
+
return item.apiEndpoint === profile.apiEndpoint
|
|
87
|
+
&& item.username === profile.username
|
|
88
|
+
&& item.password === profile.password
|
|
89
|
+
&& item.org === profile.org
|
|
90
|
+
&& item.space === profile.space;
|
|
91
|
+
}) === index;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function ensureCloudFoundryAuthenticatedForApiEndpoint(options) {
|
|
95
|
+
const apiExitCode = await setCloudFoundryApiEndpoint(options.apiEndpoint);
|
|
96
|
+
if (apiExitCode !== 0) {
|
|
97
|
+
process.exitCode = apiExitCode;
|
|
98
|
+
throw new Error(`Cannot set CF API endpoint: ${options.apiEndpoint}`);
|
|
99
|
+
}
|
|
100
|
+
const orgsCheck = await runCommand("cf", ["orgs"]);
|
|
101
|
+
if (orgsCheck.exitCode === 0) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const cache = await readCache();
|
|
105
|
+
const profiles = sortCloudFoundryProfilesForEndpoint({
|
|
106
|
+
profiles: cache.cloudFoundry.loginProfiles,
|
|
107
|
+
apiEndpoint: options.apiEndpoint,
|
|
108
|
+
preferredOrg: options.preferredOrg,
|
|
109
|
+
});
|
|
110
|
+
if (!profiles.length) {
|
|
111
|
+
console.log(chalk.yellow(`Not logged in to ${inferCloudFoundryRegionFromApiEndpoint(options.apiEndpoint)} and no cached password was found for automatic login.`));
|
|
112
|
+
console.log(chalk.gray("Run smdg cf login once, choose to save password, then retry this command."));
|
|
113
|
+
throw new Error("Cloud Foundry automatic login is required");
|
|
114
|
+
}
|
|
115
|
+
let lastError = orgsCheck.stderr || orgsCheck.stdout || "cf orgs failed";
|
|
116
|
+
for (const profile of profiles) {
|
|
117
|
+
console.log(chalk.gray(`Auto auth CF ${inferCloudFoundryRegionFromApiEndpoint(options.apiEndpoint)} as ${profile.username}...`));
|
|
118
|
+
const authExitCode = await authenticateCloudFoundry({
|
|
119
|
+
username: profile.username,
|
|
120
|
+
password: profile.password,
|
|
121
|
+
});
|
|
122
|
+
if (authExitCode !== 0) {
|
|
123
|
+
lastError = `cf auth failed for ${profile.username}`;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const nextOrgsCheck = await runCommand("cf", ["orgs"]);
|
|
127
|
+
if (nextOrgsCheck.exitCode === 0) {
|
|
128
|
+
const updatedProfile = {
|
|
129
|
+
...profile,
|
|
130
|
+
apiEndpoint: options.apiEndpoint,
|
|
131
|
+
org: options.preferredOrg || profile.org,
|
|
132
|
+
space: options.preferredSpace || profile.space,
|
|
133
|
+
updatedAt: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
await rememberCloudFoundryLoginProfile(updatedProfile);
|
|
136
|
+
return updatedProfile;
|
|
137
|
+
}
|
|
138
|
+
lastError = nextOrgsCheck.stderr || nextOrgsCheck.stdout || lastError;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`CF automatic login failed for ${options.apiEndpoint}. ${lastError}`);
|
|
141
|
+
}
|
|
77
142
|
function buildCloudFoundryLogsArgs(options) {
|
|
78
143
|
const args = ["logs", options.appName];
|
|
79
144
|
if (options.recent) {
|
|
@@ -96,15 +161,28 @@ function buildNodeInspectorRemoteCommand(remotePort) {
|
|
|
96
161
|
return [
|
|
97
162
|
`echo "Remote port ${remotePort} was requested."`,
|
|
98
163
|
`echo "SIGUSR1 starts the Node.js inspector on its default port 9229 for a running Node process."`,
|
|
99
|
-
`echo "Use remote port 9229, or start the app process with NODE_OPTIONS=--inspect=
|
|
164
|
+
`echo "Use remote port 9229, or start the app process with NODE_OPTIONS=--inspect=0.0.0.0:${remotePort}."`,
|
|
100
165
|
`exit 2`,
|
|
101
166
|
].join("; ");
|
|
102
167
|
}
|
|
168
|
+
const detectNodePidScript = [
|
|
169
|
+
`PID=""`,
|
|
170
|
+
`if command -v pgrep >/dev/null 2>&1; then PID=$(pgrep -f "(^|/| )node( |$)" 2>/dev/null | head -n 1 || true); fi`,
|
|
171
|
+
`if [ -z "$PID" ]; then PID=$(ps -eo pid=,args= 2>/dev/null | awk '/[n]ode/ && $0 !~ /awk/ && $0 !~ /pgrep/ {print $1; exit}'); fi`,
|
|
172
|
+
`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`,
|
|
173
|
+
`echo "Detected Node.js PID: $PID"`,
|
|
174
|
+
`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`,
|
|
175
|
+
`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`,
|
|
176
|
+
`kill -USR1 "$PID" || { echo "Cannot send SIGUSR1 to Node.js PID $PID. Use prepare mode: Set NODE_OPTIONS and restart app." >&2; exit 1; }`,
|
|
177
|
+
`echo "Requested Node inspector for PID $PID on 127.0.0.1:9229"`,
|
|
178
|
+
`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`,
|
|
179
|
+
`tail -f /dev/null`,
|
|
180
|
+
];
|
|
181
|
+
return detectNodePidScript.join("; ");
|
|
182
|
+
}
|
|
183
|
+
function buildKeepAliveRemoteCommand() {
|
|
103
184
|
return [
|
|
104
|
-
`
|
|
105
|
-
`if [ -z "$PID" ]; then echo "No Node.js PID found in app container" >&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`,
|
|
185
|
+
`echo "SSH tunnel is open. Keep this terminal running."`,
|
|
108
186
|
`tail -f /dev/null`,
|
|
109
187
|
].join("; ");
|
|
110
188
|
}
|
|
@@ -118,9 +196,69 @@ function buildCloudFoundryDebugSshArgs(options) {
|
|
|
118
196
|
if (options.processName?.trim()) {
|
|
119
197
|
args.push("--process", options.processName.trim());
|
|
120
198
|
}
|
|
121
|
-
|
|
199
|
+
const remoteCommand = options.prepareMode === "running-process"
|
|
200
|
+
? buildNodeInspectorRemoteCommand(options.remotePort)
|
|
201
|
+
: buildKeepAliveRemoteCommand();
|
|
202
|
+
args.push("-T", "-L", `${options.localPort}:127.0.0.1:${options.remotePort}`, "-c", remoteCommand);
|
|
122
203
|
return args;
|
|
123
204
|
}
|
|
205
|
+
async function selectNodeInspectorPrepareMode(options) {
|
|
206
|
+
return searchableSelectChoice({
|
|
207
|
+
message: "Prepare Node.js inspector",
|
|
208
|
+
choices: [
|
|
209
|
+
{
|
|
210
|
+
title: "Set NODE_OPTIONS and restart app (recommended first time)",
|
|
211
|
+
value: "set-env-restart",
|
|
212
|
+
description: `Runs cf set-env ${options.appName} NODE_OPTIONS --inspect=0.0.0.0:${options.remotePort} and cf restart`,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
title: "Try start inspector on running Node process without restart",
|
|
216
|
+
value: "running-process",
|
|
217
|
+
description: "Uses cf ssh + SIGUSR1. Fast, but may fail if Node PID cannot be detected.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
title: "Inspector is already enabled, only open SSH tunnel",
|
|
221
|
+
value: "already-enabled",
|
|
222
|
+
description: "Use when NODE_OPTIONS already contains --inspect and app was restarted.",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
allowCustomValue: false,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
async function ensureSshEnabledForDebug(appName) {
|
|
229
|
+
const sshEnabledResult = await runCommand("cf", ["ssh-enabled", appName]);
|
|
230
|
+
const combinedOutput = `${sshEnabledResult.stdout}
|
|
231
|
+
${sshEnabledResult.stderr}`;
|
|
232
|
+
if (sshEnabledResult.exitCode === 0 && /enabled/i.test(combinedOutput) && !/not enabled/i.test(combinedOutput)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
console.log(chalk.yellow("SSH is not enabled for this app. Enabling SSH..."));
|
|
236
|
+
const enableResult = await runCommand("cf", ["enable-ssh", appName]);
|
|
237
|
+
if (enableResult.stdout)
|
|
238
|
+
console.log(enableResult.stdout);
|
|
239
|
+
if (enableResult.stderr)
|
|
240
|
+
console.error(enableResult.stderr);
|
|
241
|
+
if (enableResult.exitCode !== 0) {
|
|
242
|
+
throw new Error("cf enable-ssh failed. You may not have permission to enable SSH for this app.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function setNodeInspectorEnvironmentAndRestart(options) {
|
|
246
|
+
const nodeOptions = `--inspect=0.0.0.0:${options.remotePort} --enable-source-maps`;
|
|
247
|
+
console.log(chalk.gray(`Setting NODE_OPTIONS for ${options.appName}: ${nodeOptions}`));
|
|
248
|
+
const setEnvResult = await runCommand("cf", ["set-env", options.appName, "NODE_OPTIONS", nodeOptions]);
|
|
249
|
+
if (setEnvResult.stdout)
|
|
250
|
+
console.log(setEnvResult.stdout);
|
|
251
|
+
if (setEnvResult.stderr)
|
|
252
|
+
console.error(setEnvResult.stderr);
|
|
253
|
+
if (setEnvResult.exitCode !== 0) {
|
|
254
|
+
throw new Error("cf set-env NODE_OPTIONS failed");
|
|
255
|
+
}
|
|
256
|
+
console.log(chalk.yellow("Restarting app so NODE_OPTIONS takes effect..."));
|
|
257
|
+
const restartExitCode = await runCommandInherit("cf", ["restart", options.appName]);
|
|
258
|
+
if (restartExitCode !== 0) {
|
|
259
|
+
throw new Error(`cf restart ${options.appName} failed`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
124
262
|
async function getNodeInspectorDebugUrl(localPort) {
|
|
125
263
|
const response = await fetch(`http://127.0.0.1:${localPort}/json/list`);
|
|
126
264
|
if (!response.ok) {
|
|
@@ -240,18 +378,34 @@ async function writeVscodeLaunchConfiguration(options) {
|
|
|
240
378
|
}
|
|
241
379
|
function printVscodeAttachInstructions(options) {
|
|
242
380
|
console.log("");
|
|
243
|
-
|
|
381
|
+
if (options.inspectorReady === false) {
|
|
382
|
+
console.log(chalk.yellow(`VS Code config was created, but the Node inspector is not reachable yet for ${options.appName} instance ${options.instanceIndex}.`));
|
|
383
|
+
console.log(chalk.yellow("The VS Code debug toolbar appears only after the inspector is reachable and you start the attach config."));
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
console.log(chalk.green(`VS Code debug config is ready for ${options.appName} instance ${options.instanceIndex}.`));
|
|
387
|
+
}
|
|
244
388
|
console.log(`Launch file: ${chalk.cyan(options.launchJsonPath)}`);
|
|
245
389
|
console.log(`Attach config: ${chalk.cyan(`Attach BTP ${options.appName}`)}`);
|
|
246
390
|
console.log(`Inspector: ${chalk.cyan(`127.0.0.1:${options.localPort}`)}`);
|
|
247
391
|
console.log("");
|
|
248
|
-
console.log(chalk.
|
|
392
|
+
console.log(chalk.cyan("How to start debugging in VS Code:"));
|
|
393
|
+
console.log("1. Keep this terminal open. It owns the CF SSH tunnel.");
|
|
394
|
+
console.log("2. Open VS Code Run and Debug panel with Ctrl+Shift+D.");
|
|
395
|
+
console.log(`3. Select ${chalk.cyan(`Attach BTP ${options.appName}`)}.`);
|
|
396
|
+
console.log("4. Press F5 or the green Start Debugging button.");
|
|
397
|
+
console.log("5. Debug buttons such as pause, step over, step into, restart, and stop appear only after attach succeeds.");
|
|
398
|
+
console.log("");
|
|
249
399
|
console.log(chalk.gray("Press Ctrl+C in this terminal to close the tunnel."));
|
|
250
400
|
}
|
|
251
401
|
async function openVisualStudioCode(options) {
|
|
252
|
-
const
|
|
402
|
+
const args = options.debugPanel
|
|
403
|
+
? ["--reuse-window", options.cwd, "--command", "workbench.view.debug"]
|
|
404
|
+
: [options.cwd];
|
|
405
|
+
const result = await runCommand("code", args);
|
|
253
406
|
if (result.exitCode !== 0) {
|
|
254
407
|
console.log(chalk.yellow("Could not open VS Code automatically. Open this folder manually in VS Code."));
|
|
408
|
+
console.log(chalk.gray("Then open Run and Debug with Ctrl+Shift+D."));
|
|
255
409
|
if (result.stderr)
|
|
256
410
|
console.log(chalk.gray(result.stderr));
|
|
257
411
|
}
|
|
@@ -273,9 +427,9 @@ async function selectDebugMode(options) {
|
|
|
273
427
|
message: "Select debug mode",
|
|
274
428
|
choices: [
|
|
275
429
|
{
|
|
276
|
-
title: "VS Code
|
|
430
|
+
title: "VS Code guided debugging (recommended)",
|
|
277
431
|
value: "vscode",
|
|
278
|
-
description: "Create
|
|
432
|
+
description: "Create launch.json, open VS Code Debug panel, prepare inspector, and open CF SSH tunnel",
|
|
279
433
|
},
|
|
280
434
|
{
|
|
281
435
|
title: "Chrome DevTools / chrome://inspect",
|
|
@@ -430,6 +584,7 @@ function refreshAppsCacheInDetachedProcess() {
|
|
|
430
584
|
childProcess.unref();
|
|
431
585
|
}
|
|
432
586
|
async function getAppsWithCache(options) {
|
|
587
|
+
await ensureCloudFoundrySessionFromCache();
|
|
433
588
|
if (options.refresh) {
|
|
434
589
|
return refreshAppsCacheForCurrentTarget();
|
|
435
590
|
}
|
|
@@ -480,15 +635,24 @@ function printTarget(target) {
|
|
|
480
635
|
const DEFAULT_CLOUD_FOUNDRY_API_ENDPOINTS = [
|
|
481
636
|
"https://api.cf.br10.hana.ondemand.com",
|
|
482
637
|
"https://api.cf.eu10.hana.ondemand.com",
|
|
638
|
+
"https://api.cf.eu10-004.hana.ondemand.com",
|
|
639
|
+
"https://api.cf.eu10-005.hana.ondemand.com",
|
|
640
|
+
"https://api.cf.eu20.hana.ondemand.com",
|
|
641
|
+
"https://api.cf.eu20-001.hana.ondemand.com",
|
|
642
|
+
"https://api.cf.eu20-002.hana.ondemand.com",
|
|
483
643
|
"https://api.cf.us10.hana.ondemand.com",
|
|
644
|
+
"https://api.cf.us10-001.hana.ondemand.com",
|
|
645
|
+
"https://api.cf.us11.hana.ondemand.com",
|
|
646
|
+
"https://api.cf.us20.hana.ondemand.com",
|
|
647
|
+
"https://api.cf.us21.hana.ondemand.com",
|
|
484
648
|
"https://api.cf.ap10.hana.ondemand.com",
|
|
485
649
|
"https://api.cf.ap11.hana.ondemand.com",
|
|
650
|
+
"https://api.cf.ap20.hana.ondemand.com",
|
|
486
651
|
"https://api.cf.ap21.hana.ondemand.com",
|
|
487
652
|
"https://api.cf.jp10.hana.ondemand.com",
|
|
488
653
|
"https://api.cf.ca10.hana.ondemand.com",
|
|
489
654
|
"https://api.cf.ch20.hana.ondemand.com",
|
|
490
|
-
"https://api.cf.
|
|
491
|
-
"https://api.cf.us20.hana.ondemand.com",
|
|
655
|
+
"https://api.cf.sa10.hana.ondemand.com",
|
|
492
656
|
];
|
|
493
657
|
function uniqueValues(values) {
|
|
494
658
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
@@ -689,6 +853,7 @@ function getCloudFoundryApiEndpointsForOrgSearch(options, target, cache) {
|
|
|
689
853
|
options.api ?? "",
|
|
690
854
|
target.apiEndpoint ?? "",
|
|
691
855
|
...cache.cloudFoundry.loginProfiles.map((item) => item.apiEndpoint),
|
|
856
|
+
...cache.cloudFoundry.orgsAcrossRegions.map((item) => item.apiEndpoint),
|
|
692
857
|
...DEFAULT_CLOUD_FOUNDRY_API_ENDPOINTS,
|
|
693
858
|
]);
|
|
694
859
|
}
|
|
@@ -696,8 +861,12 @@ async function getCloudFoundryOrganizationsAcrossRegions(options) {
|
|
|
696
861
|
const target = await readCloudFoundryTarget();
|
|
697
862
|
const cache = await readCache();
|
|
698
863
|
const cachedEntries = cache.cloudFoundry.orgsAcrossRegions ?? [];
|
|
699
|
-
|
|
700
|
-
|
|
864
|
+
const cachedRegionCount = new Set(cachedEntries.map((entry) => entry.region)).size;
|
|
865
|
+
if (!options.refresh && cachedEntries.length && cachedRegionCount > 1) {
|
|
866
|
+
return cachedEntries.sort((left, right) => {
|
|
867
|
+
const byOrg = left.org.localeCompare(right.org);
|
|
868
|
+
return byOrg !== 0 ? byOrg : left.region.localeCompare(right.region);
|
|
869
|
+
});
|
|
701
870
|
}
|
|
702
871
|
const apiEndpoints = getCloudFoundryApiEndpointsForOrgSearch(options, target, cache);
|
|
703
872
|
console.log(chalk.gray(`Searching CF orgs across ${apiEndpoints.length} region endpoint(s)...`));
|
|
@@ -708,6 +877,8 @@ async function getCloudFoundryOrganizationsAcrossRegions(options) {
|
|
|
708
877
|
}));
|
|
709
878
|
const entries = await scanCloudFoundryOrganizationsAcrossRegions(apiEndpoints, credentials);
|
|
710
879
|
if (entries.length) {
|
|
880
|
+
const regionCount = new Set(entries.map((entry) => entry.region)).size;
|
|
881
|
+
console.log(chalk.green(`Found ${entries.length} org(s) across ${regionCount} region(s).`));
|
|
711
882
|
await rememberCloudFoundryOrgEntries(entries);
|
|
712
883
|
return entries;
|
|
713
884
|
}
|
|
@@ -747,14 +918,17 @@ async function runOrgCommand(options) {
|
|
|
747
918
|
api: options.api,
|
|
748
919
|
refresh: options.refresh,
|
|
749
920
|
});
|
|
921
|
+
const organizationRegionCount = new Set(organizationEntries.map((entry) => entry.region)).size;
|
|
750
922
|
const latestTarget = await readCloudFoundryTarget();
|
|
751
923
|
if (action === "list") {
|
|
752
924
|
printTarget(latestTarget);
|
|
753
925
|
console.log("");
|
|
754
926
|
if (!organizationEntries.length) {
|
|
755
|
-
console.log(chalk.yellow("No orgs found for current CF user.
|
|
927
|
+
console.log(chalk.yellow("No orgs found for current CF user. Run smdg cf login, save the password, then run smdg cf org again."));
|
|
756
928
|
return;
|
|
757
929
|
}
|
|
930
|
+
console.log(chalk.gray(`Showing ${organizationEntries.length} org(s) across ${organizationRegionCount} region(s).`));
|
|
931
|
+
console.log("");
|
|
758
932
|
for (const entry of organizationEntries) {
|
|
759
933
|
const marker = entry.apiEndpoint === latestTarget.apiEndpoint && entry.org === latestTarget.org ? chalk.green("*") : " ";
|
|
760
934
|
console.log(`${marker} ${formatCloudFoundryOrgEntry(entry, latestTarget)}`);
|
|
@@ -781,7 +955,7 @@ async function runOrgCommand(options) {
|
|
|
781
955
|
return;
|
|
782
956
|
}
|
|
783
957
|
const selectedIndex = await searchableSelectChoice({
|
|
784
|
-
message:
|
|
958
|
+
message: `Search CF org across ${organizationRegionCount} region(s)`,
|
|
785
959
|
choices: organizationEntries.map((entry, index) => ({
|
|
786
960
|
title: formatCloudFoundryOrgEntry(entry, latestTarget),
|
|
787
961
|
value: String(index),
|
|
@@ -799,14 +973,16 @@ async function runOrgCommand(options) {
|
|
|
799
973
|
if (!selectedEntry.apiEndpoint) {
|
|
800
974
|
throw new Error("Cannot determine CF API endpoint for selected org.");
|
|
801
975
|
}
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
976
|
+
const authenticatedProfile = await ensureCloudFoundryAuthenticatedForApiEndpoint({
|
|
977
|
+
apiEndpoint: selectedEntry.apiEndpoint,
|
|
978
|
+
preferredOrg: selectedEntry.org,
|
|
979
|
+
preferredSpace: options.space,
|
|
980
|
+
reason: "switch-org",
|
|
981
|
+
});
|
|
807
982
|
const orgExitCode = await targetCloudFoundryOrg(selectedEntry.org);
|
|
808
983
|
if (orgExitCode !== 0) {
|
|
809
|
-
console.log(chalk.yellow("Cannot switch to this org
|
|
984
|
+
console.log(chalk.yellow("Cannot switch to this org after automatic authentication."));
|
|
985
|
+
console.log(chalk.gray("Run smdg cf login, save the password, then try again."));
|
|
810
986
|
process.exitCode = orgExitCode;
|
|
811
987
|
return;
|
|
812
988
|
}
|
|
@@ -833,6 +1009,15 @@ async function runOrgCommand(options) {
|
|
|
833
1009
|
return;
|
|
834
1010
|
}
|
|
835
1011
|
}
|
|
1012
|
+
if (authenticatedProfile?.password) {
|
|
1013
|
+
await rememberCloudFoundryLoginProfile({
|
|
1014
|
+
...authenticatedProfile,
|
|
1015
|
+
apiEndpoint: selectedEntry.apiEndpoint,
|
|
1016
|
+
org: selectedEntry.org,
|
|
1017
|
+
space,
|
|
1018
|
+
updatedAt: new Date().toISOString(),
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
836
1021
|
const switchedTarget = await readCloudFoundryTarget();
|
|
837
1022
|
console.log(chalk.green("CF org/space switched."));
|
|
838
1023
|
printTarget(switchedTarget);
|
|
@@ -962,6 +1147,7 @@ async function runLogsCommand(options) {
|
|
|
962
1147
|
}
|
|
963
1148
|
async function runDebugCommand(options) {
|
|
964
1149
|
await maybeSwitchCloudFoundryTargetForDebug(options);
|
|
1150
|
+
await ensureCloudFoundrySessionFromCache();
|
|
965
1151
|
const appName = await resolveAppSelection({
|
|
966
1152
|
app: options.app,
|
|
967
1153
|
refresh: options.refresh,
|
|
@@ -1033,7 +1219,7 @@ async function runDebugCommand(options) {
|
|
|
1033
1219
|
initial: options.open ? 1 : 0,
|
|
1034
1220
|
});
|
|
1035
1221
|
if (openResponse.open) {
|
|
1036
|
-
await openVisualStudioCode({ cwd: repositoryPath });
|
|
1222
|
+
await openVisualStudioCode({ cwd: repositoryPath, debugPanel: debugMode === "vscode" });
|
|
1037
1223
|
}
|
|
1038
1224
|
}
|
|
1039
1225
|
if (debugMode === "config-only") {
|
|
@@ -1071,6 +1257,15 @@ async function runDebugCommand(options) {
|
|
|
1071
1257
|
console.log(chalk.yellow("SSH was enabled. If cf ssh still fails, restart the app or run: cf restart " + appName));
|
|
1072
1258
|
}
|
|
1073
1259
|
}
|
|
1260
|
+
console.log("");
|
|
1261
|
+
console.log(chalk.cyan("BTP debug works by opening a CF SSH tunnel to the Node.js inspector."));
|
|
1262
|
+
console.log(chalk.gray("If this is the first time debugging this app, choose: Set NODE_OPTIONS and restart app."));
|
|
1263
|
+
console.log(chalk.gray("If the app was already restarted with NODE_OPTIONS=--inspect, choose: Inspector is already enabled."));
|
|
1264
|
+
const prepareMode = await selectNodeInspectorPrepareMode({ appName, remotePort });
|
|
1265
|
+
await ensureSshEnabledForDebug(appName);
|
|
1266
|
+
if (prepareMode === "set-env-restart") {
|
|
1267
|
+
await setNodeInspectorEnvironmentAndRestart({ appName, remotePort });
|
|
1268
|
+
}
|
|
1074
1269
|
console.log(chalk.gray(`Starting Node.js inspector tunnel for ${appName} instance ${instanceIndex}...`));
|
|
1075
1270
|
console.log(chalk.gray(`Forwarding localhost:${localPort} -> app container 127.0.0.1:${remotePort}`));
|
|
1076
1271
|
const childProcess = spawn("cf", buildCloudFoundryDebugSshArgs({
|
|
@@ -1079,6 +1274,7 @@ async function runDebugCommand(options) {
|
|
|
1079
1274
|
processName: options.process,
|
|
1080
1275
|
localPort,
|
|
1081
1276
|
remotePort,
|
|
1277
|
+
prepareMode,
|
|
1082
1278
|
}), {
|
|
1083
1279
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1084
1280
|
shell: false,
|
|
@@ -1090,16 +1286,20 @@ async function runDebugCommand(options) {
|
|
|
1090
1286
|
return;
|
|
1091
1287
|
}
|
|
1092
1288
|
hasPrintedAttachInfo = true;
|
|
1289
|
+
const debugUrl = await waitForNodeInspectorDebugUrl(localPort);
|
|
1093
1290
|
if (debugMode === "vscode") {
|
|
1291
|
+
if (!debugUrl) {
|
|
1292
|
+
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'."));
|
|
1293
|
+
}
|
|
1094
1294
|
printVscodeAttachInstructions({
|
|
1095
1295
|
appName,
|
|
1096
1296
|
instanceIndex,
|
|
1097
1297
|
localPort,
|
|
1098
1298
|
launchJsonPath: launchJsonPath ?? path.resolve(repositoryPath, ".vscode", "launch.json"),
|
|
1299
|
+
inspectorReady: Boolean(debugUrl),
|
|
1099
1300
|
});
|
|
1100
1301
|
return;
|
|
1101
1302
|
}
|
|
1102
|
-
const debugUrl = await waitForNodeInspectorDebugUrl(localPort);
|
|
1103
1303
|
printNodeInspectorAttachInfo({ appName, instanceIndex, localPort, debugUrl });
|
|
1104
1304
|
};
|
|
1105
1305
|
childProcess.stdout?.on("data", (chunk) => {
|
|
@@ -1117,6 +1317,12 @@ async function runDebugCommand(options) {
|
|
|
1117
1317
|
}, 3000);
|
|
1118
1318
|
childProcess.on("close", (exitCode) => {
|
|
1119
1319
|
clearTimeout(fallbackTimer);
|
|
1320
|
+
if (!hasPrintedAttachInfo || (exitCode ?? 0) !== 0) {
|
|
1321
|
+
console.log("");
|
|
1322
|
+
console.log(chalk.red("Debug tunnel stopped before a working inspector connection was confirmed."));
|
|
1323
|
+
console.log(chalk.yellow("Run smdg cf debug again and choose 'Set NODE_OPTIONS and restart app' when asked to prepare Node.js inspector."));
|
|
1324
|
+
console.log(chalk.gray("After the app restarts, choose VS Code guided debugging and start the attach config from VS Code Run and Debug."));
|
|
1325
|
+
}
|
|
1120
1326
|
process.exitCode = exitCode ?? 0;
|
|
1121
1327
|
});
|
|
1122
1328
|
await new Promise((resolve) => {
|