autotel-devtools 20.0.1 → 21.0.0
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 +1 -0
- package/dist/cli.cjs +7 -4
- package/dist/cli.js +7 -4
- package/dist/{error-aggregator-CGgWr2OH.d.cts → error-aggregator-D8VKciLY.d.ts} +12 -10
- package/dist/{error-aggregator-DufjI2IG.d.ts → error-aggregator-DCEKMOm3.d.cts} +12 -10
- package/dist/{exporter-CEKKJTci.d.cts → exporter-Dt4kx128.d.cts} +15 -7
- package/dist/{exporter-BJwufQwg.d.ts → exporter-Due9Rd4s.d.ts} +15 -7
- package/dist/genai/index.cjs +93 -107
- package/dist/genai/index.d.cts +1 -32
- package/dist/genai/index.d.ts +1 -32
- package/dist/genai/index.js +94 -107
- package/dist/{http-uVPjDDld.js → http-CNZMrnzv.js} +214 -60
- package/dist/{http-j-oBI6r6.cjs → http-CXSzX4ee.cjs} +218 -57
- package/dist/index.cjs +11 -4
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +11 -4
- package/dist/json-fields-CPjKZ2WH.js +47 -0
- package/dist/json-fields-DZFlNqT2.cjs +83 -0
- package/dist/{listen-DGzVi7DE.cjs → listen-CEJ3nYJf.cjs} +1 -1
- package/dist/{listen-NVMbBWHw.js → listen-DBfsfcdd.js} +1 -1
- package/dist/server/exporter.d.cts +1 -1
- package/dist/server/exporter.d.ts +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.cts +11 -2
- package/dist/server/index.d.ts +11 -2
- package/dist/server/index.js +1 -1
- package/dist/types-DHDvZnnn.d.cts +47 -0
- package/dist/types-DHDvZnnn.d.ts +47 -0
- package/dist/widget.global.js +15 -13
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
+
const require_json_fields = require('./json-fields-DZFlNqT2.cjs');
|
|
28
29
|
const require_trace_root = require('./trace-root-BMa667jK.cjs');
|
|
29
30
|
const require_resource_utils = require('./resource-utils-DjHJB6uc.cjs');
|
|
30
31
|
let node_http = require("node:http");
|
|
@@ -32,6 +33,7 @@ let ws = require("ws");
|
|
|
32
33
|
let autotel_agents = require("autotel-agents");
|
|
33
34
|
let node_fs = require("node:fs");
|
|
34
35
|
let node_path = require("node:path");
|
|
36
|
+
node_path = __toESM(node_path, 1);
|
|
35
37
|
let node_url = require("node:url");
|
|
36
38
|
let protobufjs = require("protobufjs");
|
|
37
39
|
protobufjs = __toESM(protobufjs, 1);
|
|
@@ -103,9 +105,9 @@ var ErrorAggregator = class {
|
|
|
103
105
|
*/
|
|
104
106
|
extractErrorFromSpan(span, trace) {
|
|
105
107
|
const exceptionEvent = span.events?.find((e) => e.name === "exception");
|
|
106
|
-
const errorType = span.attributes
|
|
107
|
-
const errorMessage = span.status.message || span.attributes
|
|
108
|
-
const stackTrace = span.attributes
|
|
108
|
+
const errorType = require_json_fields.stringAttr(span.attributes, "exception.type", "error.type") ?? require_json_fields.stringAttr(exceptionEvent?.attributes, "exception.type") ?? "Error";
|
|
109
|
+
const errorMessage = span.status.message || require_json_fields.stringAttr(span.attributes, "exception.message", "error.message") || "Unknown error";
|
|
110
|
+
const stackTrace = require_json_fields.stringAttr(span.attributes, "exception.stacktrace", "exception.stack", "error.stack") ?? this.extractStackFromEvents(span);
|
|
109
111
|
return {
|
|
110
112
|
traceId: trace.traceId,
|
|
111
113
|
spanId: span.spanId,
|
|
@@ -115,7 +117,8 @@ var ErrorAggregator = class {
|
|
|
115
117
|
error: {
|
|
116
118
|
type: errorType,
|
|
117
119
|
message: errorMessage,
|
|
118
|
-
stackTrace
|
|
120
|
+
stackTrace,
|
|
121
|
+
fingerprint: require_json_fields.stringAttr(span.attributes, "exception.fingerprint") ?? require_json_fields.stringAttr(exceptionEvent?.attributes, "exception.fingerprint")
|
|
119
122
|
},
|
|
120
123
|
attributes: this.extractRelevantAttributes(span.attributes)
|
|
121
124
|
};
|
|
@@ -126,7 +129,7 @@ var ErrorAggregator = class {
|
|
|
126
129
|
extractStackFromEvents(span) {
|
|
127
130
|
if (!span.events) return void 0;
|
|
128
131
|
const exceptionEvent = span.events.find((e) => e.name === "exception");
|
|
129
|
-
if (exceptionEvent?.attributes) return exceptionEvent.attributes
|
|
132
|
+
if (exceptionEvent?.attributes) return require_json_fields.stringAttr(exceptionEvent.attributes, "exception.stacktrace", "exception.stack");
|
|
130
133
|
}
|
|
131
134
|
/**
|
|
132
135
|
* Extract relevant attributes for error context
|
|
@@ -155,6 +158,7 @@ var ErrorAggregator = class {
|
|
|
155
158
|
* Uses error type + first N stack frames (normalized)
|
|
156
159
|
*/
|
|
157
160
|
generateFingerprint(occurrence) {
|
|
161
|
+
if (occurrence.error.fingerprint) return occurrence.error.fingerprint;
|
|
158
162
|
const parts = [occurrence.error.type];
|
|
159
163
|
if (occurrence.error.stackTrace) {
|
|
160
164
|
const frames = this.extractStackFrames(occurrence.error.stackTrace, this.options.stackFramesForFingerprint);
|
|
@@ -558,13 +562,28 @@ var DevtoolsServer = class {
|
|
|
558
562
|
}
|
|
559
563
|
};
|
|
560
564
|
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/server/otlp-types.ts
|
|
567
|
+
/**
|
|
568
|
+
* An exporter's payload, read as the envelope it claims to be.
|
|
569
|
+
*
|
|
570
|
+
* SAFETY: this is the one place the receiver trusts the wire. Every field of
|
|
571
|
+
* every envelope above is optional, so a payload that is not what it claims
|
|
572
|
+
* reads back as empty arrays and undefined fields rather than throwing - which
|
|
573
|
+
* is what the callers below rely on when they find no spans to add.
|
|
574
|
+
*/
|
|
575
|
+
function otlpEnvelope(payload) {
|
|
576
|
+
if (typeof payload !== "object" || payload === null) return void 0;
|
|
577
|
+
return payload;
|
|
578
|
+
}
|
|
579
|
+
|
|
561
580
|
//#endregion
|
|
562
581
|
//#region src/server/otlp.ts
|
|
563
582
|
function resolveOtlpValue(v) {
|
|
564
583
|
if (!v) return void 0;
|
|
565
584
|
if (v.stringValue !== void 0) return v.stringValue;
|
|
566
585
|
if (v.boolValue !== void 0) return v.boolValue;
|
|
567
|
-
if (v.intValue !== void 0) return
|
|
586
|
+
if (v.intValue !== void 0) return Number(v.intValue);
|
|
568
587
|
if (v.doubleValue !== void 0) return v.doubleValue;
|
|
569
588
|
if (v.bytesValue !== void 0) return v.bytesValue;
|
|
570
589
|
if (v.arrayValue?.values) return v.arrayValue.values.map(resolveOtlpValue);
|
|
@@ -576,6 +595,29 @@ function flattenAttributes(attrs) {
|
|
|
576
595
|
for (const { key, value } of attrs) out[key] = resolveOtlpValue(value);
|
|
577
596
|
return out;
|
|
578
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* A log record's body: the text it carried, or the structure it carried when
|
|
600
|
+
* the sender used an OTLP kvlist or array rather than a string.
|
|
601
|
+
*/
|
|
602
|
+
function logBody(body) {
|
|
603
|
+
const text = require_json_fields.asString(body);
|
|
604
|
+
if (text !== void 0) return text;
|
|
605
|
+
if (body === void 0 || body === null) return "";
|
|
606
|
+
const structured = require_json_fields.asObject(body);
|
|
607
|
+
if (!structured) return String(body);
|
|
608
|
+
return structured;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Attributes handed to the agent layer, whose `Attributes` is OTel's own -
|
|
612
|
+
* scalars and arrays of scalars, nothing nested.
|
|
613
|
+
*
|
|
614
|
+
* SAFETY: a coding agent's metrics and events carry scalar attributes only,
|
|
615
|
+
* so the two shapes agree in practice. A sender that nests one anyway is
|
|
616
|
+
* rendered by the Agents tab as whatever it is rather than being dropped.
|
|
617
|
+
*/
|
|
618
|
+
function agentAttributes(attributes) {
|
|
619
|
+
return attributes;
|
|
620
|
+
}
|
|
579
621
|
function nanoToMs(nano) {
|
|
580
622
|
if (!nano) return 0;
|
|
581
623
|
const ns = BigInt(nano);
|
|
@@ -583,19 +625,19 @@ function nanoToMs(nano) {
|
|
|
583
625
|
const remNs = ns % 1000000n;
|
|
584
626
|
return Number(ms) + Number(remNs) / 1e6;
|
|
585
627
|
}
|
|
586
|
-
const SPAN_KIND_MAP =
|
|
587
|
-
0
|
|
588
|
-
1
|
|
589
|
-
2
|
|
590
|
-
3
|
|
591
|
-
4
|
|
592
|
-
5
|
|
593
|
-
SPAN_KIND_INTERNAL
|
|
594
|
-
SPAN_KIND_SERVER
|
|
595
|
-
SPAN_KIND_CLIENT
|
|
596
|
-
SPAN_KIND_PRODUCER
|
|
597
|
-
SPAN_KIND_CONSUMER
|
|
598
|
-
|
|
628
|
+
const SPAN_KIND_MAP = /* @__PURE__ */ new Map([
|
|
629
|
+
[0, "INTERNAL"],
|
|
630
|
+
[1, "INTERNAL"],
|
|
631
|
+
[2, "SERVER"],
|
|
632
|
+
[3, "CLIENT"],
|
|
633
|
+
[4, "PRODUCER"],
|
|
634
|
+
[5, "CONSUMER"],
|
|
635
|
+
["SPAN_KIND_INTERNAL", "INTERNAL"],
|
|
636
|
+
["SPAN_KIND_SERVER", "SERVER"],
|
|
637
|
+
["SPAN_KIND_CLIENT", "CLIENT"],
|
|
638
|
+
["SPAN_KIND_PRODUCER", "PRODUCER"],
|
|
639
|
+
["SPAN_KIND_CONSUMER", "CONSUMER"]
|
|
640
|
+
]);
|
|
599
641
|
function normalizeHexId(id) {
|
|
600
642
|
if (!id) return "";
|
|
601
643
|
if (/^[A-Za-z0-9+/=]+$/.test(id) && !/^[0-9a-f]+$/i.test(id) && (id.length === 12 || id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48)) try {
|
|
@@ -604,15 +646,13 @@ function normalizeHexId(id) {
|
|
|
604
646
|
return id;
|
|
605
647
|
}
|
|
606
648
|
function parseOtlpTraces(payload) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (!Array.isArray(resourceSpans) || resourceSpans.length === 0) return [];
|
|
649
|
+
const resourceSpans = otlpEnvelope(payload)?.resourceSpans;
|
|
650
|
+
if (!resourceSpans || resourceSpans.length === 0) return [];
|
|
610
651
|
const traceMap = /* @__PURE__ */ new Map();
|
|
611
652
|
for (const rs of resourceSpans) {
|
|
612
653
|
const resourceAttrs = flattenAttributes(rs.resource?.attributes);
|
|
613
654
|
const service = String(resourceAttrs["service.name"] || "unknown");
|
|
614
|
-
const
|
|
615
|
-
for (const ss of scopeSpans) {
|
|
655
|
+
for (const ss of rs.scopeSpans ?? []) {
|
|
616
656
|
const scope = ss.scope?.name ? {
|
|
617
657
|
name: ss.scope.name,
|
|
618
658
|
version: ss.scope.version || void 0
|
|
@@ -631,7 +671,7 @@ function parseOtlpTraces(payload) {
|
|
|
631
671
|
spanId: normalizeHexId(span.spanId),
|
|
632
672
|
parentSpanId: normalizeHexId(span.parentSpanId) || void 0,
|
|
633
673
|
name: span.name || "unknown",
|
|
634
|
-
kind: SPAN_KIND_MAP
|
|
674
|
+
kind: SPAN_KIND_MAP.get(span.kind ?? 0) ?? "INTERNAL",
|
|
635
675
|
startTime: startMs,
|
|
636
676
|
endTime: endMs,
|
|
637
677
|
duration: endMs - startMs,
|
|
@@ -643,12 +683,12 @@ function parseOtlpTraces(payload) {
|
|
|
643
683
|
code: status,
|
|
644
684
|
message: span.status?.message
|
|
645
685
|
},
|
|
646
|
-
events: (span.events
|
|
686
|
+
events: (span.events ?? []).map((e) => ({
|
|
647
687
|
name: e.name || "",
|
|
648
688
|
timestamp: nanoToMs(e.timeUnixNano),
|
|
649
689
|
attributes: flattenAttributes(e.attributes)
|
|
650
690
|
})),
|
|
651
|
-
links: (span.links
|
|
691
|
+
links: (span.links ?? []).map((l) => ({
|
|
652
692
|
traceId: normalizeHexId(l.traceId),
|
|
653
693
|
spanId: normalizeHexId(l.spanId),
|
|
654
694
|
attributes: flattenAttributes(l.attributes)
|
|
@@ -671,7 +711,7 @@ function parseOtlpTraces(payload) {
|
|
|
671
711
|
const startTime = Math.min(...sorted.map((s) => s.startTime));
|
|
672
712
|
const endTime = Math.max(...sorted.map((s) => s.endTime));
|
|
673
713
|
const hasError = sorted.some((s) => s.status.code === "ERROR");
|
|
674
|
-
|
|
714
|
+
const trace = {
|
|
675
715
|
traceId,
|
|
676
716
|
correlationId: traceId.slice(0, 16),
|
|
677
717
|
rootSpan,
|
|
@@ -680,20 +720,20 @@ function parseOtlpTraces(payload) {
|
|
|
680
720
|
endTime,
|
|
681
721
|
duration: endTime - startTime,
|
|
682
722
|
status: hasError ? "ERROR" : "OK",
|
|
683
|
-
service
|
|
684
|
-
|
|
685
|
-
|
|
723
|
+
service
|
|
724
|
+
};
|
|
725
|
+
if (partial) trace.partial = true;
|
|
726
|
+
traces.push(trace);
|
|
686
727
|
}
|
|
687
728
|
return traces;
|
|
688
729
|
}
|
|
689
730
|
function parseOtlpLogs(payload) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (!Array.isArray(resourceLogs)) return [];
|
|
731
|
+
const resourceLogs = otlpEnvelope(payload)?.resourceLogs;
|
|
732
|
+
if (!resourceLogs) return [];
|
|
693
733
|
const logs = [];
|
|
694
734
|
for (const rl of resourceLogs) {
|
|
695
735
|
const resourceAttrs = flattenAttributes(rl.resource?.attributes);
|
|
696
|
-
for (const sl of rl.scopeLogs
|
|
736
|
+
for (const sl of rl.scopeLogs ?? []) for (const rec of sl.logRecords ?? []) {
|
|
697
737
|
const timestamp = nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano);
|
|
698
738
|
const traceId = normalizeHexId(rec.traceId) || void 0;
|
|
699
739
|
const spanId = normalizeHexId(rec.spanId) || void 0;
|
|
@@ -705,7 +745,7 @@ function parseOtlpLogs(payload) {
|
|
|
705
745
|
resourceName: require_resource_utils.getResourceName(resourceAttrs),
|
|
706
746
|
severityText: rec.severityText,
|
|
707
747
|
severityNumber: rec.severityNumber,
|
|
708
|
-
body:
|
|
748
|
+
body: logBody(body),
|
|
709
749
|
timestamp,
|
|
710
750
|
attributes: flattenAttributes(rec.attributes),
|
|
711
751
|
resource: resourceAttrs
|
|
@@ -715,11 +755,10 @@ function parseOtlpLogs(payload) {
|
|
|
715
755
|
return logs;
|
|
716
756
|
}
|
|
717
757
|
function countOtlpMetrics(payload) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (!Array.isArray(resourceMetrics)) return 0;
|
|
758
|
+
const resourceMetrics = otlpEnvelope(payload)?.resourceMetrics;
|
|
759
|
+
if (!resourceMetrics) return 0;
|
|
721
760
|
let count = 0;
|
|
722
|
-
for (const rm of resourceMetrics) for (const sm of rm.scopeMetrics
|
|
761
|
+
for (const rm of resourceMetrics) for (const sm of rm.scopeMetrics ?? []) count += (sm.metrics ?? []).length;
|
|
723
762
|
return count;
|
|
724
763
|
}
|
|
725
764
|
function extractDataPoints(metric) {
|
|
@@ -729,14 +768,14 @@ function extractDataPoints(metric) {
|
|
|
729
768
|
const value = dp.asDouble !== void 0 ? Number(dp.asDouble) : dp.asInt !== void 0 ? Number(dp.asInt) : 0;
|
|
730
769
|
points.push({
|
|
731
770
|
value,
|
|
732
|
-
attributes: flattenAttributes(dp.attributes),
|
|
771
|
+
attributes: agentAttributes(flattenAttributes(dp.attributes)),
|
|
733
772
|
timestamp: nanoToMs(dp.timeUnixNano || dp.startTimeUnixNano)
|
|
734
773
|
});
|
|
735
774
|
}
|
|
736
775
|
const histPoints = metric.histogram?.dataPoints;
|
|
737
776
|
if (Array.isArray(histPoints)) for (const dp of histPoints) points.push({
|
|
738
777
|
value: dp.count !== void 0 ? Number(dp.count) : 0,
|
|
739
|
-
attributes: flattenAttributes(dp.attributes),
|
|
778
|
+
attributes: agentAttributes(flattenAttributes(dp.attributes)),
|
|
740
779
|
timestamp: nanoToMs(dp.timeUnixNano || dp.startTimeUnixNano)
|
|
741
780
|
});
|
|
742
781
|
return points;
|
|
@@ -752,19 +791,18 @@ function readTemporality(metric) {
|
|
|
752
791
|
if (raw === 1 || raw === "AGGREGATION_TEMPORALITY_DELTA") return "delta";
|
|
753
792
|
}
|
|
754
793
|
function parseOtlpMetrics(payload) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (!Array.isArray(resourceMetrics)) return [];
|
|
794
|
+
const resourceMetrics = otlpEnvelope(payload)?.resourceMetrics;
|
|
795
|
+
if (!resourceMetrics) return [];
|
|
758
796
|
const records = [];
|
|
759
797
|
for (const rm of resourceMetrics) {
|
|
760
|
-
const resource = flattenAttributes(rm.resource?.attributes);
|
|
761
|
-
for (const sm of rm.scopeMetrics
|
|
798
|
+
const resource = agentAttributes(flattenAttributes(rm.resource?.attributes));
|
|
799
|
+
for (const sm of rm.scopeMetrics ?? []) {
|
|
762
800
|
const scope = sm.scope?.name ? {
|
|
763
801
|
name: sm.scope.name,
|
|
764
802
|
version: sm.scope.version || void 0
|
|
765
803
|
} : void 0;
|
|
766
|
-
for (const metric of sm.metrics
|
|
767
|
-
name: metric.name,
|
|
804
|
+
for (const metric of sm.metrics ?? []) records.push({
|
|
805
|
+
name: metric.name ?? "",
|
|
768
806
|
unit: metric.unit || void 0,
|
|
769
807
|
description: metric.description || void 0,
|
|
770
808
|
temporality: readTemporality(metric),
|
|
@@ -783,20 +821,19 @@ function parseOtlpMetrics(payload) {
|
|
|
783
821
|
* `parseOtlpLogs`, which feeds the generic Logs tab.
|
|
784
822
|
*/
|
|
785
823
|
function parseOtlpAgentEvents(payload) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (!Array.isArray(resourceLogs)) return [];
|
|
824
|
+
const resourceLogs = otlpEnvelope(payload)?.resourceLogs;
|
|
825
|
+
if (!resourceLogs) return [];
|
|
789
826
|
const events = [];
|
|
790
827
|
for (const rl of resourceLogs) {
|
|
791
|
-
const resource = flattenAttributes(rl.resource?.attributes);
|
|
792
|
-
for (const sl of rl.scopeLogs
|
|
828
|
+
const resource = agentAttributes(flattenAttributes(rl.resource?.attributes));
|
|
829
|
+
for (const sl of rl.scopeLogs ?? []) {
|
|
793
830
|
const scope = sl.scope?.name ? {
|
|
794
831
|
name: sl.scope.name,
|
|
795
832
|
version: sl.scope.version || void 0
|
|
796
833
|
} : void 0;
|
|
797
|
-
for (const rec of sl.logRecords
|
|
798
|
-
const attributes = flattenAttributes(rec.attributes);
|
|
799
|
-
const eventName =
|
|
834
|
+
for (const rec of sl.logRecords ?? []) {
|
|
835
|
+
const attributes = agentAttributes(flattenAttributes(rec.attributes));
|
|
836
|
+
const eventName = rec.eventName || String(attributes["event.name"] ?? "");
|
|
800
837
|
events.push({
|
|
801
838
|
eventName,
|
|
802
839
|
timestamp: nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano),
|
|
@@ -1149,6 +1186,98 @@ async function probePortHolder(host, port, timeoutMs = 500) {
|
|
|
1149
1186
|
}
|
|
1150
1187
|
}
|
|
1151
1188
|
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/server/source-file.ts
|
|
1191
|
+
/** Refuse to slurp something huge just because a span named it. */
|
|
1192
|
+
const MAX_BYTES = 2e6;
|
|
1193
|
+
const DISABLED = /* @__PURE__ */ new Set([
|
|
1194
|
+
"false",
|
|
1195
|
+
"0",
|
|
1196
|
+
"off",
|
|
1197
|
+
"no",
|
|
1198
|
+
""
|
|
1199
|
+
]);
|
|
1200
|
+
/**
|
|
1201
|
+
* Decide what `GET /source` may read, from `AUTOTEL_DEVTOOLS_SOURCE_ROOT`.
|
|
1202
|
+
*
|
|
1203
|
+
* Defaults **on**, at the working directory: devtools is a local tool whose
|
|
1204
|
+
* whole point is showing you your own code, and requiring a flag for that would
|
|
1205
|
+
* mean nobody ever sees the feature. The blast radius stays small because two
|
|
1206
|
+
* other things still hold — the receiver is bound to loopback, and nothing
|
|
1207
|
+
* outside this directory is reachable. Set the variable to `false` to turn it
|
|
1208
|
+
* off outright.
|
|
1209
|
+
*
|
|
1210
|
+
* A non-loopback bind (`--host 0.0.0.0`) removes the first of those, and the
|
|
1211
|
+
* Origin guard does not replace it: a request with no `Origin` at all — any
|
|
1212
|
+
* `curl` on the network — passes. The root holds whatever else lives in the
|
|
1213
|
+
* project, `.env` included, so the default flips to **off** there. An explicit
|
|
1214
|
+
* root is still honoured: exposing it on purpose is the caller's call.
|
|
1215
|
+
*/
|
|
1216
|
+
function resolveSourceRoot(configured, cwd, loopbackOnly = true) {
|
|
1217
|
+
if (configured === void 0) return loopbackOnly ? cwd : void 0;
|
|
1218
|
+
if (DISABLED.has(configured.trim().toLowerCase())) return void 0;
|
|
1219
|
+
return configured;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Resolve `requested` against `root`, or return `null` if it escapes.
|
|
1223
|
+
*
|
|
1224
|
+
* Containment is judged on **real** paths so a symlink inside the root that
|
|
1225
|
+
* points outside it is rejected — lexical `..` stripping alone cannot see that.
|
|
1226
|
+
* The value returned is the *lexical* resolution, because the real one differs
|
|
1227
|
+
* from the caller's path whenever an ancestor is a symlink (on macOS both
|
|
1228
|
+
* `/tmp` and `/var` are), and a caller comparing paths should not have to know.
|
|
1229
|
+
*/
|
|
1230
|
+
function resolveWithinRoot(root, requested) {
|
|
1231
|
+
const lexicalRoot = node_path.default.resolve(root);
|
|
1232
|
+
const realRoot = safeRealpath(lexicalRoot);
|
|
1233
|
+
if (realRoot === null) return null;
|
|
1234
|
+
const lexicalTarget = node_path.default.resolve(lexicalRoot, requested);
|
|
1235
|
+
const realTarget = safeRealpath(lexicalTarget);
|
|
1236
|
+
if (realTarget === null) return null;
|
|
1237
|
+
if (!isInside(realRoot, realTarget)) return null;
|
|
1238
|
+
return lexicalTarget;
|
|
1239
|
+
}
|
|
1240
|
+
/** True when `target` is `root` itself or sits beneath it. */
|
|
1241
|
+
function isInside(root, target) {
|
|
1242
|
+
if (target === root) return true;
|
|
1243
|
+
return target.startsWith(root.endsWith(node_path.default.sep) ? root : root + node_path.default.sep);
|
|
1244
|
+
}
|
|
1245
|
+
function safeRealpath(p) {
|
|
1246
|
+
try {
|
|
1247
|
+
return (0, node_fs.realpathSync)(p);
|
|
1248
|
+
} catch {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Read `context` lines either side of `line` from a file inside `root`.
|
|
1254
|
+
* Returns `null` when the path escapes the root, is not a readable file, or is
|
|
1255
|
+
* too large — the caller cannot distinguish those, which is the point.
|
|
1256
|
+
*/
|
|
1257
|
+
function readSourceWindow(root, requested, line, context) {
|
|
1258
|
+
const resolved = resolveWithinRoot(root, requested);
|
|
1259
|
+
if (resolved === null) return null;
|
|
1260
|
+
let text;
|
|
1261
|
+
try {
|
|
1262
|
+
const stat = (0, node_fs.statSync)(resolved);
|
|
1263
|
+
if (!stat.isFile() || stat.size > MAX_BYTES) return null;
|
|
1264
|
+
text = (0, node_fs.readFileSync)(resolved, "utf8");
|
|
1265
|
+
} catch {
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
const all = text.split("\n");
|
|
1269
|
+
if (all.at(-1) === "") all.pop();
|
|
1270
|
+
const startLine = Math.max(1, line - context);
|
|
1271
|
+
const endLine = Math.min(all.length, line + context);
|
|
1272
|
+
if (startLine > all.length) return null;
|
|
1273
|
+
return {
|
|
1274
|
+
file: node_path.default.relative(node_path.default.resolve(root), resolved),
|
|
1275
|
+
line,
|
|
1276
|
+
startLine,
|
|
1277
|
+
lines: all.slice(startLine - 1, endLine)
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1152
1281
|
//#endregion
|
|
1153
1282
|
//#region src/server/http.ts
|
|
1154
1283
|
function sendOtlpError(res, req, e) {
|
|
@@ -1212,6 +1341,7 @@ function getWidgetJs() {
|
|
|
1212
1341
|
}
|
|
1213
1342
|
function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
1214
1343
|
const loopbackOnly = options.loopbackOnly ?? true;
|
|
1344
|
+
const sourceRoot = options.sourceRoot;
|
|
1215
1345
|
const fullpageHtml = renderFullpageHtml(options.title);
|
|
1216
1346
|
httpServer.on("request", async (req, res) => {
|
|
1217
1347
|
if (req.headers.upgrade?.toLowerCase() === "websocket") return;
|
|
@@ -1252,6 +1382,31 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
1252
1382
|
res.end(DEVTOOLS_FAVICON_SVG);
|
|
1253
1383
|
return;
|
|
1254
1384
|
}
|
|
1385
|
+
if (req.method === "GET" && url.split("?")[0] === "/source") {
|
|
1386
|
+
if (!sourceRoot) {
|
|
1387
|
+
sendJson(res, 404, { error: "Not found" });
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
1391
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const query = new URL(url, "http://localhost").searchParams;
|
|
1395
|
+
const file = query.get("file");
|
|
1396
|
+
const line = Number(query.get("line"));
|
|
1397
|
+
const context = Math.min(Math.max(Number(query.get("context") ?? 5) || 0, 0), 50);
|
|
1398
|
+
if (!file || !Number.isInteger(line) || line < 1) {
|
|
1399
|
+
sendJson(res, 400, { error: "file and a positive integer line are required" });
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const window = readSourceWindow(sourceRoot, file, line, context);
|
|
1403
|
+
if (window === null) {
|
|
1404
|
+
sendJson(res, 404, { error: "Not found" });
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
sendJson(res, 200, { ...window });
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1255
1410
|
if (req.method === "GET" && url === "/healthz") {
|
|
1256
1411
|
sendJson(res, 200, {
|
|
1257
1412
|
ok: true,
|
|
@@ -1438,6 +1593,12 @@ Object.defineProperty(exports, 'probePortHolder', {
|
|
|
1438
1593
|
return probePortHolder;
|
|
1439
1594
|
}
|
|
1440
1595
|
});
|
|
1596
|
+
Object.defineProperty(exports, 'resolveSourceRoot', {
|
|
1597
|
+
enumerable: true,
|
|
1598
|
+
get: function () {
|
|
1599
|
+
return resolveSourceRoot;
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1441
1602
|
Object.defineProperty(exports, 'resolveTelemetryLimits', {
|
|
1442
1603
|
enumerable: true,
|
|
1443
1604
|
get: function () {
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_http = require('./http-
|
|
3
|
-
const require_listen = require('./listen-
|
|
2
|
+
const require_http = require('./http-CXSzX4ee.cjs');
|
|
3
|
+
const require_listen = require('./listen-CEJ3nYJf.cjs');
|
|
4
4
|
const require_server_exporter = require('./server/exporter.cjs');
|
|
5
5
|
const require_server_log_exporter = require('./server/log-exporter.cjs');
|
|
6
6
|
const require_server_remote_exporter = require('./server/remote-exporter.cjs');
|
|
@@ -21,12 +21,19 @@ function createDevtools(options = {}) {
|
|
|
21
21
|
maxLogCount: options.maxLogCount,
|
|
22
22
|
maxMetricCount: options.maxMetricCount
|
|
23
23
|
});
|
|
24
|
-
require_http.
|
|
24
|
+
const sourceRoot = require_http.resolveSourceRoot(options.sourceRoot === false ? "false" : options.sourceRoot ?? process.env.AUTOTEL_DEVTOOLS_SOURCE_ROOT, process.cwd(), loopbackOnly);
|
|
25
|
+
require_http.attachDevtoolsRoutes(httpServer, wsServer, {
|
|
26
|
+
loopbackOnly,
|
|
27
|
+
sourceRoot
|
|
28
|
+
});
|
|
25
29
|
const listeners = require_listen.listenLoopbackDualStack({
|
|
26
30
|
primary: httpServer,
|
|
27
31
|
port,
|
|
28
32
|
host,
|
|
29
|
-
attachSecondary: (s) => require_http.attachDevtoolsRoutes(s, wsServer, {
|
|
33
|
+
attachSecondary: (s) => require_http.attachDevtoolsRoutes(s, wsServer, {
|
|
34
|
+
loopbackOnly,
|
|
35
|
+
sourceRoot
|
|
36
|
+
})
|
|
30
37
|
});
|
|
31
38
|
if (options.verbose) listeners.ready.then(({ warnings }) => {
|
|
32
39
|
for (const w of warnings) console.warn(`[autotel-devtools] ${w}`);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as SpanAttributes, t as AttributeValue } from "./types-DHDvZnnn.cjs";
|
|
2
|
+
import { a as ErrorGroup, c as MetricData, i as DevtoolsData, l as SpanData, n as DevtoolsServer, s as LogData, t as DevtoolsSpanExporter, u as TraceData } from "./exporter-Dt4kx128.cjs";
|
|
2
3
|
import { DevtoolsLogExporter } from "./server/log-exporter.cjs";
|
|
3
4
|
import { DevtoolsRemoteExporter } from "./server/remote-exporter.cjs";
|
|
4
|
-
import { t as ErrorAggregator } from "./error-aggregator-
|
|
5
|
+
import { t as ErrorAggregator } from "./error-aggregator-DCEKMOm3.cjs";
|
|
5
6
|
import { Server } from "node:http";
|
|
6
7
|
//#region src/index.d.ts
|
|
7
8
|
interface CreateDevtoolsOptions {
|
|
@@ -12,6 +13,14 @@ interface CreateDevtoolsOptions {
|
|
|
12
13
|
maxTraceCount?: number;
|
|
13
14
|
maxLogCount?: number;
|
|
14
15
|
maxMetricCount?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Project root the Errors tab may read source from, so a stack frame can show
|
|
18
|
+
* the line that threw.
|
|
19
|
+
*
|
|
20
|
+
* Same default as the CLI: the working directory on a loopback bind, off
|
|
21
|
+
* otherwise. `false` disables it. See `resolveSourceRoot`.
|
|
22
|
+
*/
|
|
23
|
+
sourceRoot?: string | false;
|
|
15
24
|
}
|
|
16
25
|
interface DevtoolsInstance {
|
|
17
26
|
server: DevtoolsServer;
|
|
@@ -22,4 +31,4 @@ interface DevtoolsInstance {
|
|
|
22
31
|
}
|
|
23
32
|
declare function createDevtools(options?: CreateDevtoolsOptions): DevtoolsInstance;
|
|
24
33
|
//#endregion
|
|
25
|
-
export { CreateDevtoolsOptions, type DevtoolsData, DevtoolsInstance, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, type ErrorGroup, type LogData, type MetricData, type SpanData, type TraceData, createDevtools };
|
|
34
|
+
export { type AttributeValue, CreateDevtoolsOptions, type DevtoolsData, DevtoolsInstance, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, type ErrorGroup, type LogData, type MetricData, type SpanAttributes, type SpanData, type TraceData, createDevtools };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as SpanAttributes, t as AttributeValue } from "./types-DHDvZnnn.js";
|
|
2
|
+
import { a as ErrorGroup, c as MetricData, i as DevtoolsData, l as SpanData, n as DevtoolsServer, s as LogData, t as DevtoolsSpanExporter, u as TraceData } from "./exporter-Due9Rd4s.js";
|
|
2
3
|
import { DevtoolsLogExporter } from "./server/log-exporter.js";
|
|
3
4
|
import { DevtoolsRemoteExporter } from "./server/remote-exporter.js";
|
|
4
|
-
import { t as ErrorAggregator } from "./error-aggregator-
|
|
5
|
+
import { t as ErrorAggregator } from "./error-aggregator-D8VKciLY.js";
|
|
5
6
|
import { Server } from "node:http";
|
|
6
7
|
//#region src/index.d.ts
|
|
7
8
|
interface CreateDevtoolsOptions {
|
|
@@ -12,6 +13,14 @@ interface CreateDevtoolsOptions {
|
|
|
12
13
|
maxTraceCount?: number;
|
|
13
14
|
maxLogCount?: number;
|
|
14
15
|
maxMetricCount?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Project root the Errors tab may read source from, so a stack frame can show
|
|
18
|
+
* the line that threw.
|
|
19
|
+
*
|
|
20
|
+
* Same default as the CLI: the working directory on a loopback bind, off
|
|
21
|
+
* otherwise. `false` disables it. See `resolveSourceRoot`.
|
|
22
|
+
*/
|
|
23
|
+
sourceRoot?: string | false;
|
|
15
24
|
}
|
|
16
25
|
interface DevtoolsInstance {
|
|
17
26
|
server: DevtoolsServer;
|
|
@@ -22,4 +31,4 @@ interface DevtoolsInstance {
|
|
|
22
31
|
}
|
|
23
32
|
declare function createDevtools(options?: CreateDevtoolsOptions): DevtoolsInstance;
|
|
24
33
|
//#endregion
|
|
25
|
-
export { CreateDevtoolsOptions, type DevtoolsData, DevtoolsInstance, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, type ErrorGroup, type LogData, type MetricData, type SpanData, type TraceData, createDevtools };
|
|
34
|
+
export { type AttributeValue, CreateDevtoolsOptions, type DevtoolsData, DevtoolsInstance, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, type ErrorGroup, type LogData, type MetricData, type SpanAttributes, type SpanData, type TraceData, createDevtools };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as listenLoopbackDualStack } from "./listen-
|
|
1
|
+
import { f as DevtoolsServer, m as hostHeaderIsLoopback, r as resolveSourceRoot, t as attachDevtoolsRoutes, x as ErrorAggregator } from "./http-CNZMrnzv.js";
|
|
2
|
+
import { t as listenLoopbackDualStack } from "./listen-DBfsfcdd.js";
|
|
3
3
|
import { DevtoolsSpanExporter } from "./server/exporter.js";
|
|
4
4
|
import { DevtoolsLogExporter } from "./server/log-exporter.js";
|
|
5
5
|
import { DevtoolsRemoteExporter } from "./server/remote-exporter.js";
|
|
@@ -20,12 +20,19 @@ function createDevtools(options = {}) {
|
|
|
20
20
|
maxLogCount: options.maxLogCount,
|
|
21
21
|
maxMetricCount: options.maxMetricCount
|
|
22
22
|
});
|
|
23
|
-
|
|
23
|
+
const sourceRoot = resolveSourceRoot(options.sourceRoot === false ? "false" : options.sourceRoot ?? process.env.AUTOTEL_DEVTOOLS_SOURCE_ROOT, process.cwd(), loopbackOnly);
|
|
24
|
+
attachDevtoolsRoutes(httpServer, wsServer, {
|
|
25
|
+
loopbackOnly,
|
|
26
|
+
sourceRoot
|
|
27
|
+
});
|
|
24
28
|
const listeners = listenLoopbackDualStack({
|
|
25
29
|
primary: httpServer,
|
|
26
30
|
port,
|
|
27
31
|
host,
|
|
28
|
-
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, {
|
|
32
|
+
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, {
|
|
33
|
+
loopbackOnly,
|
|
34
|
+
sourceRoot
|
|
35
|
+
})
|
|
29
36
|
});
|
|
30
37
|
if (options.verbose) listeners.ready.then(({ warnings }) => {
|
|
31
38
|
for (const w of warnings) console.warn(`[autotel-devtools] ${w}`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/widget/attrs.ts
|
|
2
|
+
/** The string an attribute carries, or undefined when it carried something else. */
|
|
3
|
+
function stringAttr(attributes, ...keys) {
|
|
4
|
+
for (const key of keys) {
|
|
5
|
+
const value = attributes?.[key];
|
|
6
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** The string a wire value carries, or undefined when it carried something else. */
|
|
10
|
+
function asString(value) {
|
|
11
|
+
return typeof value === "string" ? value : void 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The number a wire value carries. A numeric string counts: instrumentations
|
|
15
|
+
* routinely send counts and durations as strings.
|
|
16
|
+
*/
|
|
17
|
+
function asNumber(value) {
|
|
18
|
+
if (typeof value === "number") return value;
|
|
19
|
+
if (typeof value === "string" && value !== "") {
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The boolean a wire value carries, including the strings 'true' and 'false'. */
|
|
25
|
+
function asBoolean(value) {
|
|
26
|
+
if (typeof value === "boolean") return value;
|
|
27
|
+
if (value === "true") return true;
|
|
28
|
+
if (value === "false") return false;
|
|
29
|
+
}
|
|
30
|
+
/** The strings a wire value carries - a list of them, or a lone one. */
|
|
31
|
+
function asStringArray(value) {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
34
|
+
return strings.length === value.length ? strings : void 0;
|
|
35
|
+
}
|
|
36
|
+
return typeof value === "string" ? [value] : void 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/widget/utils/json-fields.ts
|
|
41
|
+
/** The value as an object, or `undefined` when it is anything else. */
|
|
42
|
+
function asObject(value) {
|
|
43
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { asStringArray as a, asString as i, asBoolean as n, stringAttr as o, asNumber as r, asObject as t };
|