autotel-terminal 21.0.0 → 22.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 CHANGED
@@ -68,6 +68,32 @@ Port 4319 is used by default to avoid clashing with the standard OTLP port (4318
68
68
  | `POST /v1/metrics` | Metrics | Accepts OTLP JSON metrics (acknowledged and counted) |
69
69
  | `GET /healthz` | — | Health check |
70
70
 
71
+ When bound to a loopback host, the receiver listens on **both** `127.0.0.1`
72
+ and `::1`, so a `localhost` client connects regardless of how the OS resolves
73
+ `localhost` (macOS prefers IPv6 `::1`). The startup line prints every bound
74
+ address; an unbindable family becomes a warning, not a silent failure.
75
+
76
+ ### Behind a dev-server proxy
77
+
78
+ If a dev server proxies `/v1/traces` to the terminal receiver, two bugs make
79
+ spans silently vanish:
80
+
81
+ ```ts
82
+ // Express / http-proxy-middleware
83
+ app.use(
84
+ '/v1/traces',
85
+ createProxyMiddleware({
86
+ pathRewrite: () => '/v1/traces', // Express strips the mount prefix → would forward "/"
87
+ target: 'http://127.0.0.1:4319', // 127.0.0.1, not localhost (macOS resolves localhost → ::1)
88
+ changeOrigin: true,
89
+ }),
90
+ );
91
+ ```
92
+
93
+ The browser shows the request succeeding while the receiver stays empty — so
94
+ verify on the receiver (the TUI should show the spans), not just that the
95
+ request left the browser.
96
+
71
97
  ## Quick Start
72
98
 
73
99
  ### Recommended Usage
package/dist/cli.cjs CHANGED
@@ -2761,6 +2761,54 @@ function renderTerminal(options = {}, stream) {
2761
2761
  console.error("[autotel-terminal] Failed to render dashboard:", error);
2762
2762
  }
2763
2763
  }
2764
+ var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
2765
+ function formatAddress(host, port) {
2766
+ return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
2767
+ }
2768
+ function listenLoopbackDualStack(args) {
2769
+ const { primary, port, host, attachSecondary } = args;
2770
+ let sibling;
2771
+ const ready = new Promise(
2772
+ (resolve) => {
2773
+ const addresses = [];
2774
+ const warnings = [];
2775
+ const primaryHost = host === "localhost" ? "127.0.0.1" : host;
2776
+ primary.listen(port, primaryHost, () => {
2777
+ const addr = primary.address();
2778
+ const resolvedPort = addr && typeof addr === "object" ? addr.port : port;
2779
+ addresses.push(formatAddress(primaryHost, resolvedPort));
2780
+ if (!LOOPBACK.has(host)) {
2781
+ resolve({ addresses, warnings });
2782
+ return;
2783
+ }
2784
+ const siblingHost = primaryHost === "::1" ? "127.0.0.1" : "::1";
2785
+ const s = http.createServer();
2786
+ attachSecondary(s);
2787
+ const onError = (e) => {
2788
+ s.close();
2789
+ warnings.push(
2790
+ `could not also bind ${formatAddress(siblingHost, resolvedPort)} (${e.message}); clients using the ${siblingHost === "::1" ? "IPv6" : "IPv4"} form of "localhost" may not connect.`
2791
+ );
2792
+ resolve({ addresses, warnings });
2793
+ };
2794
+ s.once("error", onError);
2795
+ s.listen(resolvedPort, siblingHost, () => {
2796
+ s.off("error", onError);
2797
+ sibling = s;
2798
+ addresses.push(formatAddress(siblingHost, resolvedPort));
2799
+ resolve({ addresses, warnings });
2800
+ });
2801
+ });
2802
+ }
2803
+ );
2804
+ return {
2805
+ ready,
2806
+ closeSibling: () => new Promise((res) => {
2807
+ if (!sibling) return res();
2808
+ sibling.close(() => res());
2809
+ })
2810
+ };
2811
+ }
2764
2812
 
2765
2813
  // src/cli-stream.ts
2766
2814
  var CliTerminalSpanStream = class {
@@ -3132,7 +3180,7 @@ async function main() {
3132
3180
  },
3133
3181
  spanStream
3134
3182
  );
3135
- const server = http.createServer(async (req, res) => {
3183
+ const requestHandler = async (req, res) => {
3136
3184
  if (req.method === "GET" && req.url === "/healthz") {
3137
3185
  sendJson(res, 200, { ok: true });
3138
3186
  return;
@@ -3170,23 +3218,34 @@ async function main() {
3170
3218
  message: error instanceof Error ? error.message : String(error)
3171
3219
  });
3172
3220
  }
3221
+ };
3222
+ const server = http.createServer(requestHandler);
3223
+ const listeners = listenLoopbackDualStack({
3224
+ primary: server,
3225
+ port: options.port,
3226
+ host: options.host,
3227
+ attachSecondary: (s) => s.on("request", requestHandler)
3173
3228
  });
3174
- server.listen(options.port, options.host, () => {
3175
- process.stdout.write(
3176
- `[autotel-terminal] listening on http://${options.host}:${options.port}
3229
+ const { addresses, warnings } = await listeners.ready;
3230
+ process.stdout.write(
3231
+ `[autotel-terminal] listening on ${addresses.join(" + ")}
3177
3232
  `
3178
- );
3179
- process.stdout.write(
3180
- "[autotel-terminal] endpoints: /v1/traces, /v1/logs, /v1/metrics\n"
3181
- );
3182
- process.stdout.write(
3183
- "[autotel-terminal] set OTEL_EXPORTER_OTLP_PROTOCOL=http/json and OTEL_EXPORTER_OTLP_ENDPOINT to this host/port\n"
3184
- );
3185
- });
3233
+ );
3234
+ process.stdout.write(
3235
+ "[autotel-terminal] endpoints: /v1/traces, /v1/logs, /v1/metrics\n"
3236
+ );
3237
+ process.stdout.write(
3238
+ "[autotel-terminal] set OTEL_EXPORTER_OTLP_PROTOCOL=http/json and OTEL_EXPORTER_OTLP_ENDPOINT to this host/port\n"
3239
+ );
3240
+ for (const w of warnings) {
3241
+ process.stdout.write(`[autotel-terminal] \u26A0 ${w}
3242
+ `);
3243
+ }
3186
3244
  const shutdown = () => {
3187
- server.close(() => {
3188
- process.exit(0);
3189
- });
3245
+ Promise.all([
3246
+ new Promise((r) => server.close(() => r())),
3247
+ listeners.closeSibling()
3248
+ ]).then(() => process.exit(0));
3190
3249
  };
3191
3250
  process.on("SIGINT", shutdown);
3192
3251
  process.on("SIGTERM", shutdown);