@saptools/cf-live-trace 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +247 -71
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -4
- package/dist/index.js +139 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/runtime-source.ts
|
|
2
2
|
var CF_LIVE_TRACE_GLOBAL_NAME = "__SAPTOOLS_CF_LIVE_TRACE__";
|
|
3
|
-
var CF_LIVE_TRACE_RUNTIME_VERSION =
|
|
3
|
+
var CF_LIVE_TRACE_RUNTIME_VERSION = 2;
|
|
4
4
|
var CF_LIVE_TRACE_RUNTIME_SOURCE = `
|
|
5
5
|
(() => {
|
|
6
6
|
const name = '${CF_LIVE_TRACE_GLOBAL_NAME}';
|
|
@@ -197,15 +197,12 @@ var CF_LIVE_TRACE_RUNTIME_SOURCE = `
|
|
|
197
197
|
status() {
|
|
198
198
|
return { installed: state.installed, enabled: state.enabled, queueSize: state.queue.length, droppedCount: state.droppedCount, maxEvents: state.options.maxEvents };
|
|
199
199
|
},
|
|
200
|
-
uninstall() {
|
|
200
|
+
async uninstall() {
|
|
201
201
|
state.enabled = false;
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (state.originals.httpServerEmit && http && http.Server) http.Server.prototype.emit = state.originals.httpServerEmit;
|
|
207
|
-
if (state.originals.httpsServerEmit && https && https.Server) https.Server.prototype.emit = state.originals.httpsServerEmit;
|
|
208
|
-
}
|
|
202
|
+
const http = await loadModule('http');
|
|
203
|
+
const https = await loadModule('https');
|
|
204
|
+
if (state.originals.httpServerEmit && http && http.Server) http.Server.prototype.emit = state.originals.httpServerEmit;
|
|
205
|
+
if (state.originals.httpsServerEmit && https && https.Server) https.Server.prototype.emit = state.originals.httpsServerEmit;
|
|
209
206
|
state.installed = false;
|
|
210
207
|
return api.status();
|
|
211
208
|
}
|
|
@@ -461,15 +458,19 @@ async function ensureSshEnabled(target, dependencies = defaultCfDependencies) {
|
|
|
461
458
|
});
|
|
462
459
|
}
|
|
463
460
|
async function tryStartNodeInspector(target, dependencies = defaultCfDependencies) {
|
|
461
|
+
const result = await startNodeInspector(target, dependencies);
|
|
462
|
+
return result.status === "ready";
|
|
463
|
+
}
|
|
464
|
+
async function startNodeInspector(target, dependencies = defaultCfDependencies) {
|
|
465
|
+
const redactor = createSecretRedactor([target.email, target.password]);
|
|
464
466
|
try {
|
|
465
|
-
const redactor = createSecretRedactor([target.email, target.password]);
|
|
466
467
|
const stdout = await dependencies.runCf(
|
|
467
468
|
buildCfSshArgs(target.app, target.instanceIndex, ["-c", buildInspectorSignalCommand()]),
|
|
468
469
|
{ ...buildRunOptions(target, redactor), timeoutMs: INSPECTOR_SIGNAL_TIMEOUT_MS }
|
|
469
470
|
);
|
|
470
|
-
return hasInspectorReadyMarker(stdout);
|
|
471
|
-
} catch {
|
|
472
|
-
return
|
|
471
|
+
return hasInspectorReadyMarker(stdout) ? { status: "ready" } : { status: "not-ready", ...optionalDetail(summarizeInspectorStartupOutput(stdout)) };
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return { status: "not-ready", ...optionalDetail(redactor(formatErrorMessage(error))) };
|
|
473
474
|
}
|
|
474
475
|
}
|
|
475
476
|
async function openInspectorTunnel(target, dependencies = defaultTunnelDependencies) {
|
|
@@ -517,6 +518,8 @@ function spawnPortForward(params) {
|
|
|
517
518
|
env: buildCfEnv(params.cfHomeDir),
|
|
518
519
|
stdio: ["ignore", "pipe", "pipe"]
|
|
519
520
|
});
|
|
521
|
+
child.stdout.resume();
|
|
522
|
+
child.stderr.resume();
|
|
520
523
|
return {
|
|
521
524
|
process: child,
|
|
522
525
|
localPort: params.localPort,
|
|
@@ -612,6 +615,7 @@ function resolveTunnelAppName(target) {
|
|
|
612
615
|
return appName;
|
|
613
616
|
}
|
|
614
617
|
async function raceForwardReadiness(handle, dependencies) {
|
|
618
|
+
const controller = new AbortController();
|
|
615
619
|
let markFailed = () => {
|
|
616
620
|
return;
|
|
617
621
|
};
|
|
@@ -622,11 +626,14 @@ async function raceForwardReadiness(handle, dependencies) {
|
|
|
622
626
|
handle.process.once("exit", markFailed);
|
|
623
627
|
handle.process.once("error", markFailed);
|
|
624
628
|
});
|
|
625
|
-
const ready = dependencies.waitForLocalPort(handle.localPort, TUNNEL_READY_TIMEOUT_MS);
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
629
|
+
const ready = dependencies.waitForLocalPort(handle.localPort, TUNNEL_READY_TIMEOUT_MS, controller.signal);
|
|
630
|
+
try {
|
|
631
|
+
return await Promise.race([ready, failedEarly]);
|
|
632
|
+
} finally {
|
|
633
|
+
controller.abort();
|
|
634
|
+
handle.process.removeListener("exit", markFailed);
|
|
635
|
+
handle.process.removeListener("error", markFailed);
|
|
636
|
+
}
|
|
630
637
|
}
|
|
631
638
|
function resolveCommand(command) {
|
|
632
639
|
const resolvedBin = command ?? process.env["CF_LIVE_TRACE_CF_BIN"] ?? "cf";
|
|
@@ -654,7 +661,15 @@ function extractErrorDetail(error) {
|
|
|
654
661
|
if (stderr.length > 0) {
|
|
655
662
|
return stderr;
|
|
656
663
|
}
|
|
657
|
-
|
|
664
|
+
const message = error["message"];
|
|
665
|
+
if (typeof message === "string") {
|
|
666
|
+
return message.trim();
|
|
667
|
+
}
|
|
668
|
+
return message instanceof Error ? message.message.trim() : "";
|
|
669
|
+
}
|
|
670
|
+
function formatErrorMessage(error) {
|
|
671
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
672
|
+
return message.trim().length > 0 ? message.trim() : "Unknown error";
|
|
658
673
|
}
|
|
659
674
|
function formatArgs(args) {
|
|
660
675
|
return args.join(" ");
|
|
@@ -662,6 +677,13 @@ function formatArgs(args) {
|
|
|
662
677
|
function hasInspectorReadyMarker(stdout) {
|
|
663
678
|
return stdout.split(/\r?\n/).map((line) => line.trim()).includes("saptools-inspector-ready");
|
|
664
679
|
}
|
|
680
|
+
function summarizeInspectorStartupOutput(stdout) {
|
|
681
|
+
const markers = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("saptools-inspector-"));
|
|
682
|
+
return markers.length === 0 ? void 0 : markers.join("; ");
|
|
683
|
+
}
|
|
684
|
+
function optionalDetail(detail) {
|
|
685
|
+
return detail === void 0 || detail.length === 0 ? {} : { detail };
|
|
686
|
+
}
|
|
665
687
|
function findFreePort() {
|
|
666
688
|
return new Promise((resolve, reject) => {
|
|
667
689
|
const server = createServer();
|
|
@@ -679,29 +701,61 @@ function findFreePort() {
|
|
|
679
701
|
});
|
|
680
702
|
});
|
|
681
703
|
}
|
|
682
|
-
function waitForLocalPort(port, timeoutMs) {
|
|
704
|
+
function waitForLocalPort(port, timeoutMs, signal) {
|
|
683
705
|
const deadline = Date.now() + timeoutMs;
|
|
684
706
|
return new Promise((resolve) => {
|
|
707
|
+
let activeSocket;
|
|
708
|
+
let settled = false;
|
|
709
|
+
let retryTimer;
|
|
710
|
+
const finish = (ready) => {
|
|
711
|
+
if (settled) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
settled = true;
|
|
715
|
+
if (retryTimer !== void 0) {
|
|
716
|
+
clearTimeout(retryTimer);
|
|
717
|
+
}
|
|
718
|
+
activeSocket?.destroy();
|
|
719
|
+
activeSocket = void 0;
|
|
720
|
+
signal?.removeEventListener("abort", onAbort);
|
|
721
|
+
resolve(ready);
|
|
722
|
+
};
|
|
723
|
+
const onAbort = () => {
|
|
724
|
+
finish(false);
|
|
725
|
+
};
|
|
685
726
|
const attempt = () => {
|
|
727
|
+
if (settled) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
686
730
|
const socket = netConnect({ host: "127.0.0.1", port });
|
|
731
|
+
activeSocket = socket;
|
|
687
732
|
socket.once("connect", () => {
|
|
688
733
|
socket.destroy();
|
|
689
|
-
|
|
734
|
+
activeSocket = void 0;
|
|
735
|
+
finish(true);
|
|
690
736
|
});
|
|
691
737
|
socket.once("error", () => {
|
|
692
|
-
|
|
738
|
+
activeSocket = void 0;
|
|
739
|
+
retryPortProbe(socket, deadline, attempt, finish, (timer) => {
|
|
740
|
+
retryTimer = timer;
|
|
741
|
+
});
|
|
693
742
|
});
|
|
694
743
|
};
|
|
744
|
+
if (signal?.aborted === true) {
|
|
745
|
+
finish(false);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
695
749
|
attempt();
|
|
696
750
|
});
|
|
697
751
|
}
|
|
698
|
-
function retryPortProbe(socket, deadline, attempt,
|
|
752
|
+
function retryPortProbe(socket, deadline, attempt, finish, setRetryTimer) {
|
|
699
753
|
socket.destroy();
|
|
700
754
|
if (Date.now() >= deadline) {
|
|
701
|
-
|
|
755
|
+
finish(false);
|
|
702
756
|
return;
|
|
703
757
|
}
|
|
704
|
-
setTimeout(attempt, TUNNEL_READY_POLL_MS);
|
|
758
|
+
setRetryTimer(setTimeout(attempt, TUNNEL_READY_POLL_MS));
|
|
705
759
|
}
|
|
706
760
|
function isRecord2(value) {
|
|
707
761
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -816,7 +870,7 @@ var DRAIN_TRANSPORT_BODY_LIMIT = 2e4;
|
|
|
816
870
|
var defaultDependencies = {
|
|
817
871
|
prepareCfSession,
|
|
818
872
|
ensureSshEnabled,
|
|
819
|
-
tryStartNodeInspector,
|
|
873
|
+
tryStartNodeInspector: startNodeInspector,
|
|
820
874
|
openInspectorTunnel,
|
|
821
875
|
connectInspector: connectRuntimeInspector,
|
|
822
876
|
setInterval,
|
|
@@ -847,6 +901,9 @@ var LiveTraceSession = class {
|
|
|
847
901
|
async stop(options) {
|
|
848
902
|
this.stopRequested = true;
|
|
849
903
|
if (!this.isRunning()) {
|
|
904
|
+
if (this.state === "error" && options.reason === "error") {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
850
907
|
this.postState("stopped", `Trace stopped (${options.reason}).`, false, false);
|
|
851
908
|
return;
|
|
852
909
|
}
|
|
@@ -869,9 +926,11 @@ var LiveTraceSession = class {
|
|
|
869
926
|
} catch (error) {
|
|
870
927
|
this.log(`Live Trace startup failed for ${this.options.target.app}: ${formatError(error)}`);
|
|
871
928
|
await this.stopRuntimeTrace(false);
|
|
929
|
+
const startupError = toStartupError(error);
|
|
872
930
|
if (!this.shouldStop()) {
|
|
873
|
-
this.postState("error",
|
|
931
|
+
this.postState("error", startupError.stateMessage, false, false);
|
|
874
932
|
}
|
|
933
|
+
throw startupError;
|
|
875
934
|
}
|
|
876
935
|
}
|
|
877
936
|
async startInspector(options) {
|
|
@@ -881,7 +940,7 @@ var LiveTraceSession = class {
|
|
|
881
940
|
return;
|
|
882
941
|
}
|
|
883
942
|
this.postState("starting-inspector", "Requesting Node Inspector startup.", false, false);
|
|
884
|
-
await this.dependencies.tryStartNodeInspector(this.options.target);
|
|
943
|
+
this.reportInspectorStartup(await this.dependencies.tryStartNodeInspector(this.options.target));
|
|
885
944
|
if (this.shouldStop()) {
|
|
886
945
|
return;
|
|
887
946
|
}
|
|
@@ -895,8 +954,10 @@ var LiveTraceSession = class {
|
|
|
895
954
|
return;
|
|
896
955
|
}
|
|
897
956
|
if (tunnel.status !== "ready") {
|
|
898
|
-
|
|
899
|
-
|
|
957
|
+
throw new LiveTraceStartupError(
|
|
958
|
+
"Node Inspector is not reachable on 127.0.0.1:9229.",
|
|
959
|
+
"Node Inspector is not reachable on 127.0.0.1:9229."
|
|
960
|
+
);
|
|
900
961
|
}
|
|
901
962
|
this.tunnelHandle = tunnel.handle;
|
|
902
963
|
this.inspectorClient = await this.dependencies.connectInspector(tunnel.handle.localPort);
|
|
@@ -967,11 +1028,27 @@ var LiveTraceSession = class {
|
|
|
967
1028
|
this.stopPolling();
|
|
968
1029
|
this.consecutiveDrainTimeouts = 0;
|
|
969
1030
|
const uninstalled = await this.stopInspectorHook(uninstallRuntimeHook);
|
|
970
|
-
await this.
|
|
1031
|
+
await this.closeInspectorClient();
|
|
1032
|
+
this.stopTunnel();
|
|
1033
|
+
return uninstalled;
|
|
1034
|
+
}
|
|
1035
|
+
async closeInspectorClient() {
|
|
1036
|
+
const client = this.inspectorClient;
|
|
971
1037
|
this.inspectorClient = void 0;
|
|
972
|
-
|
|
1038
|
+
try {
|
|
1039
|
+
await client?.close();
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
this.log(`Live Trace inspector close failed for ${this.options.target.app}: ${formatError(error)}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
stopTunnel() {
|
|
1045
|
+
const tunnel = this.tunnelHandle;
|
|
973
1046
|
this.tunnelHandle = void 0;
|
|
974
|
-
|
|
1047
|
+
try {
|
|
1048
|
+
tunnel?.stop();
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
this.log(`Live Trace tunnel cleanup failed for ${this.options.target.app}: ${formatError(error)}`);
|
|
1051
|
+
}
|
|
975
1052
|
}
|
|
976
1053
|
async stopInspectorHook(uninstallRuntimeHook) {
|
|
977
1054
|
if (this.inspectorClient === void 0) {
|
|
@@ -1015,6 +1092,21 @@ var LiveTraceSession = class {
|
|
|
1015
1092
|
log(message) {
|
|
1016
1093
|
this.options.onLog?.(message);
|
|
1017
1094
|
}
|
|
1095
|
+
reportInspectorStartup(result) {
|
|
1096
|
+
const normalized = normalizeInspectorStartupResult(result);
|
|
1097
|
+
if (normalized.status === "ready") {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const detail = normalized.detail === void 0 ? "" : `: ${normalized.detail}`;
|
|
1101
|
+
this.log(`Node Inspector startup was not confirmed for ${this.options.target.app}${detail}`);
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
var LiveTraceStartupError = class extends Error {
|
|
1105
|
+
constructor(message, stateMessage, cause) {
|
|
1106
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
1107
|
+
this.stateMessage = stateMessage;
|
|
1108
|
+
}
|
|
1109
|
+
stateMessage;
|
|
1018
1110
|
};
|
|
1019
1111
|
function resolveStartOptions(options) {
|
|
1020
1112
|
return {
|
|
@@ -1037,6 +1129,19 @@ function isEvaluateTimeout(error) {
|
|
|
1037
1129
|
const message = error instanceof Error ? error.message : String(error);
|
|
1038
1130
|
return message.includes("Runtime.evaluate timed out");
|
|
1039
1131
|
}
|
|
1132
|
+
function toStartupError(error) {
|
|
1133
|
+
if (error instanceof LiveTraceStartupError) {
|
|
1134
|
+
return error;
|
|
1135
|
+
}
|
|
1136
|
+
return new LiveTraceStartupError(
|
|
1137
|
+
"Runtime HTTP trace could not be started.",
|
|
1138
|
+
"Runtime HTTP trace could not be started.",
|
|
1139
|
+
error
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
function normalizeInspectorStartupResult(result) {
|
|
1143
|
+
return typeof result === "boolean" ? { status: result ? "ready" : "not-ready" } : result;
|
|
1144
|
+
}
|
|
1040
1145
|
function formatError(error) {
|
|
1041
1146
|
const message = error instanceof Error ? error.message : String(error);
|
|
1042
1147
|
return message.trim().length > 0 ? message.trim() : "Unknown error";
|
|
@@ -1057,6 +1162,7 @@ export {
|
|
|
1057
1162
|
openInspectorTunnel,
|
|
1058
1163
|
parseDrainResult,
|
|
1059
1164
|
prepareCfSession,
|
|
1165
|
+
startNodeInspector,
|
|
1060
1166
|
truncatePreview,
|
|
1061
1167
|
tryStartNodeInspector
|
|
1062
1168
|
};
|