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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { i as asString, o as stringAttr, t as asObject } from "./json-fields-CPjKZ2WH.js";
|
|
1
2
|
import { t as pickRoot } from "./trace-root-EHnvuA7f.js";
|
|
2
3
|
import { t as getResourceName } from "./resource-utils-B4UVvfnH.js";
|
|
3
4
|
import { createServer } from "node:http";
|
|
4
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5
6
|
import { ingestAgentEvents, ingestAgentMetrics } from "autotel-agents";
|
|
6
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { dirname, resolve } from "node:path";
|
|
7
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
8
|
+
import path, { dirname, resolve } from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import protobuf from "protobufjs";
|
|
10
11
|
|
|
@@ -75,9 +76,9 @@ var ErrorAggregator = class {
|
|
|
75
76
|
*/
|
|
76
77
|
extractErrorFromSpan(span, trace) {
|
|
77
78
|
const exceptionEvent = span.events?.find((e) => e.name === "exception");
|
|
78
|
-
const errorType = span.attributes
|
|
79
|
-
const errorMessage = span.status.message || span.attributes
|
|
80
|
-
const stackTrace = span.attributes
|
|
79
|
+
const errorType = stringAttr(span.attributes, "exception.type", "error.type") ?? stringAttr(exceptionEvent?.attributes, "exception.type") ?? "Error";
|
|
80
|
+
const errorMessage = span.status.message || stringAttr(span.attributes, "exception.message", "error.message") || "Unknown error";
|
|
81
|
+
const stackTrace = stringAttr(span.attributes, "exception.stacktrace", "exception.stack", "error.stack") ?? this.extractStackFromEvents(span);
|
|
81
82
|
return {
|
|
82
83
|
traceId: trace.traceId,
|
|
83
84
|
spanId: span.spanId,
|
|
@@ -87,7 +88,8 @@ var ErrorAggregator = class {
|
|
|
87
88
|
error: {
|
|
88
89
|
type: errorType,
|
|
89
90
|
message: errorMessage,
|
|
90
|
-
stackTrace
|
|
91
|
+
stackTrace,
|
|
92
|
+
fingerprint: stringAttr(span.attributes, "exception.fingerprint") ?? stringAttr(exceptionEvent?.attributes, "exception.fingerprint")
|
|
91
93
|
},
|
|
92
94
|
attributes: this.extractRelevantAttributes(span.attributes)
|
|
93
95
|
};
|
|
@@ -98,7 +100,7 @@ var ErrorAggregator = class {
|
|
|
98
100
|
extractStackFromEvents(span) {
|
|
99
101
|
if (!span.events) return void 0;
|
|
100
102
|
const exceptionEvent = span.events.find((e) => e.name === "exception");
|
|
101
|
-
if (exceptionEvent?.attributes) return exceptionEvent.attributes
|
|
103
|
+
if (exceptionEvent?.attributes) return stringAttr(exceptionEvent.attributes, "exception.stacktrace", "exception.stack");
|
|
102
104
|
}
|
|
103
105
|
/**
|
|
104
106
|
* Extract relevant attributes for error context
|
|
@@ -127,6 +129,7 @@ var ErrorAggregator = class {
|
|
|
127
129
|
* Uses error type + first N stack frames (normalized)
|
|
128
130
|
*/
|
|
129
131
|
generateFingerprint(occurrence) {
|
|
132
|
+
if (occurrence.error.fingerprint) return occurrence.error.fingerprint;
|
|
130
133
|
const parts = [occurrence.error.type];
|
|
131
134
|
if (occurrence.error.stackTrace) {
|
|
132
135
|
const frames = this.extractStackFrames(occurrence.error.stackTrace, this.options.stackFramesForFingerprint);
|
|
@@ -530,13 +533,28 @@ var DevtoolsServer = class {
|
|
|
530
533
|
}
|
|
531
534
|
};
|
|
532
535
|
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/server/otlp-types.ts
|
|
538
|
+
/**
|
|
539
|
+
* An exporter's payload, read as the envelope it claims to be.
|
|
540
|
+
*
|
|
541
|
+
* SAFETY: this is the one place the receiver trusts the wire. Every field of
|
|
542
|
+
* every envelope above is optional, so a payload that is not what it claims
|
|
543
|
+
* reads back as empty arrays and undefined fields rather than throwing - which
|
|
544
|
+
* is what the callers below rely on when they find no spans to add.
|
|
545
|
+
*/
|
|
546
|
+
function otlpEnvelope(payload) {
|
|
547
|
+
if (typeof payload !== "object" || payload === null) return void 0;
|
|
548
|
+
return payload;
|
|
549
|
+
}
|
|
550
|
+
|
|
533
551
|
//#endregion
|
|
534
552
|
//#region src/server/otlp.ts
|
|
535
553
|
function resolveOtlpValue(v) {
|
|
536
554
|
if (!v) return void 0;
|
|
537
555
|
if (v.stringValue !== void 0) return v.stringValue;
|
|
538
556
|
if (v.boolValue !== void 0) return v.boolValue;
|
|
539
|
-
if (v.intValue !== void 0) return
|
|
557
|
+
if (v.intValue !== void 0) return Number(v.intValue);
|
|
540
558
|
if (v.doubleValue !== void 0) return v.doubleValue;
|
|
541
559
|
if (v.bytesValue !== void 0) return v.bytesValue;
|
|
542
560
|
if (v.arrayValue?.values) return v.arrayValue.values.map(resolveOtlpValue);
|
|
@@ -548,6 +566,29 @@ function flattenAttributes(attrs) {
|
|
|
548
566
|
for (const { key, value } of attrs) out[key] = resolveOtlpValue(value);
|
|
549
567
|
return out;
|
|
550
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* A log record's body: the text it carried, or the structure it carried when
|
|
571
|
+
* the sender used an OTLP kvlist or array rather than a string.
|
|
572
|
+
*/
|
|
573
|
+
function logBody(body) {
|
|
574
|
+
const text = asString(body);
|
|
575
|
+
if (text !== void 0) return text;
|
|
576
|
+
if (body === void 0 || body === null) return "";
|
|
577
|
+
const structured = asObject(body);
|
|
578
|
+
if (!structured) return String(body);
|
|
579
|
+
return structured;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Attributes handed to the agent layer, whose `Attributes` is OTel's own -
|
|
583
|
+
* scalars and arrays of scalars, nothing nested.
|
|
584
|
+
*
|
|
585
|
+
* SAFETY: a coding agent's metrics and events carry scalar attributes only,
|
|
586
|
+
* so the two shapes agree in practice. A sender that nests one anyway is
|
|
587
|
+
* rendered by the Agents tab as whatever it is rather than being dropped.
|
|
588
|
+
*/
|
|
589
|
+
function agentAttributes(attributes) {
|
|
590
|
+
return attributes;
|
|
591
|
+
}
|
|
551
592
|
function nanoToMs(nano) {
|
|
552
593
|
if (!nano) return 0;
|
|
553
594
|
const ns = BigInt(nano);
|
|
@@ -555,19 +596,19 @@ function nanoToMs(nano) {
|
|
|
555
596
|
const remNs = ns % 1000000n;
|
|
556
597
|
return Number(ms) + Number(remNs) / 1e6;
|
|
557
598
|
}
|
|
558
|
-
const SPAN_KIND_MAP =
|
|
559
|
-
0
|
|
560
|
-
1
|
|
561
|
-
2
|
|
562
|
-
3
|
|
563
|
-
4
|
|
564
|
-
5
|
|
565
|
-
SPAN_KIND_INTERNAL
|
|
566
|
-
SPAN_KIND_SERVER
|
|
567
|
-
SPAN_KIND_CLIENT
|
|
568
|
-
SPAN_KIND_PRODUCER
|
|
569
|
-
SPAN_KIND_CONSUMER
|
|
570
|
-
|
|
599
|
+
const SPAN_KIND_MAP = /* @__PURE__ */ new Map([
|
|
600
|
+
[0, "INTERNAL"],
|
|
601
|
+
[1, "INTERNAL"],
|
|
602
|
+
[2, "SERVER"],
|
|
603
|
+
[3, "CLIENT"],
|
|
604
|
+
[4, "PRODUCER"],
|
|
605
|
+
[5, "CONSUMER"],
|
|
606
|
+
["SPAN_KIND_INTERNAL", "INTERNAL"],
|
|
607
|
+
["SPAN_KIND_SERVER", "SERVER"],
|
|
608
|
+
["SPAN_KIND_CLIENT", "CLIENT"],
|
|
609
|
+
["SPAN_KIND_PRODUCER", "PRODUCER"],
|
|
610
|
+
["SPAN_KIND_CONSUMER", "CONSUMER"]
|
|
611
|
+
]);
|
|
571
612
|
function normalizeHexId(id) {
|
|
572
613
|
if (!id) return "";
|
|
573
614
|
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 {
|
|
@@ -576,15 +617,13 @@ function normalizeHexId(id) {
|
|
|
576
617
|
return id;
|
|
577
618
|
}
|
|
578
619
|
function parseOtlpTraces(payload) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (!Array.isArray(resourceSpans) || resourceSpans.length === 0) return [];
|
|
620
|
+
const resourceSpans = otlpEnvelope(payload)?.resourceSpans;
|
|
621
|
+
if (!resourceSpans || resourceSpans.length === 0) return [];
|
|
582
622
|
const traceMap = /* @__PURE__ */ new Map();
|
|
583
623
|
for (const rs of resourceSpans) {
|
|
584
624
|
const resourceAttrs = flattenAttributes(rs.resource?.attributes);
|
|
585
625
|
const service = String(resourceAttrs["service.name"] || "unknown");
|
|
586
|
-
const
|
|
587
|
-
for (const ss of scopeSpans) {
|
|
626
|
+
for (const ss of rs.scopeSpans ?? []) {
|
|
588
627
|
const scope = ss.scope?.name ? {
|
|
589
628
|
name: ss.scope.name,
|
|
590
629
|
version: ss.scope.version || void 0
|
|
@@ -603,7 +642,7 @@ function parseOtlpTraces(payload) {
|
|
|
603
642
|
spanId: normalizeHexId(span.spanId),
|
|
604
643
|
parentSpanId: normalizeHexId(span.parentSpanId) || void 0,
|
|
605
644
|
name: span.name || "unknown",
|
|
606
|
-
kind: SPAN_KIND_MAP
|
|
645
|
+
kind: SPAN_KIND_MAP.get(span.kind ?? 0) ?? "INTERNAL",
|
|
607
646
|
startTime: startMs,
|
|
608
647
|
endTime: endMs,
|
|
609
648
|
duration: endMs - startMs,
|
|
@@ -615,12 +654,12 @@ function parseOtlpTraces(payload) {
|
|
|
615
654
|
code: status,
|
|
616
655
|
message: span.status?.message
|
|
617
656
|
},
|
|
618
|
-
events: (span.events
|
|
657
|
+
events: (span.events ?? []).map((e) => ({
|
|
619
658
|
name: e.name || "",
|
|
620
659
|
timestamp: nanoToMs(e.timeUnixNano),
|
|
621
660
|
attributes: flattenAttributes(e.attributes)
|
|
622
661
|
})),
|
|
623
|
-
links: (span.links
|
|
662
|
+
links: (span.links ?? []).map((l) => ({
|
|
624
663
|
traceId: normalizeHexId(l.traceId),
|
|
625
664
|
spanId: normalizeHexId(l.spanId),
|
|
626
665
|
attributes: flattenAttributes(l.attributes)
|
|
@@ -643,7 +682,7 @@ function parseOtlpTraces(payload) {
|
|
|
643
682
|
const startTime = Math.min(...sorted.map((s) => s.startTime));
|
|
644
683
|
const endTime = Math.max(...sorted.map((s) => s.endTime));
|
|
645
684
|
const hasError = sorted.some((s) => s.status.code === "ERROR");
|
|
646
|
-
|
|
685
|
+
const trace = {
|
|
647
686
|
traceId,
|
|
648
687
|
correlationId: traceId.slice(0, 16),
|
|
649
688
|
rootSpan,
|
|
@@ -652,20 +691,20 @@ function parseOtlpTraces(payload) {
|
|
|
652
691
|
endTime,
|
|
653
692
|
duration: endTime - startTime,
|
|
654
693
|
status: hasError ? "ERROR" : "OK",
|
|
655
|
-
service
|
|
656
|
-
|
|
657
|
-
|
|
694
|
+
service
|
|
695
|
+
};
|
|
696
|
+
if (partial) trace.partial = true;
|
|
697
|
+
traces.push(trace);
|
|
658
698
|
}
|
|
659
699
|
return traces;
|
|
660
700
|
}
|
|
661
701
|
function parseOtlpLogs(payload) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (!Array.isArray(resourceLogs)) return [];
|
|
702
|
+
const resourceLogs = otlpEnvelope(payload)?.resourceLogs;
|
|
703
|
+
if (!resourceLogs) return [];
|
|
665
704
|
const logs = [];
|
|
666
705
|
for (const rl of resourceLogs) {
|
|
667
706
|
const resourceAttrs = flattenAttributes(rl.resource?.attributes);
|
|
668
|
-
for (const sl of rl.scopeLogs
|
|
707
|
+
for (const sl of rl.scopeLogs ?? []) for (const rec of sl.logRecords ?? []) {
|
|
669
708
|
const timestamp = nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano);
|
|
670
709
|
const traceId = normalizeHexId(rec.traceId) || void 0;
|
|
671
710
|
const spanId = normalizeHexId(rec.spanId) || void 0;
|
|
@@ -677,7 +716,7 @@ function parseOtlpLogs(payload) {
|
|
|
677
716
|
resourceName: getResourceName(resourceAttrs),
|
|
678
717
|
severityText: rec.severityText,
|
|
679
718
|
severityNumber: rec.severityNumber,
|
|
680
|
-
body:
|
|
719
|
+
body: logBody(body),
|
|
681
720
|
timestamp,
|
|
682
721
|
attributes: flattenAttributes(rec.attributes),
|
|
683
722
|
resource: resourceAttrs
|
|
@@ -687,11 +726,10 @@ function parseOtlpLogs(payload) {
|
|
|
687
726
|
return logs;
|
|
688
727
|
}
|
|
689
728
|
function countOtlpMetrics(payload) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (!Array.isArray(resourceMetrics)) return 0;
|
|
729
|
+
const resourceMetrics = otlpEnvelope(payload)?.resourceMetrics;
|
|
730
|
+
if (!resourceMetrics) return 0;
|
|
693
731
|
let count = 0;
|
|
694
|
-
for (const rm of resourceMetrics) for (const sm of rm.scopeMetrics
|
|
732
|
+
for (const rm of resourceMetrics) for (const sm of rm.scopeMetrics ?? []) count += (sm.metrics ?? []).length;
|
|
695
733
|
return count;
|
|
696
734
|
}
|
|
697
735
|
function extractDataPoints(metric) {
|
|
@@ -701,14 +739,14 @@ function extractDataPoints(metric) {
|
|
|
701
739
|
const value = dp.asDouble !== void 0 ? Number(dp.asDouble) : dp.asInt !== void 0 ? Number(dp.asInt) : 0;
|
|
702
740
|
points.push({
|
|
703
741
|
value,
|
|
704
|
-
attributes: flattenAttributes(dp.attributes),
|
|
742
|
+
attributes: agentAttributes(flattenAttributes(dp.attributes)),
|
|
705
743
|
timestamp: nanoToMs(dp.timeUnixNano || dp.startTimeUnixNano)
|
|
706
744
|
});
|
|
707
745
|
}
|
|
708
746
|
const histPoints = metric.histogram?.dataPoints;
|
|
709
747
|
if (Array.isArray(histPoints)) for (const dp of histPoints) points.push({
|
|
710
748
|
value: dp.count !== void 0 ? Number(dp.count) : 0,
|
|
711
|
-
attributes: flattenAttributes(dp.attributes),
|
|
749
|
+
attributes: agentAttributes(flattenAttributes(dp.attributes)),
|
|
712
750
|
timestamp: nanoToMs(dp.timeUnixNano || dp.startTimeUnixNano)
|
|
713
751
|
});
|
|
714
752
|
return points;
|
|
@@ -724,19 +762,18 @@ function readTemporality(metric) {
|
|
|
724
762
|
if (raw === 1 || raw === "AGGREGATION_TEMPORALITY_DELTA") return "delta";
|
|
725
763
|
}
|
|
726
764
|
function parseOtlpMetrics(payload) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (!Array.isArray(resourceMetrics)) return [];
|
|
765
|
+
const resourceMetrics = otlpEnvelope(payload)?.resourceMetrics;
|
|
766
|
+
if (!resourceMetrics) return [];
|
|
730
767
|
const records = [];
|
|
731
768
|
for (const rm of resourceMetrics) {
|
|
732
|
-
const resource = flattenAttributes(rm.resource?.attributes);
|
|
733
|
-
for (const sm of rm.scopeMetrics
|
|
769
|
+
const resource = agentAttributes(flattenAttributes(rm.resource?.attributes));
|
|
770
|
+
for (const sm of rm.scopeMetrics ?? []) {
|
|
734
771
|
const scope = sm.scope?.name ? {
|
|
735
772
|
name: sm.scope.name,
|
|
736
773
|
version: sm.scope.version || void 0
|
|
737
774
|
} : void 0;
|
|
738
|
-
for (const metric of sm.metrics
|
|
739
|
-
name: metric.name,
|
|
775
|
+
for (const metric of sm.metrics ?? []) records.push({
|
|
776
|
+
name: metric.name ?? "",
|
|
740
777
|
unit: metric.unit || void 0,
|
|
741
778
|
description: metric.description || void 0,
|
|
742
779
|
temporality: readTemporality(metric),
|
|
@@ -755,20 +792,19 @@ function parseOtlpMetrics(payload) {
|
|
|
755
792
|
* `parseOtlpLogs`, which feeds the generic Logs tab.
|
|
756
793
|
*/
|
|
757
794
|
function parseOtlpAgentEvents(payload) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (!Array.isArray(resourceLogs)) return [];
|
|
795
|
+
const resourceLogs = otlpEnvelope(payload)?.resourceLogs;
|
|
796
|
+
if (!resourceLogs) return [];
|
|
761
797
|
const events = [];
|
|
762
798
|
for (const rl of resourceLogs) {
|
|
763
|
-
const resource = flattenAttributes(rl.resource?.attributes);
|
|
764
|
-
for (const sl of rl.scopeLogs
|
|
799
|
+
const resource = agentAttributes(flattenAttributes(rl.resource?.attributes));
|
|
800
|
+
for (const sl of rl.scopeLogs ?? []) {
|
|
765
801
|
const scope = sl.scope?.name ? {
|
|
766
802
|
name: sl.scope.name,
|
|
767
803
|
version: sl.scope.version || void 0
|
|
768
804
|
} : void 0;
|
|
769
|
-
for (const rec of sl.logRecords
|
|
770
|
-
const attributes = flattenAttributes(rec.attributes);
|
|
771
|
-
const eventName =
|
|
805
|
+
for (const rec of sl.logRecords ?? []) {
|
|
806
|
+
const attributes = agentAttributes(flattenAttributes(rec.attributes));
|
|
807
|
+
const eventName = rec.eventName || String(attributes["event.name"] ?? "");
|
|
772
808
|
events.push({
|
|
773
809
|
eventName,
|
|
774
810
|
timestamp: nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano),
|
|
@@ -1121,6 +1157,98 @@ async function probePortHolder(host, port, timeoutMs = 500) {
|
|
|
1121
1157
|
}
|
|
1122
1158
|
}
|
|
1123
1159
|
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/server/source-file.ts
|
|
1162
|
+
/** Refuse to slurp something huge just because a span named it. */
|
|
1163
|
+
const MAX_BYTES = 2e6;
|
|
1164
|
+
const DISABLED = /* @__PURE__ */ new Set([
|
|
1165
|
+
"false",
|
|
1166
|
+
"0",
|
|
1167
|
+
"off",
|
|
1168
|
+
"no",
|
|
1169
|
+
""
|
|
1170
|
+
]);
|
|
1171
|
+
/**
|
|
1172
|
+
* Decide what `GET /source` may read, from `AUTOTEL_DEVTOOLS_SOURCE_ROOT`.
|
|
1173
|
+
*
|
|
1174
|
+
* Defaults **on**, at the working directory: devtools is a local tool whose
|
|
1175
|
+
* whole point is showing you your own code, and requiring a flag for that would
|
|
1176
|
+
* mean nobody ever sees the feature. The blast radius stays small because two
|
|
1177
|
+
* other things still hold — the receiver is bound to loopback, and nothing
|
|
1178
|
+
* outside this directory is reachable. Set the variable to `false` to turn it
|
|
1179
|
+
* off outright.
|
|
1180
|
+
*
|
|
1181
|
+
* A non-loopback bind (`--host 0.0.0.0`) removes the first of those, and the
|
|
1182
|
+
* Origin guard does not replace it: a request with no `Origin` at all — any
|
|
1183
|
+
* `curl` on the network — passes. The root holds whatever else lives in the
|
|
1184
|
+
* project, `.env` included, so the default flips to **off** there. An explicit
|
|
1185
|
+
* root is still honoured: exposing it on purpose is the caller's call.
|
|
1186
|
+
*/
|
|
1187
|
+
function resolveSourceRoot(configured, cwd, loopbackOnly = true) {
|
|
1188
|
+
if (configured === void 0) return loopbackOnly ? cwd : void 0;
|
|
1189
|
+
if (DISABLED.has(configured.trim().toLowerCase())) return void 0;
|
|
1190
|
+
return configured;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Resolve `requested` against `root`, or return `null` if it escapes.
|
|
1194
|
+
*
|
|
1195
|
+
* Containment is judged on **real** paths so a symlink inside the root that
|
|
1196
|
+
* points outside it is rejected — lexical `..` stripping alone cannot see that.
|
|
1197
|
+
* The value returned is the *lexical* resolution, because the real one differs
|
|
1198
|
+
* from the caller's path whenever an ancestor is a symlink (on macOS both
|
|
1199
|
+
* `/tmp` and `/var` are), and a caller comparing paths should not have to know.
|
|
1200
|
+
*/
|
|
1201
|
+
function resolveWithinRoot(root, requested) {
|
|
1202
|
+
const lexicalRoot = path.resolve(root);
|
|
1203
|
+
const realRoot = safeRealpath(lexicalRoot);
|
|
1204
|
+
if (realRoot === null) return null;
|
|
1205
|
+
const lexicalTarget = path.resolve(lexicalRoot, requested);
|
|
1206
|
+
const realTarget = safeRealpath(lexicalTarget);
|
|
1207
|
+
if (realTarget === null) return null;
|
|
1208
|
+
if (!isInside(realRoot, realTarget)) return null;
|
|
1209
|
+
return lexicalTarget;
|
|
1210
|
+
}
|
|
1211
|
+
/** True when `target` is `root` itself or sits beneath it. */
|
|
1212
|
+
function isInside(root, target) {
|
|
1213
|
+
if (target === root) return true;
|
|
1214
|
+
return target.startsWith(root.endsWith(path.sep) ? root : root + path.sep);
|
|
1215
|
+
}
|
|
1216
|
+
function safeRealpath(p) {
|
|
1217
|
+
try {
|
|
1218
|
+
return realpathSync(p);
|
|
1219
|
+
} catch {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Read `context` lines either side of `line` from a file inside `root`.
|
|
1225
|
+
* Returns `null` when the path escapes the root, is not a readable file, or is
|
|
1226
|
+
* too large — the caller cannot distinguish those, which is the point.
|
|
1227
|
+
*/
|
|
1228
|
+
function readSourceWindow(root, requested, line, context) {
|
|
1229
|
+
const resolved = resolveWithinRoot(root, requested);
|
|
1230
|
+
if (resolved === null) return null;
|
|
1231
|
+
let text;
|
|
1232
|
+
try {
|
|
1233
|
+
const stat = statSync(resolved);
|
|
1234
|
+
if (!stat.isFile() || stat.size > MAX_BYTES) return null;
|
|
1235
|
+
text = readFileSync(resolved, "utf8");
|
|
1236
|
+
} catch {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
const all = text.split("\n");
|
|
1240
|
+
if (all.at(-1) === "") all.pop();
|
|
1241
|
+
const startLine = Math.max(1, line - context);
|
|
1242
|
+
const endLine = Math.min(all.length, line + context);
|
|
1243
|
+
if (startLine > all.length) return null;
|
|
1244
|
+
return {
|
|
1245
|
+
file: path.relative(path.resolve(root), resolved),
|
|
1246
|
+
line,
|
|
1247
|
+
startLine,
|
|
1248
|
+
lines: all.slice(startLine - 1, endLine)
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1124
1252
|
//#endregion
|
|
1125
1253
|
//#region src/server/http.ts
|
|
1126
1254
|
function sendOtlpError(res, req, e) {
|
|
@@ -1184,6 +1312,7 @@ function getWidgetJs() {
|
|
|
1184
1312
|
}
|
|
1185
1313
|
function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
1186
1314
|
const loopbackOnly = options.loopbackOnly ?? true;
|
|
1315
|
+
const sourceRoot = options.sourceRoot;
|
|
1187
1316
|
const fullpageHtml = renderFullpageHtml(options.title);
|
|
1188
1317
|
httpServer.on("request", async (req, res) => {
|
|
1189
1318
|
if (req.headers.upgrade?.toLowerCase() === "websocket") return;
|
|
@@ -1224,6 +1353,31 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
1224
1353
|
res.end(DEVTOOLS_FAVICON_SVG);
|
|
1225
1354
|
return;
|
|
1226
1355
|
}
|
|
1356
|
+
if (req.method === "GET" && url.split("?")[0] === "/source") {
|
|
1357
|
+
if (!sourceRoot) {
|
|
1358
|
+
sendJson(res, 404, { error: "Not found" });
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
1362
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const query = new URL(url, "http://localhost").searchParams;
|
|
1366
|
+
const file = query.get("file");
|
|
1367
|
+
const line = Number(query.get("line"));
|
|
1368
|
+
const context = Math.min(Math.max(Number(query.get("context") ?? 5) || 0, 0), 50);
|
|
1369
|
+
if (!file || !Number.isInteger(line) || line < 1) {
|
|
1370
|
+
sendJson(res, 400, { error: "file and a positive integer line are required" });
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const window = readSourceWindow(sourceRoot, file, line, context);
|
|
1374
|
+
if (window === null) {
|
|
1375
|
+
sendJson(res, 404, { error: "Not found" });
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
sendJson(res, 200, { ...window });
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1227
1381
|
if (req.method === "GET" && url === "/healthz") {
|
|
1228
1382
|
sendJson(res, 200, {
|
|
1229
1383
|
ok: true,
|
|
@@ -1296,4 +1450,4 @@ function createDevtoolsHttpServer(devtools, _options = {}) {
|
|
|
1296
1450
|
}
|
|
1297
1451
|
|
|
1298
1452
|
//#endregion
|
|
1299
|
-
export {
|
|
1453
|
+
export { appendManyWithLimit as _, probePortHolder as a, resolveTelemetryLimits as b, decodeOtlpTraceRequest as c, parseOtlpTraces as d, DevtoolsServer as f, originIsLoopback as g, isLoopbackHostname as h, DEVTOOLS_IDENTITY as i, isProtobufContentType as l, hostHeaderIsLoopback as m, createDevtoolsHttpServer as n, decodeOtlpLogsRequest as o, allowSensitiveRequest as p, resolveSourceRoot as r, decodeOtlpMetricsRequest as s, attachDevtoolsRoutes as t, parseOtlpLogs as u, appendWithLimit as v, ErrorAggregator as x, applyTelemetryLimits as y };
|