autotel-devtools 6.0.0 → 6.0.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 +6 -1
- package/dist/cli.cjs +80 -19
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +80 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +51 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +51 -12
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +3 -0
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/widget.global.js +1 -1
- package/package.json +2 -2
- package/skills/autotel-devtools/SKILL.md +4 -2
package/dist/cli.js
CHANGED
|
@@ -366,6 +366,9 @@ var DevtoolsServer = class {
|
|
|
366
366
|
this.onData = options.onData;
|
|
367
367
|
this.httpServer = options.server ?? createServer();
|
|
368
368
|
this.wss = new WebSocketServer({ server: this.httpServer, path: options.path ?? "/ws" });
|
|
369
|
+
this.wss.on("error", (err) => {
|
|
370
|
+
if (this.httpServer.listening) throw err;
|
|
371
|
+
});
|
|
369
372
|
this.wss.on("connection", (ws) => {
|
|
370
373
|
this.clients.add(ws);
|
|
371
374
|
this.log(`Client connected (${this.clients.size} total)`);
|
|
@@ -1027,43 +1030,79 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1027
1030
|
});
|
|
1028
1031
|
}
|
|
1029
1032
|
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
1033
|
+
var DEFAULT_MAX_PORT_TRIES = 20;
|
|
1030
1034
|
function formatAddress(host, port) {
|
|
1031
1035
|
return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
|
|
1032
1036
|
}
|
|
1033
1037
|
function listenLoopbackDualStack(args) {
|
|
1034
|
-
const { primary, port, host, attachSecondary } = args;
|
|
1038
|
+
const { primary, port, host, attachSecondary, maxTries } = args;
|
|
1039
|
+
const maxAttempts = Math.max(1, maxTries ?? DEFAULT_MAX_PORT_TRIES);
|
|
1035
1040
|
let sibling;
|
|
1036
1041
|
const ready = new Promise(
|
|
1037
|
-
(resolve3) => {
|
|
1042
|
+
(resolve3, reject) => {
|
|
1038
1043
|
const addresses = [];
|
|
1039
1044
|
const warnings = [];
|
|
1040
1045
|
const primaryHost = host === "localhost" ? "127.0.0.1" : host;
|
|
1041
|
-
|
|
1046
|
+
let candidate = port;
|
|
1047
|
+
let attempt = 0;
|
|
1048
|
+
const bindFailed = (atPort, msg) => reject(
|
|
1049
|
+
new Error(`could not bind ${formatAddress(primaryHost, atPort)}: ${msg}`)
|
|
1050
|
+
);
|
|
1051
|
+
const onError = (e) => {
|
|
1052
|
+
if (e.code !== "EADDRINUSE") return bindFailed(candidate, e.message);
|
|
1053
|
+
if (++attempt >= maxAttempts) {
|
|
1054
|
+
reject(
|
|
1055
|
+
new Error(
|
|
1056
|
+
`could not bind ${formatAddress(primaryHost, port)}: ${maxAttempts} consecutive ports in use`
|
|
1057
|
+
)
|
|
1058
|
+
);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
candidate++;
|
|
1062
|
+
listen();
|
|
1063
|
+
};
|
|
1064
|
+
const onListening = () => {
|
|
1065
|
+
primary.removeListener("error", onError);
|
|
1066
|
+
if (candidate !== port) {
|
|
1067
|
+
warnings.push(`port ${port} was busy; using ${candidate} instead`);
|
|
1068
|
+
}
|
|
1042
1069
|
const addr = primary.address();
|
|
1043
|
-
const resolvedPort = addr && typeof addr === "object" ? addr.port :
|
|
1070
|
+
const resolvedPort = addr && typeof addr === "object" ? addr.port : candidate;
|
|
1044
1071
|
addresses.push(formatAddress(primaryHost, resolvedPort));
|
|
1045
1072
|
if (!LOOPBACK.has(host)) {
|
|
1046
|
-
resolve3({ addresses, warnings });
|
|
1073
|
+
resolve3({ addresses, port: resolvedPort, warnings });
|
|
1047
1074
|
return;
|
|
1048
1075
|
}
|
|
1049
1076
|
const siblingHost = primaryHost === "::1" ? "127.0.0.1" : "::1";
|
|
1050
1077
|
const s = createServer();
|
|
1051
1078
|
attachSecondary(s);
|
|
1052
|
-
const
|
|
1079
|
+
const onSiblingError = (se) => {
|
|
1053
1080
|
s.close();
|
|
1054
1081
|
warnings.push(
|
|
1055
|
-
`could not also bind ${formatAddress(siblingHost, resolvedPort)} (${
|
|
1082
|
+
`could not also bind ${formatAddress(siblingHost, resolvedPort)} (${se.message}); clients using the ${siblingHost === "::1" ? "IPv6" : "IPv4"} form of "localhost" may not connect.`
|
|
1056
1083
|
);
|
|
1057
|
-
resolve3({ addresses, warnings });
|
|
1084
|
+
resolve3({ addresses, port: resolvedPort, warnings });
|
|
1058
1085
|
};
|
|
1059
|
-
s.once("error",
|
|
1086
|
+
s.once("error", onSiblingError);
|
|
1060
1087
|
s.listen(resolvedPort, siblingHost, () => {
|
|
1061
|
-
s.off("error",
|
|
1088
|
+
s.off("error", onSiblingError);
|
|
1062
1089
|
sibling = s;
|
|
1063
1090
|
addresses.push(formatAddress(siblingHost, resolvedPort));
|
|
1064
|
-
resolve3({ addresses, warnings });
|
|
1091
|
+
resolve3({ addresses, port: resolvedPort, warnings });
|
|
1065
1092
|
});
|
|
1066
|
-
}
|
|
1093
|
+
};
|
|
1094
|
+
const listen = () => {
|
|
1095
|
+
try {
|
|
1096
|
+
primary.listen(candidate, primaryHost);
|
|
1097
|
+
} catch (e) {
|
|
1098
|
+
primary.removeListener("error", onError);
|
|
1099
|
+
primary.removeListener("listening", onListening);
|
|
1100
|
+
bindFailed(candidate, e.message);
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
primary.on("error", onError);
|
|
1104
|
+
primary.once("listening", onListening);
|
|
1105
|
+
listen();
|
|
1067
1106
|
}
|
|
1068
1107
|
);
|
|
1069
1108
|
return {
|
|
@@ -1080,10 +1119,14 @@ function printHelp() {
|
|
|
1080
1119
|
process.stdout.write(
|
|
1081
1120
|
`autotel-devtools - Standalone OTLP receiver with web devtools UI
|
|
1082
1121
|
|
|
1083
|
-
Usage: autotel-devtools [options]
|
|
1122
|
+
Usage: autotel-devtools [port] [options]
|
|
1123
|
+
|
|
1124
|
+
Arguments:
|
|
1125
|
+
port Port to listen on (shorthand for --port; must be a positive integer)
|
|
1084
1126
|
|
|
1085
1127
|
Options:
|
|
1086
|
-
-p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT)
|
|
1128
|
+
-p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).
|
|
1129
|
+
If the port is taken, the next free port is used and a warning is shown.
|
|
1087
1130
|
-H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)
|
|
1088
1131
|
-t, --title <title> Dashboard title (env: AUTOTEL_DEVTOOLS_TITLE)
|
|
1089
1132
|
Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT, AUTOTEL_MAX_METRIC_COUNT
|
|
@@ -1103,7 +1146,8 @@ Endpoints:
|
|
|
1103
1146
|
|
|
1104
1147
|
Examples:
|
|
1105
1148
|
npx autotel-devtools
|
|
1106
|
-
npx autotel-devtools
|
|
1149
|
+
npx autotel-devtools 4319
|
|
1150
|
+
npx autotel-devtools -p 4319 -H 0.0.0.0
|
|
1107
1151
|
|
|
1108
1152
|
Then point your app:
|
|
1109
1153
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js
|
|
@@ -1130,10 +1174,12 @@ function printVersion() {
|
|
|
1130
1174
|
}
|
|
1131
1175
|
function parseArgs(argv) {
|
|
1132
1176
|
const options = {
|
|
1133
|
-
port:
|
|
1177
|
+
port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || "4318"),
|
|
1134
1178
|
host: process.env.AUTOTEL_DEVTOOLS_HOST || "127.0.0.1",
|
|
1135
1179
|
title: process.env.AUTOTEL_DEVTOOLS_TITLE
|
|
1136
1180
|
};
|
|
1181
|
+
let portWasExplicit = false;
|
|
1182
|
+
let positionalPortConsumed = false;
|
|
1137
1183
|
for (let i = 0; i < argv.length; i++) {
|
|
1138
1184
|
const arg = argv[i];
|
|
1139
1185
|
const next = argv[i + 1];
|
|
@@ -1146,7 +1192,8 @@ function parseArgs(argv) {
|
|
|
1146
1192
|
return null;
|
|
1147
1193
|
}
|
|
1148
1194
|
if ((arg === "--port" || arg === "-p") && next) {
|
|
1149
|
-
options.port =
|
|
1195
|
+
options.port = parsePort(next);
|
|
1196
|
+
portWasExplicit = true;
|
|
1150
1197
|
i++;
|
|
1151
1198
|
continue;
|
|
1152
1199
|
}
|
|
@@ -1160,9 +1207,23 @@ function parseArgs(argv) {
|
|
|
1160
1207
|
i++;
|
|
1161
1208
|
continue;
|
|
1162
1209
|
}
|
|
1210
|
+
if (/^\d+$/.test(arg) && !positionalPortConsumed) {
|
|
1211
|
+
if (!portWasExplicit) options.port = parsePort(arg);
|
|
1212
|
+
positionalPortConsumed = true;
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1163
1215
|
}
|
|
1164
1216
|
return options;
|
|
1165
1217
|
}
|
|
1218
|
+
function parsePort(value) {
|
|
1219
|
+
const n = Number(value);
|
|
1220
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
1221
|
+
process.stderr.write(`[autotel-devtools] invalid port: ${value}
|
|
1222
|
+
`);
|
|
1223
|
+
process.exit(2);
|
|
1224
|
+
}
|
|
1225
|
+
return n;
|
|
1226
|
+
}
|
|
1166
1227
|
async function main() {
|
|
1167
1228
|
const options = parseArgs(process.argv.slice(2));
|
|
1168
1229
|
if (!options) {
|
|
@@ -1177,8 +1238,8 @@ async function main() {
|
|
|
1177
1238
|
host: options.host,
|
|
1178
1239
|
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
|
|
1179
1240
|
});
|
|
1180
|
-
const { addresses, warnings } = await listeners.ready;
|
|
1181
|
-
const uiBase = `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${
|
|
1241
|
+
const { addresses, warnings, port: boundPort } = await listeners.ready;
|
|
1242
|
+
const uiBase = `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}`;
|
|
1182
1243
|
const title = options.title || "autotel-devtools";
|
|
1183
1244
|
process.stdout.write(`
|
|
1184
1245
|
${title}
|