@autter/runtime-node 1.9.0 → 1.10.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.
@@ -0,0 +1,878 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ var _chunkS6WJ5YLPcjs = require('./chunk-S6WJ5YLP.cjs');
11
+
12
+ // src/doctor.ts
13
+ var _fs = require('fs');
14
+ var _path = require('path');
15
+
16
+ // src/connectivity.ts
17
+ var _promises = require('dns/promises');
18
+ var _net = require('net');
19
+ var _tls = require('tls');
20
+ var elapsed = (start) => Math.round(performance.now() - start);
21
+ function errCode(err) {
22
+ const e = err;
23
+ return typeof _optionalChain([e, 'optionalAccess', _ => _.code]) === "string" ? e.code : String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _2 => _2.message]), () => ( err)));
24
+ }
25
+ function proxyNote(env) {
26
+ const proxy = env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy;
27
+ return proxy ? ` HTTPS_PROXY is set, but Node's HTTP client (and the OTLP exporter) do not use it: if this network only allows traffic through the proxy, allow the ingester host directly or run the app where it can reach it.` : "";
28
+ }
29
+ function connectOnce(address, port, timeoutMs, tls) {
30
+ return new Promise((resolve) => {
31
+ let socket;
32
+ const timer = setTimeout(() => {
33
+ _optionalChain([socket, 'optionalAccess', _3 => _3.destroy, 'call', _4 => _4()]);
34
+ resolve({ ok: false, code: "TIMEOUT" });
35
+ }, timeoutMs);
36
+ const done = (result) => {
37
+ clearTimeout(timer);
38
+ _optionalChain([socket, 'optionalAccess', _5 => _5.destroy, 'call', _6 => _6()]);
39
+ resolve(result);
40
+ };
41
+ try {
42
+ socket = tls ? _tls.connect.call(void 0, { host: address, port, servername: tls.servername, ALPNProtocols: ["http/1.1"] }, () => done({ ok: true })) : _net.connect.call(void 0, { host: address, port }, () => done({ ok: true }));
43
+ socket.on("error", (err) => done({ ok: false, code: errCode(err) }));
44
+ } catch (err) {
45
+ done({ ok: false, code: errCode(err) });
46
+ }
47
+ });
48
+ }
49
+ var TCP_HINTS = {
50
+ TIMEOUT: "Packets to this address get no answer: an outbound firewall, security group or egress rule is dropping them, or (for an IPv6 address) the network has no IPv6 route.",
51
+ ECONNREFUSED: "The host answered but nothing listens on this port: check the port in the endpoint, or that the ingester is running.",
52
+ ENETUNREACH: "No route to this network from here (often IPv6 on an IPv4-only network).",
53
+ EHOSTUNREACH: "No route to this host from here."
54
+ };
55
+ var TLS_HINTS = {
56
+ SELF_SIGNED_CERT_IN_CHAIN: "A proxy or security product is intercepting TLS with its own certificate. Point NODE_EXTRA_CA_CERTS at its CA certificate.",
57
+ UNABLE_TO_GET_ISSUER_CERT_LOCALLY: "The certificate's issuer is not trusted here (TLS interception or a private CA). Point NODE_EXTRA_CA_CERTS at that CA certificate.",
58
+ DEPTH_ZERO_SELF_SIGNED_CERT: "The ingester uses a self-signed certificate. Point NODE_EXTRA_CA_CERTS at it.",
59
+ ERR_TLS_CERT_ALTNAME_INVALID: "The certificate is not for this host name: check the endpoint URL.",
60
+ CERT_HAS_EXPIRED: "The ingester's certificate has expired.",
61
+ TIMEOUT: "TCP connected but the TLS handshake never finished: a middlebox may be blocking it."
62
+ };
63
+ async function diagnoseConnectivity(endpoint, options = {}) {
64
+ const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 5e3));
65
+ const env = _nullishCoalesce(options.env, () => ( process.env));
66
+ let url;
67
+ try {
68
+ url = new URL(endpoint);
69
+ } catch (e2) {
70
+ return {
71
+ ok: false,
72
+ host: endpoint,
73
+ port: 0,
74
+ steps: [],
75
+ failed: { step: "dns", detail: `not a URL: ${endpoint}`, hint: "Use the ingester's base URL, e.g. https://otlp.autter.dev or http://localhost:4318." }
76
+ };
77
+ }
78
+ const host = url.hostname.replace(/^\[|\]$/g, "");
79
+ const secure = url.protocol === "https:";
80
+ const port = Number(url.port) || (secure ? 443 : 80);
81
+ const steps = [];
82
+ const report = (failed) => ({
83
+ ok: !failed,
84
+ host,
85
+ port,
86
+ steps,
87
+ ...failed ? { failed } : {}
88
+ });
89
+ let addresses;
90
+ if (_net.isIP.call(void 0, host)) {
91
+ addresses = [{ address: host, family: _net.isIP.call(void 0, host) }];
92
+ } else {
93
+ const start2 = performance.now();
94
+ try {
95
+ addresses = await _promises.lookup.call(void 0, host, { all: true });
96
+ steps.push({ step: "dns", ok: true, ms: elapsed(start2), detail: `${host} \u2192 ${addresses.map((a) => a.address).join(", ")}` });
97
+ } catch (err) {
98
+ const code = errCode(err);
99
+ const detail = `${host} does not resolve (${code})`;
100
+ steps.push({ step: "dns", ok: false, ms: elapsed(start2), detail });
101
+ return report({
102
+ step: "dns",
103
+ detail,
104
+ hint: "Check the host name in the endpoint; on a VPN or in a container, check its DNS settings."
105
+ });
106
+ }
107
+ }
108
+ const ordered = [...addresses].sort((a, b) => a.family - b.family).slice(0, 3);
109
+ let reached = null;
110
+ const tcpFailures = [];
111
+ let lastCode = "TIMEOUT";
112
+ for (const { address } of ordered) {
113
+ const start2 = performance.now();
114
+ const result = await connectOnce(address, port, timeoutMs, null);
115
+ if (result.ok) {
116
+ steps.push({ step: "tcp", ok: true, ms: elapsed(start2), detail: `connected to ${address}:${port}` });
117
+ reached = address;
118
+ break;
119
+ }
120
+ lastCode = result.code;
121
+ tcpFailures.push(`${address}:${port} ${result.code === "TIMEOUT" ? `timed out after ${timeoutMs} ms` : result.code}`);
122
+ }
123
+ if (tcpFailures.length && reached) {
124
+ steps[steps.length - 1].detail += ` (failed first: ${tcpFailures.join("; ")})`;
125
+ }
126
+ if (!reached) {
127
+ const detail = tcpFailures.join("; ");
128
+ steps.push({ step: "tcp", ok: false, ms: 0, detail });
129
+ return report({ step: "tcp", detail, hint: (_nullishCoalesce(TCP_HINTS[lastCode], () => ( "The connection failed."))) + proxyNote(env) });
130
+ }
131
+ if (secure) {
132
+ const start2 = performance.now();
133
+ const result = await connectOnce(reached, port, timeoutMs, { servername: host });
134
+ if (!result.ok) {
135
+ const detail = `TLS handshake with ${host} failed (${result.code})`;
136
+ steps.push({ step: "tls", ok: false, ms: elapsed(start2), detail });
137
+ return report({ step: "tls", detail, hint: _nullishCoalesce(TLS_HINTS[result.code], () => ( "The TLS handshake failed.")) });
138
+ }
139
+ steps.push({ step: "tls", ok: true, ms: elapsed(start2), detail: `certificate valid for ${host}` });
140
+ }
141
+ const start = performance.now();
142
+ const base = endpoint.replace(/\/$/, "");
143
+ const res = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "GET", `${base}/livez`, { unref: false, timeoutMs });
144
+ if (!res) {
145
+ const detail = `no HTTP answer from ${base}/livez within ${timeoutMs} ms`;
146
+ steps.push({ step: "http", ok: false, ms: elapsed(start), detail });
147
+ return report({
148
+ step: "http",
149
+ detail,
150
+ hint: "The connection opens but no HTTP response comes back: a proxy or load balancer in front of the ingester may be holding the request." + proxyNote(env)
151
+ });
152
+ }
153
+ steps.push({ step: "http", ok: true, ms: elapsed(start), detail: `GET /livez answered ${res.status}` });
154
+ return report();
155
+ }
156
+
157
+ // src/doctor.ts
158
+ var SDK_PACKAGES = ["@autter/runtime-node", "@autter/runtime-next", "@autter/runtime-browser"];
159
+ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
160
+ function resolveEndpoint(flag, env = process.env) {
161
+ return (flag || env.AUTTER_ENDPOINT || env.AUTTER_RUNTIME_ENDPOINT || env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_ENDPOINT).replace(/\/$/, "");
162
+ }
163
+ function installedSdks(cwd) {
164
+ const found = /* @__PURE__ */ new Map();
165
+ for (let dir = cwd; ; dir = _path.dirname.call(void 0, dir)) {
166
+ for (const name of SDK_PACKAGES) {
167
+ if (found.has(name)) continue;
168
+ const file = _path.join.call(void 0, dir, "node_modules", ...name.split("/"), "package.json");
169
+ if (!_fs.existsSync.call(void 0, file)) continue;
170
+ try {
171
+ const version = JSON.parse(_fs.readFileSync.call(void 0, file, "utf8")).version;
172
+ if (typeof version === "string") found.set(name, version);
173
+ } catch (e3) {
174
+ }
175
+ }
176
+ if (_path.dirname.call(void 0, dir) === dir) break;
177
+ }
178
+ if (!found.has(_chunkS6WJ5YLPcjs.SDK_IDENTITY.name)) found.set(_chunkS6WJ5YLPcjs.SDK_IDENTITY.name, _chunkS6WJ5YLPcjs.SDK_IDENTITY.version);
179
+ return [...found].map(([name, version]) => ({ name, version }));
180
+ }
181
+ function featuresInUse(sdks) {
182
+ return _chunkS6WJ5YLPcjs.COMPAT_MANIFEST.features.filter(
183
+ (feature) => sdks.some((sdk) => {
184
+ const min = feature.sdks[sdk.name];
185
+ return min !== void 0 && _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
186
+ })
187
+ ).map((feature) => feature.id);
188
+ }
189
+ async function checkKey(endpoint, key, timeoutMs) {
190
+ const res = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
191
+ body: JSON.stringify({ resourceSpans: [] }),
192
+ headers: { authorization: `Bearer ${key}` },
193
+ unref: false,
194
+ timeoutMs
195
+ });
196
+ if (!res) return { key: "unknown" };
197
+ const detail = _chunkS6WJ5YLPcjs.refusalReason.call(void 0, res.body) || void 0;
198
+ const withDetail = (key2) => detail ? { key: key2, detail } : { key: key2 };
199
+ if (res.status >= 200 && res.status < 300) return { key: "accepted" };
200
+ if (res.status === 401) return withDetail("rejected");
201
+ if (res.status === 403) return withDetail("client_key");
202
+ if (res.status === 503) return withDetail(res.headers["retry-after"] ? "unverified" : "storage_unavailable");
203
+ return withDetail("unknown");
204
+ }
205
+ var STORAGE_TEXT = {
206
+ up: "ready: ClickHouse accepts writes",
207
+ down: "ClickHouse is unreachable or failing writes (retryable): ingest answers 503 and exporters retry",
208
+ auth_failed: "ClickHouse refuses the ingester's credentials: fix CLICKHOUSE_USER/CLICKHOUSE_PASSWORD on the ingester",
209
+ schema_missing: "ClickHouse tables are missing: check the ingester logs for the boot migrations, then restart it",
210
+ unconfigured: "no ClickHouse configured (CLICKHOUSE_URL unset): every ingest request is refused with 503"
211
+ };
212
+ async function checkStorage(endpoint, timeoutMs) {
213
+ const res = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "GET", `${endpoint}/healthz`, { unref: false, timeoutMs });
214
+ if (!res) return null;
215
+ let body = {};
216
+ try {
217
+ body = JSON.parse(res.body);
218
+ } catch (e4) {
219
+ }
220
+ const reported = typeof body.clickhouse === "string" ? body.clickhouse : null;
221
+ if (res.status === 404) {
222
+ return { state: "unknown", ok: true, writeChecked: false, detail: "not checked: this ingester has no /healthz" };
223
+ }
224
+ if (!reported) {
225
+ if (res.status >= 200 && res.status < 300) {
226
+ return {
227
+ state: "up",
228
+ ok: true,
229
+ writeChecked: false,
230
+ detail: "reachable (this ingester predates 1.12 and does not check writes; `verify` does)"
231
+ };
232
+ }
233
+ return res.status === 503 ? { state: "down", ok: false, writeChecked: false, detail: "/healthz answered 503: the ingester is not ready" } : { state: "unknown", ok: true, writeChecked: false, detail: `not checked: /healthz answered ${res.status}` };
234
+ }
235
+ const ok = res.status >= 200 && res.status < 300 && reported === "up";
236
+ return { state: reported, ok, writeChecked: true, detail: _nullishCoalesce(STORAGE_TEXT[reported], () => ( `ClickHouse ${reported}`)) };
237
+ }
238
+ function versionSkew(sdks, info) {
239
+ return sdks.map((sdk) => {
240
+ const emitted = _chunkS6WJ5YLPcjs.COMPAT_MANIFEST.features.filter((f) => {
241
+ const min = f.sdks[sdk.name];
242
+ return min !== void 0 && _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
243
+ });
244
+ const notYet = _chunkS6WJ5YLPcjs.COMPAT_MANIFEST.features.filter((f) => {
245
+ const min = f.sdks[sdk.name];
246
+ return min !== void 0 && _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, sdk.version, min) === false;
247
+ });
248
+ if (!_optionalChain([info, 'optionalAccess', _7 => _7.version])) {
249
+ return { sdk: sdk.name, version: sdk.version, status: "unknown", detail: "ingester version unknown" };
250
+ }
251
+ const unstored = emitted.filter((f) => _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, info.version, f.ingester) === false);
252
+ if (unstored.length) {
253
+ const need = unstored.map((f) => f.ingester).sort((a, b) => _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, a, b) ? -1 : 1)[0];
254
+ return {
255
+ sdk: sdk.name,
256
+ version: sdk.version,
257
+ status: "ingester_behind",
258
+ detail: `ingester ${info.version} is behind: ${unstored.length} feature(s) this SDK sends need ingester >= ${need} (${unstored.map((f) => f.id).join(", ")})`
259
+ };
260
+ }
261
+ const available = notYet.filter((f) => _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, info.version, f.ingester) === true);
262
+ if (available.length) {
263
+ const need = available.map((f) => f.sdks[sdk.name]).sort((a, b) => _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, a, b) ? -1 : 1)[0];
264
+ return {
265
+ sdk: sdk.name,
266
+ version: sdk.version,
267
+ status: "sdk_behind",
268
+ detail: `the ingester stores ${available.length} feature(s) this SDK does not send yet; ${sdk.name} >= ${need} adds them (${available.map((f) => f.id).join(", ")})`
269
+ };
270
+ }
271
+ return { sdk: sdk.name, version: sdk.version, status: "in_sync", detail: `in sync with ingester ${info.version}` };
272
+ });
273
+ }
274
+ async function runDoctor(options = {}) {
275
+ const endpoint = resolveEndpoint(options.endpoint);
276
+ const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 5e3));
277
+ const sdks = installedSdks(_nullishCoalesce(options.cwd, () => ( process.cwd())));
278
+ const features = _optionalChain([options, 'access', _8 => _8.features, 'optionalAccess', _9 => _9.length]) ? options.features : featuresInUse(sdks);
279
+ const routes = [
280
+ ...new Set(
281
+ features.map((id) => _optionalChain([_chunkS6WJ5YLPcjs.COMPAT_MANIFEST, 'access', _10 => _10.features, 'access', _11 => _11.find, 'call', _12 => _12((f) => f.id === id), 'optionalAccess', _13 => _13.route])).filter((route) => Boolean(route))
282
+ )
283
+ ];
284
+ const { info, error } = await _chunkS6WJ5YLPcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
285
+ const issues = [];
286
+ const seen = /* @__PURE__ */ new Set();
287
+ const add = (issue) => {
288
+ const key2 = `${issue.feature}:${issue.kind}:${issue.component}`;
289
+ if (!seen.has(key2)) {
290
+ seen.add(key2);
291
+ issues.push(issue);
292
+ }
293
+ };
294
+ _chunkS6WJ5YLPcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
295
+ for (const sdk of sdks) {
296
+ if (_optionalChain([options, 'access', _14 => _14.features, 'optionalAccess', _15 => _15.length])) {
297
+ _chunkS6WJ5YLPcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
298
+ }
299
+ }
300
+ const keyCheck = options.key ? await checkKey(endpoint, options.key, timeoutMs) : null;
301
+ const key = _nullishCoalesce(_optionalChain([keyCheck, 'optionalAccess', _16 => _16.key]), () => ( "not_checked"));
302
+ const storage = info ? await checkStorage(endpoint, timeoutMs) : null;
303
+ const connectivity = info ? void 0 : await diagnoseConnectivity(endpoint, { timeoutMs });
304
+ const featureRows = features.flatMap((id) => {
305
+ const feature = _chunkS6WJ5YLPcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
306
+ if (!feature) return [];
307
+ const bad = issues.some((issue) => issue.feature === id);
308
+ const known = _optionalChain([info, 'optionalAccess', _17 => _17.version]) != null || feature.route !== void 0 && _optionalChain([info, 'optionalAccess', _18 => _18.routes, 'optionalAccess', _19 => _19[feature.route]]) !== void 0;
309
+ return [{ id, title: feature.title, status: bad ? "incompatible" : known ? "ok" : "unknown" }];
310
+ });
311
+ const exitCode = !info ? 2 : issues.length || _optionalChain([storage, 'optionalAccess', _20 => _20.ok]) === false || key === "rejected" || key === "client_key" || key === "storage_unavailable" || key === "unverified" ? 1 : 0;
312
+ return {
313
+ endpoint,
314
+ sdks,
315
+ ingester: info,
316
+ ...error && !info ? { ingesterError: error } : {},
317
+ key,
318
+ ..._optionalChain([keyCheck, 'optionalAccess', _21 => _21.detail]) ? { keyDetail: keyCheck.detail } : {},
319
+ storage,
320
+ features: featureRows,
321
+ skew: versionSkew(sdks, info),
322
+ issues,
323
+ ...connectivity ? { connectivity } : {},
324
+ exitCode
325
+ };
326
+ }
327
+ function formatDoctorReport(report) {
328
+ const lines = [];
329
+ lines.push(`Autter Runtime doctor`);
330
+ lines.push(` endpoint ${report.endpoint}`);
331
+ for (const sdk of report.sdks) lines.push(` sdk ${sdk.name}@${sdk.version}`);
332
+ if (!report.ingester) {
333
+ lines.push(` ingester unreachable or unidentified (${_nullishCoalesce(report.ingesterError, () => ( "unknown error"))})`);
334
+ } else if (report.ingester.version) {
335
+ lines.push(` ingester @autter/otlp-ingester@${report.ingester.version}`);
336
+ const schema = report.ingester.schema;
337
+ if (schema) {
338
+ lines.push(
339
+ ` schema ${schema.status}${schema.applied.length ? ` (level ${schema.applied[schema.applied.length - 1]})` : ""}`
340
+ );
341
+ }
342
+ } else {
343
+ lines.push(` ingester 1.4.0 or older (no /v1/compat endpoint; upgrade for a full report)`);
344
+ }
345
+ if (report.storage) lines.push(` storage ${report.storage.ok ? "" : "NOT READY: "}${report.storage.detail}`);
346
+ {
347
+ const reason = report.keyDetail ? ` (${report.keyDetail})` : "";
348
+ const text = {
349
+ accepted: "accepted",
350
+ rejected: `REJECTED (401${reason}): check AUTTER_RUNTIME_KEY`,
351
+ client_key: `refused (403${reason}): server SDKs need a server key (autter_rt_\u2026)`,
352
+ storage_unavailable: "accepted, but the ingester's ClickHouse is unavailable (503)",
353
+ unverified: "could not be checked: the ingester's key validator is unavailable (503); exporters retry",
354
+ unknown: `could not be verified${reason}`,
355
+ not_checked: "not checked (pass --key or set AUTTER_RUNTIME_KEY)"
356
+ }[report.key];
357
+ lines.push(` key ${text}`);
358
+ }
359
+ if (report.connectivity) {
360
+ lines.push("", "Connection");
361
+ for (const step of report.connectivity.steps) {
362
+ lines.push(` ${step.ok ? "ok " : "FAIL"} ${step.step.padEnd(5)} ${step.detail}${step.ms ? ` (${step.ms} ms)` : ""}`);
363
+ }
364
+ if (report.connectivity.failed) lines.push(` -> ${report.connectivity.failed.hint}`);
365
+ }
366
+ if (report.skew.some((line) => line.status !== "unknown")) {
367
+ lines.push("", "Versions");
368
+ for (const line of report.skew) {
369
+ const mark = line.status === "in_sync" ? "ok " : line.status === "ingester_behind" ? "FAIL" : line.status === "sdk_behind" ? "note" : "?? ";
370
+ lines.push(` ${mark} ${line.sdk}@${line.version}: ${line.detail}`);
371
+ }
372
+ }
373
+ if (report.features.length) {
374
+ lines.push("", "Features");
375
+ for (const feature of report.features) {
376
+ const mark = feature.status === "ok" ? "on " : feature.status === "incompatible" ? "FAIL" : "?? ";
377
+ lines.push(` ${mark} ${feature.title} (${feature.id})`);
378
+ }
379
+ }
380
+ if (report.issues.length) {
381
+ lines.push("", "Problems");
382
+ for (const issue of report.issues) lines.push(` - ${issue.message}`);
383
+ }
384
+ if (report.storage && !report.storage.ok) lines.push(`${report.issues.length ? "" : "\nProblems\n"} - Storage: ${report.storage.detail}`);
385
+ lines.push("", verdict(report));
386
+ return lines.join("\n");
387
+ }
388
+ function verdict(report) {
389
+ if (report.exitCode === 2) {
390
+ return _optionalChain([report, 'access', _22 => _22.connectivity, 'optionalAccess', _23 => _23.failed]) ? `Could not reach the ingester: the ${report.connectivity.failed.step.toUpperCase()} step failed (see Connection above).` : "Could not check the ingester. Pass --endpoint <url> (or set AUTTER_ENDPOINT).";
391
+ }
392
+ if (report.exitCode === 1) {
393
+ if (report.storage && !report.storage.ok) return "Not ready: the ingester cannot store data right now (see storage above).";
394
+ if (report.key !== "accepted" && report.key !== "not_checked" && !report.issues.length) return "Not ready: the key was not accepted (see key above).";
395
+ return "Incompatible: see the problems above.";
396
+ }
397
+ if (report.key === "not_checked") {
398
+ return "Versions compatible and storage ready. Key not checked: pass --key, or run `npx @autter/runtime-node verify` to send a test event end to end.";
399
+ }
400
+ return "Ready: versions compatible, storage ready, key accepted. `npx @autter/runtime-node verify` sends a test event end to end.";
401
+ }
402
+ var DOCTOR_USAGE = `Usage: npx @autter/runtime-node doctor [options]
403
+
404
+ Checks that your Autter SDKs and the ingester's version, ClickHouse schema
405
+ and storage match, which features are active, and the ingest key. For an
406
+ end-to-end check that sends a test event, use: npx @autter/runtime-node verify
407
+
408
+ Options:
409
+ --endpoint <url> Ingester base URL (default: $AUTTER_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT, or ${DEFAULT_ENDPOINT})
410
+ --key <key> Also verify an ingest key (default: $AUTTER_RUNTIME_KEY)
411
+ --features <a,b> Only check these features (${_chunkS6WJ5YLPcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
412
+ --json Machine-readable output
413
+ --timeout <ms> Per-request timeout (default 5000)
414
+
415
+ Exit codes: 0 ready, 1 mismatch, rejected key or storage not ready, 2 ingester unreachable.`;
416
+ function parseDoctorArgs(argv, env = process.env) {
417
+ const options = {};
418
+ for (let i = 0; i < argv.length; i++) {
419
+ const arg = argv[i];
420
+ const [flag, inline] = arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, void 0];
421
+ const value = () => _nullishCoalesce(inline, () => ( argv[++i]));
422
+ switch (flag) {
423
+ case "-h":
424
+ case "--help":
425
+ return { help: true };
426
+ case "--endpoint":
427
+ options.endpoint = value();
428
+ break;
429
+ case "--key":
430
+ options.key = value();
431
+ break;
432
+ case "--features":
433
+ options.features = (_nullishCoalesce(value(), () => ( ""))).split(",").map((f) => f.trim()).filter(Boolean);
434
+ break;
435
+ case "--json":
436
+ options.json = true;
437
+ break;
438
+ case "--timeout": {
439
+ const ms = Number(value());
440
+ if (!Number.isFinite(ms) || ms <= 0) return { error: "--timeout needs a positive number of ms" };
441
+ options.timeoutMs = ms;
442
+ break;
443
+ }
444
+ default:
445
+ return { error: `unknown option ${arg}` };
446
+ }
447
+ }
448
+ if (!options.key && env.AUTTER_RUNTIME_KEY) options.key = env.AUTTER_RUNTIME_KEY;
449
+ return options;
450
+ }
451
+
452
+ // src/verify.ts
453
+ var _crypto = require('crypto');
454
+
455
+
456
+ var ORDER = ["key", "network", "ingester", "storage", "auth", "ingest", "stored", "trace", "release", "logs"];
457
+ function defaultService(cwd, env) {
458
+ if (env.AUTTER_SERVICE) return env.AUTTER_SERVICE;
459
+ if (env.OTEL_SERVICE_NAME) return env.OTEL_SERVICE_NAME;
460
+ try {
461
+ const name = JSON.parse(_fs.readFileSync.call(void 0, _path.join.call(void 0, cwd, "package.json"), "utf8")).name;
462
+ if (typeof name === "string" && name) return name.replace(/^@[^/]+\//, "");
463
+ } catch (e5) {
464
+ }
465
+ return "autter-verify";
466
+ }
467
+ function defaultRelease(env) {
468
+ return env.AUTTER_RELEASE || env.GIT_SHA || env.VERCEL_GIT_COMMIT_SHA || env.RENDER_GIT_COMMIT || env.RAILWAY_GIT_COMMIT_SHA || env.CF_PAGES_COMMIT_SHA || env.GITHUB_SHA || null;
469
+ }
470
+ var nowNanos = () => (BigInt(Date.now()) * 1000000n).toString();
471
+ var str = (key, value) => ({ key, value: { stringValue: value } });
472
+ function resourceAttributes(opts) {
473
+ return [
474
+ str("service.name", opts.service),
475
+ str("deployment.environment", opts.environment),
476
+ ...opts.release ? [str("service.version", opts.release)] : [],
477
+ str("autter.verify.id", opts.traceId)
478
+ ];
479
+ }
480
+ function testTraces(opts) {
481
+ const end = nowNanos();
482
+ const start = (BigInt(end) - 1000000n).toString();
483
+ return {
484
+ resourceSpans: [
485
+ {
486
+ resource: { attributes: resourceAttributes(opts) },
487
+ scopeSpans: [
488
+ {
489
+ scope: { name: "autter-verify", version: _chunkS6WJ5YLPcjs.SDK_IDENTITY.version },
490
+ spans: [
491
+ {
492
+ traceId: opts.traceId,
493
+ spanId: _crypto.randomBytes.call(void 0, 8).toString("hex"),
494
+ name: "autter verify",
495
+ kind: 1,
496
+ startTimeUnixNano: start,
497
+ endTimeUnixNano: end,
498
+ attributes: [str("autter.verify", "true")],
499
+ ...opts.withError ? {
500
+ status: { code: 2, message: "autter verify test event" },
501
+ events: [
502
+ {
503
+ name: "exception",
504
+ timeUnixNano: end,
505
+ attributes: [
506
+ str("exception.type", "AutterVerifyTestEvent"),
507
+ str("exception.message", "Test event from autter-runtime verify (never opens an issue)"),
508
+ str("autter.severity", "info")
509
+ ]
510
+ }
511
+ ]
512
+ } : {}
513
+ }
514
+ ]
515
+ }
516
+ ]
517
+ }
518
+ ]
519
+ };
520
+ }
521
+ function testLogs(opts) {
522
+ return {
523
+ resourceLogs: [
524
+ {
525
+ resource: { attributes: resourceAttributes(opts) },
526
+ scopeLogs: [
527
+ {
528
+ scope: { name: "autter-verify" },
529
+ logRecords: [
530
+ {
531
+ timeUnixNano: nowNanos(),
532
+ severityNumber: 9,
533
+ severityText: "INFO",
534
+ body: { stringValue: "autter verify test log record" },
535
+ traceId: opts.traceId,
536
+ attributes: [str("autter.verify", "true")]
537
+ }
538
+ ]
539
+ }
540
+ ]
541
+ }
542
+ ]
543
+ };
544
+ }
545
+ function parse(res) {
546
+ if (!res) return null;
547
+ try {
548
+ return JSON.parse(res.body);
549
+ } catch (e6) {
550
+ return null;
551
+ }
552
+ }
553
+ var AUTH_FIX = {
554
+ 401: "Check AUTTER_RUNTIME_KEY: it must be a server key (autter_rt_\u2026) of this repository, not revoked. Create one in Autter \u2192 Repository \u2192 Settings \u2192 Runtime.",
555
+ 403: "Server SDKs need a server key (autter_rt_\u2026). Client keys (autter_rtc_\u2026) are for browsers only, upload keys (autter_rtu_\u2026) for source maps only.",
556
+ 429: "The ingester is rate limiting this key or address; wait for Retry-After and run verify again.",
557
+ 503: "The ingester could not check the key or store data right now. Run again in a few seconds; if it persists, see storage above."
558
+ };
559
+ async function runVerify(options = {}, env = process.env) {
560
+ const endpoint = resolveEndpoint(options.endpoint, env);
561
+ const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 5e3));
562
+ const waitMs = _nullishCoalesce(options.waitMs, () => ( 2e4));
563
+ const pollMs = _nullishCoalesce(options.pollMs, () => ( 500));
564
+ const cwd = _nullishCoalesce(options.cwd, () => ( process.cwd()));
565
+ const service = options.service || defaultService(cwd, env);
566
+ const environment = options.environment || "verify";
567
+ const release = options.release || defaultRelease(env);
568
+ const traceId = _crypto.randomBytes.call(void 0, 16).toString("hex");
569
+ const key = _chunkS6WJ5YLPcjs.resolveIngestKey.call(void 0, options.key, env);
570
+ const steps = [];
571
+ const add = (step) => steps.push(step);
572
+ const finish = (exitCode) => {
573
+ const firstFailure = _nullishCoalesce(steps.find((s) => s.status === "fail"), () => ( null));
574
+ for (const name of ORDER) {
575
+ if (!steps.some((s) => s.step === name)) steps.push({ step: name, status: "skip", detail: "not run: an earlier step failed" });
576
+ }
577
+ steps.sort((a, b) => ORDER.indexOf(a.step) - ORDER.indexOf(b.step));
578
+ return {
579
+ endpoint,
580
+ service,
581
+ environment,
582
+ release,
583
+ traceId,
584
+ steps,
585
+ firstFailure,
586
+ exitCode: _nullishCoalesce(exitCode, () => ( (firstFailure ? 1 : 0)))
587
+ };
588
+ };
589
+ const headers = key ? { authorization: `Bearer ${key}` } : {};
590
+ if (!key) {
591
+ add({
592
+ step: "key",
593
+ status: "fail",
594
+ detail: "no ingest key: --key not given and AUTTER_RUNTIME_KEY is not set",
595
+ fix: "Set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026), or pass --key. Locally with the repo's docker compose: autter_rt_local_dev_only."
596
+ });
597
+ return finish();
598
+ }
599
+ const kind = key.startsWith("autter_rtc_") ? "client key" : key.startsWith("autter_rtu_") ? "upload key" : "key";
600
+ if (kind !== "key") {
601
+ add({ step: "key", status: "fail", detail: `the key is a ${kind}`, fix: AUTH_FIX[403] });
602
+ return finish();
603
+ }
604
+ add({ step: "key", status: "pass", detail: `${key.slice(0, Math.min(10, key.length))}\u2026 (${key.length} characters)` });
605
+ const network = await diagnoseConnectivity(endpoint, { timeoutMs, env });
606
+ if (!network.ok) {
607
+ add({ step: "network", status: "fail", detail: `${network.failed.step.toUpperCase()}: ${network.failed.detail}`, fix: network.failed.hint });
608
+ return finish(2);
609
+ }
610
+ const total = network.steps.reduce((sum, s) => sum + s.ms, 0);
611
+ add({ step: "network", status: "pass", detail: `${network.steps.map((s) => s.step.toUpperCase()).join(", ")} ok (${network.host}:${network.port}, ${total} ms)` });
612
+ const { info, error } = await _chunkS6WJ5YLPcjs.fetchIngesterCompat.call(void 0, endpoint, { unref: false, timeoutMs });
613
+ if (!info) {
614
+ add({
615
+ step: "ingester",
616
+ status: "fail",
617
+ detail: `the server answered but is not an Autter ingester (${_nullishCoalesce(error, () => ( "unknown"))})`,
618
+ fix: "Point --endpoint (AUTTER_ENDPOINT) at the ingester's base URL, without /v1/traces."
619
+ });
620
+ return finish();
621
+ }
622
+ const skew = versionSkew(installedSdks(cwd), info).filter((line) => line.status === "ingester_behind");
623
+ add({
624
+ step: "ingester",
625
+ status: skew.length ? "warn" : "pass",
626
+ detail: `@autter/otlp-ingester@${_nullishCoalesce(info.version, () => ( "1.4.0 or older"))}${info.schema ? `, schema ${info.schema.status}` : ""}${skew.length ? `; ${skew.map((l) => l.detail).join("; ")}` : ""}`
627
+ });
628
+ const storage = await checkStorage(endpoint, timeoutMs);
629
+ if (storage && !storage.ok) {
630
+ add({ step: "storage", status: "fail", detail: `ClickHouse ${storage.state}`, fix: storage.detail });
631
+ return finish();
632
+ }
633
+ add({ step: "storage", status: _optionalChain([storage, 'optionalAccess', _24 => _24.writeChecked]) ? "pass" : "warn", detail: _nullishCoalesce(_optionalChain([storage, 'optionalAccess', _25 => _25.detail]), () => ( "not checked: /healthz did not answer")) });
634
+ const authRes = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "GET", `${endpoint}/v1/verify`, { headers, unref: false, timeoutMs });
635
+ let lookupSupported = true;
636
+ if (_optionalChain([authRes, 'optionalAccess', _26 => _26.status]) === 404) {
637
+ lookupSupported = false;
638
+ const probe = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
639
+ body: JSON.stringify({ resourceSpans: [] }),
640
+ headers,
641
+ unref: false,
642
+ timeoutMs
643
+ });
644
+ if (!probe || probe.status >= 300) {
645
+ const status = _nullishCoalesce(_optionalChain([probe, 'optionalAccess', _27 => _27.status]), () => ( 0));
646
+ add({ step: "auth", status: "fail", detail: probe ? `${status}: ${_chunkS6WJ5YLPcjs.refusalReason.call(void 0, probe.body) || "refused"}` : "no answer", fix: AUTH_FIX[status] });
647
+ return finish();
648
+ }
649
+ add({ step: "auth", status: "pass", detail: "key accepted" });
650
+ } else if (!authRes || authRes.status !== 200) {
651
+ const status = _nullishCoalesce(_optionalChain([authRes, 'optionalAccess', _28 => _28.status]), () => ( 0));
652
+ add({ step: "auth", status: "fail", detail: authRes ? `${status}: ${_chunkS6WJ5YLPcjs.refusalReason.call(void 0, authRes.body) || "refused"}` : "no answer", fix: AUTH_FIX[status] });
653
+ return finish();
654
+ } else {
655
+ const body = parse(authRes);
656
+ const lastRejected = _optionalChain([body, 'optionalAccess', _29 => _29.delivery, 'optionalAccess', _30 => _30.lastRejected]);
657
+ add({
658
+ step: "auth",
659
+ status: "pass",
660
+ detail: "key accepted" + (lastRejected ? `; this key's last refused request: ${lastRejected.route} ${lastRejected.status} ${_nullishCoalesce(lastRejected.reason, () => ( ""))}${lastRejected.error ? ` (${lastRejected.error})` : ""} at ${lastRejected.at}` : "")
661
+ });
662
+ }
663
+ const ingestRes = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
664
+ body: JSON.stringify(testTraces({ service, environment, release, traceId, withError: lookupSupported })),
665
+ headers,
666
+ unref: false,
667
+ timeoutMs
668
+ });
669
+ if (!ingestRes || ingestRes.status >= 300) {
670
+ const status = _nullishCoalesce(_optionalChain([ingestRes, 'optionalAccess', _31 => _31.status]), () => ( 0));
671
+ add({
672
+ step: "ingest",
673
+ status: "fail",
674
+ detail: ingestRes ? `/v1/traces ${status}: ${_chunkS6WJ5YLPcjs.refusalReason.call(void 0, ingestRes.body) || "refused"}` : "no answer from /v1/traces",
675
+ fix: _nullishCoalesce(AUTH_FIX[status], () => ( "Run with --json and check the ingester's logs."))
676
+ });
677
+ return finish();
678
+ }
679
+ const partial = _optionalChain([parse, 'call', _32 => _32(ingestRes), 'optionalAccess', _33 => _33.partialSuccess]);
680
+ const rejected = Number(_nullishCoalesce(_optionalChain([partial, 'optionalAccess', _34 => _34.rejectedSpans]), () => ( 0)));
681
+ const summary = ingestRes.headers["x-autter-ingest"];
682
+ if (rejected > 0) {
683
+ add({ step: "ingest", status: "fail", detail: `/v1/traces accepted the request but rejected ${rejected} span(s): ${_nullishCoalesce(_optionalChain([partial, 'optionalAccess', _35 => _35.errorMessage]), () => ( ""))}` });
684
+ return finish();
685
+ }
686
+ add({ step: "ingest", status: "pass", detail: `/v1/traces ${ingestRes.status}${summary ? `: ${summary}` : ""}` });
687
+ if (!lookupSupported) {
688
+ add({
689
+ step: "stored",
690
+ status: "warn",
691
+ detail: `not checked: ingester ${_nullishCoalesce(info.version, () => ( "1.4.0 or older"))} has no /v1/verify (upgrade it to have verify read the event back)`
692
+ });
693
+ add({ step: "trace", status: "skip", detail: "not checked (needs /v1/verify)" });
694
+ add({ step: "release", status: release ? "skip" : "warn", detail: release ? "not checked (needs /v1/verify)" : "no release set (see below)" });
695
+ add({ step: "logs", status: "skip", detail: "not checked (needs /v1/verify)" });
696
+ return finish();
697
+ }
698
+ const started = Date.now();
699
+ let lookup2 = null;
700
+ let lookupStatus = 0;
701
+ for (; ; ) {
702
+ const res = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "GET", `${endpoint}/v1/verify/${traceId}`, { headers, unref: false, timeoutMs });
703
+ lookupStatus = _nullishCoalesce(_optionalChain([res, 'optionalAccess', _36 => _36.status]), () => ( 0));
704
+ const body = _optionalChain([res, 'optionalAccess', _37 => _37.status]) === 200 ? parse(res) : null;
705
+ if (_optionalChain([body, 'optionalAccess', _38 => _38.stored])) {
706
+ lookup2 = body;
707
+ if (body.stored.spans.rows > 0 && body.stored.occurrences.rows > 0) break;
708
+ }
709
+ if (Date.now() - started >= waitMs) break;
710
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
711
+ }
712
+ const seconds = ((Date.now() - started) / 1e3).toFixed(1);
713
+ const stored = _optionalChain([lookup2, 'optionalAccess', _39 => _39.stored]);
714
+ if (!stored || stored.spans.rows === 0 || stored.occurrences.rows === 0) {
715
+ add({
716
+ step: "stored",
717
+ status: "fail",
718
+ detail: !stored ? `could not read it back (GET /v1/verify answered ${lookupStatus || "nothing"})` : `accepted but not found after ${seconds} s: ${stored.spans.rows} span(s), ${stored.occurrences.rows} error(s) stored`,
719
+ fix: "The ingester answered 200 but the rows are not in ClickHouse. Check the ingester's logs for write errors and its /healthz `writes` section."
720
+ });
721
+ return finish();
722
+ }
723
+ add({ step: "stored", status: "pass", detail: `${stored.spans.rows} span and ${stored.occurrences.rows} error stored after ${seconds} s` });
724
+ add({ step: "trace", status: "pass", detail: `the error is linked to its span by trace id ${traceId}` });
725
+ if (!release) {
726
+ add({
727
+ step: "release",
728
+ status: "warn",
729
+ detail: `no release set: errors are not tied to a deploy, so "broke in release X" can't work`,
730
+ fix: "Pass `release` to initAutterServer (and initAutterBrowser), e.g. process.env.GIT_SHA; run verify with --release or GIT_SHA set."
731
+ });
732
+ } else if (stored.occurrences.releases.includes(release) && stored.spans.releases.includes(release)) {
733
+ add({ step: "release", status: "pass", detail: `release ${release} stored on the error and the span` });
734
+ } else {
735
+ add({
736
+ step: "release",
737
+ status: "fail",
738
+ detail: `sent release ${release}, stored ${JSON.stringify(stored.occurrences.releases)}`,
739
+ fix: "The ingester did not keep service.version: check for a collector or proxy that rewrites resource attributes."
740
+ });
741
+ }
742
+ const logsSupported = info.version === null ? false : _chunkS6WJ5YLPcjs.versionAtLeast.call(void 0, info.version, "1.4.0") === true;
743
+ if (!logsSupported || stored.logs === null) {
744
+ add({ step: "logs", status: "skip", detail: "not checked: this ingester stores no OTLP logs (operation logging needs ingester >= 1.4.0)" });
745
+ return finish();
746
+ }
747
+ const logRes = await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/logs`, {
748
+ body: JSON.stringify(testLogs({ service, environment, release, traceId })),
749
+ headers,
750
+ unref: false,
751
+ timeoutMs
752
+ });
753
+ if (!logRes || logRes.status >= 300) {
754
+ add({
755
+ step: "logs",
756
+ status: "fail",
757
+ detail: logRes ? `/v1/logs ${logRes.status}: ${_chunkS6WJ5YLPcjs.refusalReason.call(void 0, logRes.body) || "refused"}` : "no answer from /v1/logs",
758
+ fix: _optionalChain([logRes, 'optionalAccess', _40 => _40.status]) === 404 ? "This ingester has no /v1/logs: upgrade it, or check for a path prefix in the endpoint." : AUTH_FIX[_nullishCoalesce(_optionalChain([logRes, 'optionalAccess', _41 => _41.status]), () => ( 0))]
759
+ });
760
+ return finish();
761
+ }
762
+ const logStart = Date.now();
763
+ let logRows = 0;
764
+ for (; ; ) {
765
+ const body = parse(await _chunkS6WJ5YLPcjs.compatRequest.call(void 0, "GET", `${endpoint}/v1/verify/${traceId}`, { headers, unref: false, timeoutMs }));
766
+ logRows = _nullishCoalesce(_optionalChain([body, 'optionalAccess', _42 => _42.stored, 'optionalAccess', _43 => _43.logs, 'optionalAccess', _44 => _44.rows]), () => ( 0));
767
+ if (logRows > 0 || Date.now() - logStart >= waitMs) break;
768
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
769
+ }
770
+ add(
771
+ logRows > 0 ? { step: "logs", status: "pass", detail: `${logRows} log record stored after ${((Date.now() - logStart) / 1e3).toFixed(1)} s` } : { step: "logs", status: "fail", detail: "/v1/logs accepted the record but it was not stored", fix: "Check the ingester's logs for runtime_logs write errors." }
772
+ );
773
+ return finish();
774
+ }
775
+ var MARK = { pass: "ok ", fail: "FAIL", warn: "warn", skip: "-- " };
776
+ function formatVerifyReport(report) {
777
+ const lines = [
778
+ "Autter Runtime verify",
779
+ ` endpoint ${report.endpoint}`,
780
+ ` service ${report.service} (environment ${report.environment}${report.release ? `, release ${report.release}` : ", no release"})`,
781
+ ` test id ${report.traceId}`,
782
+ ""
783
+ ];
784
+ for (const step of report.steps) {
785
+ lines.push(` ${MARK[step.status]} ${step.step.padEnd(9)} ${step.detail}`);
786
+ if (step.status !== "pass" && step.status !== "skip" && step.fix) lines.push(` -> ${step.fix}`);
787
+ }
788
+ lines.push("");
789
+ if (report.firstFailure) {
790
+ lines.push(`First failing step: ${report.firstFailure.step}. ${_nullishCoalesce(report.firstFailure.fix, () => ( ""))}`.trim());
791
+ } else {
792
+ const warned = report.steps.filter((s) => s.status === "warn").map((s) => s.step);
793
+ lines.push(
794
+ `Delivered: a test event from "${report.service}" was accepted and stored.` + (warned.length ? ` Check the warnings: ${warned.join(", ")}.` : "") + ' It is stored under environment "' + report.environment + '" and never opens an issue.'
795
+ );
796
+ }
797
+ return lines.join("\n");
798
+ }
799
+ var VERIFY_USAGE = `Usage: npx @autter/runtime-node verify [options]
800
+
801
+ Sends one tagged test event (a span with an error, and a log record) through
802
+ the real path and reads it back, naming the first step that fails:
803
+ key, network (DNS/TCP/TLS/HTTP), ingester, storage, auth, ingest, stored,
804
+ trace, release, logs. The test event is stored under environment "verify"
805
+ and never opens an issue.
806
+
807
+ Options:
808
+ --endpoint <url> Ingester base URL (default: $AUTTER_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT, or https://otlp.autter.dev)
809
+ --key <key> Server ingest key (default: $AUTTER_RUNTIME_KEY)
810
+ --service <name> Service name (default: $AUTTER_SERVICE, $OTEL_SERVICE_NAME, or package.json name)
811
+ --environment <name> Environment for the test event (default: verify)
812
+ --release <id> Release to check (default: $AUTTER_RELEASE, $GIT_SHA or your platform's commit variable)
813
+ --wait <ms> How long to wait for the event to be stored (default 20000)
814
+ --timeout <ms> Per-request timeout (default 5000)
815
+ --json Machine-readable output
816
+
817
+ Exit codes: 0 delivered, 1 a step failed, 2 ingester unreachable.`;
818
+ function parseVerifyArgs(argv) {
819
+ const options = {};
820
+ for (let i = 0; i < argv.length; i++) {
821
+ const arg = argv[i];
822
+ const [flag, inline] = arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, void 0];
823
+ const value = () => _nullishCoalesce(inline, () => ( argv[++i]));
824
+ const ms = (name) => {
825
+ const n = Number(value());
826
+ return Number.isFinite(n) && n > 0 ? n : { error: `${name} needs a positive number of ms` };
827
+ };
828
+ switch (flag) {
829
+ case "-h":
830
+ case "--help":
831
+ return { help: true };
832
+ case "--endpoint":
833
+ options.endpoint = value();
834
+ break;
835
+ case "--key":
836
+ options.key = value();
837
+ break;
838
+ case "--service":
839
+ options.service = value();
840
+ break;
841
+ case "--environment":
842
+ options.environment = value();
843
+ break;
844
+ case "--release":
845
+ options.release = value();
846
+ break;
847
+ case "--json":
848
+ options.json = true;
849
+ break;
850
+ case "--wait": {
851
+ const n = ms("--wait");
852
+ if (typeof n !== "number") return n;
853
+ options.waitMs = n;
854
+ break;
855
+ }
856
+ case "--timeout": {
857
+ const n = ms("--timeout");
858
+ if (typeof n !== "number") return n;
859
+ options.timeoutMs = n;
860
+ break;
861
+ }
862
+ default:
863
+ return { error: `unknown option ${arg}` };
864
+ }
865
+ }
866
+ return options;
867
+ }
868
+
869
+
870
+
871
+
872
+
873
+
874
+
875
+
876
+
877
+
878
+ exports.runDoctor = runDoctor; exports.formatDoctorReport = formatDoctorReport; exports.DOCTOR_USAGE = DOCTOR_USAGE; exports.parseDoctorArgs = parseDoctorArgs; exports.runVerify = runVerify; exports.formatVerifyReport = formatVerifyReport; exports.VERIFY_USAGE = VERIFY_USAGE; exports.parseVerifyArgs = parseVerifyArgs;