@astroscope/node 1.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/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/boot.d.ts +15 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +2 -0
- package/dist/compress-B11aeRHQ.js +58 -0
- package/dist/compress-B11aeRHQ.js.map +1 -0
- package/dist/construct-DgB-jR0a.d.ts +14 -0
- package/dist/construct-DgB-jR0a.d.ts.map +1 -0
- package/dist/context-Bkg-FnnQ.js +20 -0
- package/dist/context-Bkg-FnnQ.js.map +1 -0
- package/dist/csrf-middleware-entrypoint.d.ts +5 -0
- package/dist/csrf-middleware-entrypoint.d.ts.map +1 -0
- package/dist/csrf-middleware-entrypoint.js +30 -0
- package/dist/csrf-middleware-entrypoint.js.map +1 -0
- package/dist/dev-middleware-entrypoint.d.ts +15 -0
- package/dist/dev-middleware-entrypoint.d.ts.map +1 -0
- package/dist/dev-middleware-entrypoint.js +26 -0
- package/dist/dev-middleware-entrypoint.js.map +1 -0
- package/dist/events-CUzQ2_cp.d.ts +20 -0
- package/dist/events-CUzQ2_cp.d.ts.map +1 -0
- package/dist/events-CgoM3Fvu.js +39 -0
- package/dist/events-CgoM3Fvu.js.map +1 -0
- package/dist/excludes-DLF3A_Cf.d.ts +63 -0
- package/dist/excludes-DLF3A_Cf.d.ts.map +1 -0
- package/dist/excludes-pE23EbmQ.js +78 -0
- package/dist/excludes-pE23EbmQ.js.map +1 -0
- package/dist/excludes.d.ts +2 -0
- package/dist/excludes.js +2 -0
- package/dist/generation-Bp2IA0jf.js +45 -0
- package/dist/generation-Bp2IA0jf.js.map +1 -0
- package/dist/health.d.ts +38 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +31 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +732 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/events.d.ts +2 -0
- package/dist/lifecycle/events.js +2 -0
- package/dist/log/index.d.ts +55 -0
- package/dist/log/index.d.ts.map +1 -0
- package/dist/log/index.js +2 -0
- package/dist/log-CSJlKaxY.js +112 -0
- package/dist/log-CSJlKaxY.js.map +1 -0
- package/dist/mime-C_GwZovh.js +39 -0
- package/dist/mime-C_GwZovh.js.map +1 -0
- package/dist/native-mount-hhwWdLtL.js +120 -0
- package/dist/native-mount-hhwWdLtL.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +2 -0
- package/dist/prepare-Be2GgRyf.js +390 -0
- package/dist/prepare-Be2GgRyf.js.map +1 -0
- package/dist/preview.d.ts +12 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +23 -0
- package/dist/preview.js.map +1 -0
- package/dist/restart-page.html +107 -0
- package/dist/route-middleware-entrypoint.d.ts +13 -0
- package/dist/route-middleware-entrypoint.d.ts.map +1 -0
- package/dist/route-middleware-entrypoint.js +24 -0
- package/dist/route-middleware-entrypoint.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +441 -0
- package/dist/server.js.map +1 -0
- package/dist/store-8pnTxM1x.js +37 -0
- package/dist/store-8pnTxM1x.js.map +1 -0
- package/dist/store-BIUF4lqk.js +29 -0
- package/dist/store-BIUF4lqk.js.map +1 -0
- package/dist/types-D0uMBi2M.d.ts +12 -0
- package/dist/types-D0uMBi2M.d.ts.map +1 -0
- package/package.json +114 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { t as emit } from "./events-CgoM3Fvu.js";
|
|
2
|
+
import { t as getLogStore } from "./store-BIUF4lqk.js";
|
|
3
|
+
import { n as log, t as generateReqId } from "./log-CSJlKaxY.js";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { SpanKind, SpanStatusCode, ValueType, context, isSpanContextValid, metrics, propagation, trace } from "@opentelemetry/api";
|
|
6
|
+
import pino from "pino";
|
|
7
|
+
import { createMatcher } from "@entwico/dash/match";
|
|
8
|
+
//#region src/lifecycle/lifecycle.ts
|
|
9
|
+
async function runStartup(boot, context) {
|
|
10
|
+
await emit("beforeOnStartup", context);
|
|
11
|
+
await boot.onStartup?.(context);
|
|
12
|
+
await emit("afterOnStartup", context);
|
|
13
|
+
}
|
|
14
|
+
async function runShutdown(boot, context) {
|
|
15
|
+
try {
|
|
16
|
+
await emit("beforeOnShutdown", context);
|
|
17
|
+
await boot.onShutdown?.(context);
|
|
18
|
+
} finally {
|
|
19
|
+
await emit("afterOnShutdown", context);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/observability/telemetry/metrics.ts
|
|
24
|
+
const LIB_NAME$1 = "@astroscope/node";
|
|
25
|
+
let httpRequestDuration = null;
|
|
26
|
+
let httpActiveRequests = null;
|
|
27
|
+
let actionDuration = null;
|
|
28
|
+
function getHttpRequestDuration() {
|
|
29
|
+
return httpRequestDuration ??= metrics.getMeter(LIB_NAME$1).createHistogram("http.server.request.duration", {
|
|
30
|
+
description: "Duration of HTTP server requests",
|
|
31
|
+
unit: "s",
|
|
32
|
+
valueType: ValueType.DOUBLE
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function getHttpActiveRequests() {
|
|
36
|
+
return httpActiveRequests ??= metrics.getMeter(LIB_NAME$1).createUpDownCounter("http.server.active_requests", {
|
|
37
|
+
description: "Number of active HTTP server requests",
|
|
38
|
+
unit: "{request}",
|
|
39
|
+
valueType: ValueType.INT
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function getActionDuration() {
|
|
43
|
+
return actionDuration ??= metrics.getMeter(LIB_NAME$1).createHistogram("astro.action.duration", {
|
|
44
|
+
description: "Duration of Astro action executions",
|
|
45
|
+
unit: "s",
|
|
46
|
+
valueType: ValueType.DOUBLE
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Record the start of an HTTP request. Returns a function to call when the
|
|
51
|
+
* request ends. Route is unknown at the native-handler level, so active
|
|
52
|
+
* requests carry only the method.
|
|
53
|
+
*/
|
|
54
|
+
function recordHttpRequestStart(method) {
|
|
55
|
+
getHttpActiveRequests().add(1, { "http.request.method": method });
|
|
56
|
+
return () => {
|
|
57
|
+
getHttpActiveRequests().add(-1, { "http.request.method": method });
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function recordHttpRequestDuration(attributes, durationMs) {
|
|
61
|
+
getHttpRequestDuration().record(durationMs / 1e3, {
|
|
62
|
+
"http.request.method": attributes.method,
|
|
63
|
+
"http.route": attributes.route ?? "",
|
|
64
|
+
"http.response.status_code": attributes.status
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function recordActionDuration(attributes, durationMs) {
|
|
68
|
+
getActionDuration().record(durationMs / 1e3, {
|
|
69
|
+
"astro.action.name": attributes.name,
|
|
70
|
+
"http.response.status_code": attributes.status
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/observability/instrument.ts
|
|
75
|
+
const LIB_NAME = "@astroscope/node";
|
|
76
|
+
const ACTIONS_PREFIX = "/_actions/";
|
|
77
|
+
const REQUEST_ID_PATTERN = /^[\w.-]{1,64}$/;
|
|
78
|
+
const roundTime = (n) => Math.round(n * 100) / 100;
|
|
79
|
+
function getClientIp(req) {
|
|
80
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
81
|
+
return (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(",")[0]?.trim() ?? req.headers["x-real-ip"] ?? req.headers["cf-connecting-ip"];
|
|
82
|
+
}
|
|
83
|
+
function resolveReqId(req) {
|
|
84
|
+
const incoming = req.headers["x-request-id"];
|
|
85
|
+
const value = Array.isArray(incoming) ? incoming[0] : incoming;
|
|
86
|
+
return value && REQUEST_ID_PATTERN.test(value) ? value : generateReqId();
|
|
87
|
+
}
|
|
88
|
+
function chunkSize(chunk) {
|
|
89
|
+
if (chunk == null) return 0;
|
|
90
|
+
if (ArrayBuffer.isView(chunk)) return chunk.byteLength;
|
|
91
|
+
if (typeof chunk === "string") return Buffer.byteLength(chunk);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wraps the native request/response with logging and telemetry: a request
|
|
96
|
+
* logger in async context (real status, response size, aborted-vs-completed
|
|
97
|
+
* on `finish`/`close`), a SERVER span with propagation extraction, and
|
|
98
|
+
* request metrics. Both concerns honor their own exclude patterns; when both
|
|
99
|
+
* are excluded the request passes through untouched.
|
|
100
|
+
*/
|
|
101
|
+
function createRequestInstrumentation(config) {
|
|
102
|
+
const tracer = trace.getTracer(LIB_NAME);
|
|
103
|
+
const store = getLogStore();
|
|
104
|
+
const loggingExcluded = config.logging ? createMatcher(config.logging.exclude) : () => true;
|
|
105
|
+
const telemetryExcluded = config.telemetry ? createMatcher(config.telemetry.exclude) : () => true;
|
|
106
|
+
return (req, res, inner) => {
|
|
107
|
+
const url = req.url ?? "";
|
|
108
|
+
const queryIndex = url.indexOf("?");
|
|
109
|
+
const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
110
|
+
const method = req.method ?? "GET";
|
|
111
|
+
const logging = config.logging && !loggingExcluded(pathname) ? config.logging : false;
|
|
112
|
+
const telemetry = config.telemetry && !telemetryExcluded(pathname) ? config.telemetry : false;
|
|
113
|
+
if (!logging && !telemetry) {
|
|
114
|
+
inner();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const startTime = performance.now();
|
|
118
|
+
const isAction = pathname.startsWith(ACTIONS_PREFIX);
|
|
119
|
+
let requestLogger;
|
|
120
|
+
if (logging && store.root) {
|
|
121
|
+
const reqId = resolveReqId(req);
|
|
122
|
+
const reqData = {
|
|
123
|
+
method,
|
|
124
|
+
url: pathname
|
|
125
|
+
};
|
|
126
|
+
if (logging.extended) {
|
|
127
|
+
reqData["query"] = queryIndex === -1 ? "" : url.slice(queryIndex + 1);
|
|
128
|
+
reqData["headers"] = req.headers;
|
|
129
|
+
reqData["remoteAddress"] = getClientIp(req) ?? req.socket.remoteAddress;
|
|
130
|
+
}
|
|
131
|
+
requestLogger = store.root.child({
|
|
132
|
+
reqId,
|
|
133
|
+
req: reqData
|
|
134
|
+
});
|
|
135
|
+
res.setHeader("x-request-id", reqId);
|
|
136
|
+
}
|
|
137
|
+
const record = {
|
|
138
|
+
logger: requestLogger,
|
|
139
|
+
url,
|
|
140
|
+
route: void 0,
|
|
141
|
+
actionName: isAction ? pathname.slice(10).replace(/\/$/, "") : void 0
|
|
142
|
+
};
|
|
143
|
+
let span;
|
|
144
|
+
let endActiveRequest;
|
|
145
|
+
if (telemetry) {
|
|
146
|
+
const parentContext = propagation.extract(context.active(), req.headers);
|
|
147
|
+
const contentLength = req.headers["content-length"];
|
|
148
|
+
const clientIp = getClientIp(req);
|
|
149
|
+
const host = req.headers["host"];
|
|
150
|
+
span = tracer.startSpan(isAction ? `ACTION ${record.actionName}` : method, {
|
|
151
|
+
kind: SpanKind.SERVER,
|
|
152
|
+
attributes: {
|
|
153
|
+
"http.request.method": method,
|
|
154
|
+
"url.path": pathname,
|
|
155
|
+
"url.query": queryIndex === -1 ? "" : url.slice(queryIndex + 1),
|
|
156
|
+
"url.scheme": "http",
|
|
157
|
+
"user_agent.original": req.headers["user-agent"] ?? "",
|
|
158
|
+
...host && { "server.address": host },
|
|
159
|
+
...contentLength && { "http.request.body.size": parseInt(contentLength) },
|
|
160
|
+
...clientIp && { "client.address": clientIp }
|
|
161
|
+
}
|
|
162
|
+
}, parentContext);
|
|
163
|
+
endActiveRequest = recordHttpRequestStart(method);
|
|
164
|
+
}
|
|
165
|
+
let responseSize = 0;
|
|
166
|
+
let firstByteTime;
|
|
167
|
+
const originalWrite = res.write.bind(res);
|
|
168
|
+
const originalEnd = res.end.bind(res);
|
|
169
|
+
res.write = ((chunk, ...rest) => {
|
|
170
|
+
firstByteTime ??= performance.now();
|
|
171
|
+
responseSize += chunkSize(chunk);
|
|
172
|
+
return originalWrite(chunk, ...rest);
|
|
173
|
+
});
|
|
174
|
+
res.end = ((chunk, ...rest) => {
|
|
175
|
+
firstByteTime ??= performance.now();
|
|
176
|
+
responseSize += chunkSize(chunk);
|
|
177
|
+
return originalEnd(chunk, ...rest);
|
|
178
|
+
});
|
|
179
|
+
let finalized = false;
|
|
180
|
+
const finalize = (aborted) => {
|
|
181
|
+
if (finalized) return;
|
|
182
|
+
finalized = true;
|
|
183
|
+
const status = res.statusCode;
|
|
184
|
+
const responseTime = performance.now() - startTime;
|
|
185
|
+
if (requestLogger) requestLogger[status >= 500 ? "error" : status >= 400 ? "warn" : "info"]({
|
|
186
|
+
res: { statusCode: status },
|
|
187
|
+
responseTime: roundTime(responseTime),
|
|
188
|
+
ttfb: roundTime((firstByteTime ?? performance.now()) - startTime),
|
|
189
|
+
responseSize,
|
|
190
|
+
...record.route && { route: record.route },
|
|
191
|
+
...aborted && { aborted: true }
|
|
192
|
+
}, aborted ? "request aborted" : "request completed");
|
|
193
|
+
if (span) {
|
|
194
|
+
span.setAttribute("http.response.status_code", status);
|
|
195
|
+
span.setAttribute("http.response.body.size", responseSize);
|
|
196
|
+
if (aborted || status >= 400) span.setStatus({
|
|
197
|
+
code: SpanStatusCode.ERROR,
|
|
198
|
+
message: aborted ? "request aborted" : `HTTP ${status}`
|
|
199
|
+
});
|
|
200
|
+
else span.setStatus({ code: SpanStatusCode.OK });
|
|
201
|
+
span.end();
|
|
202
|
+
}
|
|
203
|
+
if (telemetry) {
|
|
204
|
+
endActiveRequest?.();
|
|
205
|
+
recordHttpRequestDuration({
|
|
206
|
+
method,
|
|
207
|
+
route: record.route,
|
|
208
|
+
status
|
|
209
|
+
}, responseTime);
|
|
210
|
+
if (record.actionName) recordActionDuration({
|
|
211
|
+
name: record.actionName,
|
|
212
|
+
status
|
|
213
|
+
}, responseTime);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
res.once("finish", () => finalize(false));
|
|
217
|
+
res.once("close", () => finalize(!res.writableFinished));
|
|
218
|
+
const run = () => store.requestStorage.run(record, inner);
|
|
219
|
+
if (span) context.with(trace.setSpan(context.active(), span), run);
|
|
220
|
+
else run();
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/observability/log/construct.ts
|
|
225
|
+
/**
|
|
226
|
+
* Compose the user mixin (if any) with platform trace correlation: when a
|
|
227
|
+
* span is active, every entry carries `trace_id` / `span_id` / `trace_flags`.
|
|
228
|
+
*/
|
|
229
|
+
function composeMixin(userMixin) {
|
|
230
|
+
return (mergeObject, level, logger) => {
|
|
231
|
+
const user = userMixin ? userMixin(mergeObject, level, logger) : {};
|
|
232
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
233
|
+
if (!spanContext || !isSpanContextValid(spanContext)) return user;
|
|
234
|
+
return {
|
|
235
|
+
...user,
|
|
236
|
+
trace_id: spanContext.traceId,
|
|
237
|
+
span_id: spanContext.spanId,
|
|
238
|
+
trace_flags: `0${spanContext.traceFlags.toString(16)}`
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Construct the root logger from the app's options seam and replay any logs
|
|
244
|
+
* buffered before construction (original timestamps kept as `bufferedTime`).
|
|
245
|
+
* In dev this runs once per generation; the buffer only exists the first time.
|
|
246
|
+
*/
|
|
247
|
+
async function constructRootLogger(factory, ctx) {
|
|
248
|
+
const store = getLogStore();
|
|
249
|
+
const options = (typeof factory === "function" ? await factory(ctx) : factory) ?? {};
|
|
250
|
+
const root = pino({
|
|
251
|
+
level: "info",
|
|
252
|
+
...options,
|
|
253
|
+
mixin: composeMixin(options.mixin)
|
|
254
|
+
});
|
|
255
|
+
store.root = root;
|
|
256
|
+
for (const entry of store.buffer.splice(0)) {
|
|
257
|
+
const bindings = entry.bindings.length ? Object.assign({}, ...entry.bindings) : {};
|
|
258
|
+
root.child({
|
|
259
|
+
...bindings,
|
|
260
|
+
bufferedTime: new Date(entry.time).toISOString()
|
|
261
|
+
})[entry.level](...entry.args);
|
|
262
|
+
}
|
|
263
|
+
if (store.dropped > 0) {
|
|
264
|
+
root.warn({ dropped: store.dropped }, "early log buffer overflowed, entries dropped");
|
|
265
|
+
store.dropped = 0;
|
|
266
|
+
}
|
|
267
|
+
return root;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Failure path for startups that die before the logger exists: dump the
|
|
271
|
+
* buffered entries to the console so no phase is silent.
|
|
272
|
+
*/
|
|
273
|
+
function dumpEarlyLogs() {
|
|
274
|
+
const store = getLogStore();
|
|
275
|
+
if (store.root) return;
|
|
276
|
+
for (const entry of store.buffer.splice(0)) {
|
|
277
|
+
const bindings = entry.bindings.length ? Object.assign({}, ...entry.bindings) : void 0;
|
|
278
|
+
console.error(new Date(entry.time).toISOString(), entry.level.toUpperCase(), ...bindings ? [bindings] : [], ...entry.args);
|
|
279
|
+
}
|
|
280
|
+
if (store.dropped > 0) {
|
|
281
|
+
console.error(`(${store.dropped} early log entries dropped)`);
|
|
282
|
+
store.dropped = 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/observability/telemetry/sdk.ts
|
|
287
|
+
/**
|
|
288
|
+
* Platform-owned telemetry bundle: NodeSDK with undici (fetch) and node
|
|
289
|
+
* runtime instrumentation, host metrics, and a Prometheus reader. Trace
|
|
290
|
+
* exporters are driven by standard `OTEL_*` env vars; without any of them
|
|
291
|
+
* traces stay off (no failing localhost OTLP exports).
|
|
292
|
+
*
|
|
293
|
+
* Guarded per process (dev restarts are in-process; a re-created NodeSDK
|
|
294
|
+
* would double-register instrumentations and leak the Prometheus port).
|
|
295
|
+
*/
|
|
296
|
+
const TELEMETRY_KEY = Symbol.for("@astroscope/node/telemetry");
|
|
297
|
+
function getHandle() {
|
|
298
|
+
return globalThis[TELEMETRY_KEY];
|
|
299
|
+
}
|
|
300
|
+
function defaultEnv(key, value) {
|
|
301
|
+
if (!process.env[key]) process.env[key] = value;
|
|
302
|
+
}
|
|
303
|
+
async function startTelemetry(options) {
|
|
304
|
+
const g = globalThis;
|
|
305
|
+
if (g[TELEMETRY_KEY]) return;
|
|
306
|
+
if (process.env["OTEL_SDK_DISABLED"] === "true") {
|
|
307
|
+
log.debug("telemetry disabled via OTEL_SDK_DISABLED");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!process.env["OTEL_EXPORTER_OTLP_ENDPOINT"] && !process.env["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"]) defaultEnv("OTEL_TRACES_EXPORTER", "none");
|
|
311
|
+
defaultEnv("OTEL_METRICS_EXPORTER", "none");
|
|
312
|
+
defaultEnv("OTEL_LOGS_EXPORTER", "none");
|
|
313
|
+
const [{ NodeSDK }, { UndiciInstrumentation }, { RuntimeNodeInstrumentation }, { PrometheusExporter }, { HostMetrics }] = await Promise.all([
|
|
314
|
+
import("@opentelemetry/sdk-node"),
|
|
315
|
+
import("@opentelemetry/instrumentation-undici"),
|
|
316
|
+
import("@opentelemetry/instrumentation-runtime-node"),
|
|
317
|
+
import("@opentelemetry/exporter-prometheus"),
|
|
318
|
+
import("@opentelemetry/host-metrics")
|
|
319
|
+
]);
|
|
320
|
+
const prometheus = options.prometheus ? {
|
|
321
|
+
host: process.env["OTEL_EXPORTER_PROMETHEUS_HOST"] ?? options.prometheus.host ?? "0.0.0.0",
|
|
322
|
+
port: process.env["OTEL_EXPORTER_PROMETHEUS_PORT"] ? Number(process.env["OTEL_EXPORTER_PROMETHEUS_PORT"]) : options.prometheus.port ?? 9464
|
|
323
|
+
} : false;
|
|
324
|
+
const sdk = new NodeSDK({
|
|
325
|
+
instrumentations: [new UndiciInstrumentation(), new RuntimeNodeInstrumentation()],
|
|
326
|
+
...prometheus && { metricReaders: [new PrometheusExporter(prometheus)] }
|
|
327
|
+
});
|
|
328
|
+
sdk.start();
|
|
329
|
+
new HostMetrics().start();
|
|
330
|
+
g[TELEMETRY_KEY] = { shutdown: () => sdk.shutdown() };
|
|
331
|
+
if (prometheus) log.debug({
|
|
332
|
+
host: prometheus.host,
|
|
333
|
+
port: prometheus.port
|
|
334
|
+
}, "prometheus metrics listening");
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Flush and shut the SDK down. Prod-only (dev keeps the SDK for the process
|
|
338
|
+
* lifetime across generations).
|
|
339
|
+
*/
|
|
340
|
+
async function shutdownTelemetry() {
|
|
341
|
+
const handle = getHandle();
|
|
342
|
+
if (!handle) return;
|
|
343
|
+
delete globalThis[TELEMETRY_KEY];
|
|
344
|
+
await handle.shutdown();
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/platform/env.ts
|
|
348
|
+
/**
|
|
349
|
+
* Platform env loading (position −1, before the config seam):
|
|
350
|
+
* `CONFIG_PATH` → `./.env` → none. Existing process env vars win
|
|
351
|
+
*/
|
|
352
|
+
function loadEnvFiles() {
|
|
353
|
+
const configPath = process.env["CONFIG_PATH"];
|
|
354
|
+
if (configPath) {
|
|
355
|
+
process.loadEnvFile(configPath);
|
|
356
|
+
log.debug({ path: configPath }, "loaded env file from CONFIG_PATH");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (fs.existsSync(".env")) {
|
|
360
|
+
process.loadEnvFile(".env");
|
|
361
|
+
log.debug({ path: ".env" }, "loaded env file");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
log.debug("no env file loaded");
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/platform/prepare.ts
|
|
368
|
+
const INSTRUMENTATION_KEY = Symbol.for("@astroscope/node/instrumentation");
|
|
369
|
+
/**
|
|
370
|
+
* The platform sequence in front of the boot lifecycle:
|
|
371
|
+
* env → config → instrumentation (platform SDK + `register`, once per
|
|
372
|
+
* process) → logger construction (after instrumentation, so entries carry
|
|
373
|
+
* trace correlation). Prod runs it once in `startServer()`; dev re-runs it
|
|
374
|
+
* per generation with the once-per-process parts guarded.
|
|
375
|
+
*/
|
|
376
|
+
async function preparePlatform(options) {
|
|
377
|
+
loadEnvFiles();
|
|
378
|
+
await options.seams.config?.();
|
|
379
|
+
const g = globalThis;
|
|
380
|
+
if (!g[INSTRUMENTATION_KEY]) {
|
|
381
|
+
g[INSTRUMENTATION_KEY] = true;
|
|
382
|
+
if (options.telemetry) await startTelemetry(options.telemetry);
|
|
383
|
+
await (await options.seams.instrumentation?.())?.register?.({ dev: options.dev });
|
|
384
|
+
}
|
|
385
|
+
await constructRootLogger((await options.seams.log?.())?.default, { dev: options.dev });
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
export { runShutdown as a, createRequestInstrumentation as i, shutdownTelemetry as n, runStartup as o, dumpEarlyLogs as r, preparePlatform as t };
|
|
389
|
+
|
|
390
|
+
//# sourceMappingURL=prepare-Be2GgRyf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare-Be2GgRyf.js","names":["LIB_NAME"],"sources":["../src/lifecycle/lifecycle.ts","../src/observability/telemetry/metrics.ts","../src/observability/instrument.ts","../src/observability/log/construct.ts","../src/observability/telemetry/sdk.ts","../src/platform/env.ts","../src/platform/prepare.ts"],"sourcesContent":["import { emit } from './events.js';\nimport type { BootContext } from './types.js';\n\nexport interface BootModule {\n onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;\n onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;\n}\n\nexport async function runStartup(boot: BootModule, context: BootContext): Promise<void> {\n await emit('beforeOnStartup', context);\n await boot.onStartup?.(context);\n await emit('afterOnStartup', context);\n}\n\nexport async function runShutdown(boot: BootModule, context: BootContext): Promise<void> {\n try {\n await emit('beforeOnShutdown', context);\n await boot.onShutdown?.(context);\n } finally {\n await emit('afterOnShutdown', context);\n }\n}\n","import { type Histogram, type UpDownCounter, ValueType, metrics } from '@opentelemetry/api';\n\nconst LIB_NAME = '@astroscope/node';\n\n// lazy initialization so instruments bind to the SDK meter provider\nlet httpRequestDuration: Histogram | null = null;\nlet httpActiveRequests: UpDownCounter | null = null;\nlet actionDuration: Histogram | null = null;\n\nfunction getHttpRequestDuration(): Histogram {\n return (httpRequestDuration ??= metrics.getMeter(LIB_NAME).createHistogram('http.server.request.duration', {\n description: 'Duration of HTTP server requests',\n unit: 's',\n valueType: ValueType.DOUBLE,\n }));\n}\n\nfunction getHttpActiveRequests(): UpDownCounter {\n return (httpActiveRequests ??= metrics.getMeter(LIB_NAME).createUpDownCounter('http.server.active_requests', {\n description: 'Number of active HTTP server requests',\n unit: '{request}',\n valueType: ValueType.INT,\n }));\n}\n\nfunction getActionDuration(): Histogram {\n return (actionDuration ??= metrics.getMeter(LIB_NAME).createHistogram('astro.action.duration', {\n description: 'Duration of Astro action executions',\n unit: 's',\n valueType: ValueType.DOUBLE,\n }));\n}\n\n/**\n * Record the start of an HTTP request. Returns a function to call when the\n * request ends. Route is unknown at the native-handler level, so active\n * requests carry only the method.\n */\nexport function recordHttpRequestStart(method: string): () => void {\n getHttpActiveRequests().add(1, { 'http.request.method': method });\n\n return () => {\n getHttpActiveRequests().add(-1, { 'http.request.method': method });\n };\n}\n\nexport function recordHttpRequestDuration(\n attributes: { method: string; route: string | undefined; status: number },\n durationMs: number,\n): void {\n getHttpRequestDuration().record(durationMs / 1000, {\n 'http.request.method': attributes.method,\n 'http.route': attributes.route ?? '',\n 'http.response.status_code': attributes.status,\n });\n}\n\nexport function recordActionDuration(attributes: { name: string; status: number }, durationMs: number): void {\n getActionDuration().record(durationMs / 1000, {\n 'astro.action.name': attributes.name,\n 'http.response.status_code': attributes.status,\n });\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createMatcher } from '@entwico/dash/match';\nimport { SpanKind, SpanStatusCode, context, propagation, trace } from '@opentelemetry/api';\nimport type { Logger } from 'pino';\nimport type { ExcludePattern } from '../excludes/excludes.js';\nimport { generateReqId } from './log/index.js';\nimport { type RequestRecord, getLogStore } from './log/store.js';\nimport { recordActionDuration, recordHttpRequestDuration, recordHttpRequestStart } from './telemetry/metrics.js';\n\nconst LIB_NAME = '@astroscope/node';\nconst ACTIONS_PREFIX = '/_actions/';\nconst REQUEST_ID_PATTERN = /^[\\w.-]{1,64}$/;\n\nconst roundTime = (n: number) => Math.round(n * 100) / 100;\n\nexport interface RequestLoggingConfig {\n exclude: ExcludePattern[];\n extended: boolean;\n}\n\nexport interface RequestTelemetryConfig {\n exclude: ExcludePattern[];\n}\n\nexport interface RequestInstrumentationConfig {\n logging: RequestLoggingConfig | false;\n telemetry: RequestTelemetryConfig | false;\n}\n\nfunction getClientIp(req: IncomingMessage): string | undefined {\n const forwarded = req.headers['x-forwarded-for'];\n const first = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n\n return (\n first?.split(',')[0]?.trim() ??\n (req.headers['x-real-ip'] as string | undefined) ??\n (req.headers['cf-connecting-ip'] as string | undefined)\n );\n}\n\nfunction resolveReqId(req: IncomingMessage): string {\n const incoming = req.headers['x-request-id'];\n const value = Array.isArray(incoming) ? incoming[0] : incoming;\n\n return value && REQUEST_ID_PATTERN.test(value) ? value : generateReqId();\n}\n\nfunction chunkSize(chunk: unknown): number {\n if (chunk == null) return 0;\n if (ArrayBuffer.isView(chunk)) return chunk.byteLength;\n if (typeof chunk === 'string') return Buffer.byteLength(chunk);\n\n return 0;\n}\n\n/**\n * Wraps the native request/response with logging and telemetry: a request\n * logger in async context (real status, response size, aborted-vs-completed\n * on `finish`/`close`), a SERVER span with propagation extraction, and\n * request metrics. Both concerns honor their own exclude patterns; when both\n * are excluded the request passes through untouched.\n */\nexport function createRequestInstrumentation(config: RequestInstrumentationConfig) {\n const tracer = trace.getTracer(LIB_NAME);\n const store = getLogStore();\n const loggingExcluded = config.logging ? createMatcher(config.logging.exclude) : () => true;\n const telemetryExcluded = config.telemetry ? createMatcher(config.telemetry.exclude) : () => true;\n\n return (req: IncomingMessage, res: ServerResponse, inner: () => void): void => {\n const url = req.url ?? '';\n const queryIndex = url.indexOf('?');\n const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);\n const method = req.method ?? 'GET';\n\n const logging = config.logging && !loggingExcluded(pathname) ? config.logging : false;\n const telemetry = config.telemetry && !telemetryExcluded(pathname) ? config.telemetry : false;\n\n if (!logging && !telemetry) {\n inner();\n\n return;\n }\n\n const startTime = performance.now();\n const isAction = pathname.startsWith(ACTIONS_PREFIX);\n\n let requestLogger: Logger | undefined;\n\n if (logging && store.root) {\n const reqId = resolveReqId(req);\n const reqData: Record<string, unknown> = { method, url: pathname };\n\n // extended logging includes potentially sensitive data\n if (logging.extended) {\n reqData['query'] = queryIndex === -1 ? '' : url.slice(queryIndex + 1);\n reqData['headers'] = req.headers;\n reqData['remoteAddress'] = getClientIp(req) ?? req.socket.remoteAddress;\n }\n\n requestLogger = store.root.child({ reqId, req: reqData });\n\n res.setHeader('x-request-id', reqId);\n }\n\n const record: RequestRecord = {\n logger: requestLogger,\n url,\n route: undefined,\n actionName: isAction ? pathname.slice(ACTIONS_PREFIX.length).replace(/\\/$/, '') : undefined,\n };\n\n let span: ReturnType<typeof tracer.startSpan> | undefined;\n let endActiveRequest: (() => void) | undefined;\n\n if (telemetry) {\n const parentContext = propagation.extract(context.active(), req.headers);\n const contentLength = req.headers['content-length'];\n const clientIp = getClientIp(req);\n const host = req.headers['host'];\n\n span = tracer.startSpan(\n isAction ? `ACTION ${record.actionName}` : method,\n {\n kind: SpanKind.SERVER,\n attributes: {\n 'http.request.method': method,\n 'url.path': pathname,\n 'url.query': queryIndex === -1 ? '' : url.slice(queryIndex + 1),\n 'url.scheme': 'http',\n 'user_agent.original': req.headers['user-agent'] ?? '',\n ...(host && { 'server.address': host }),\n ...(contentLength && { 'http.request.body.size': parseInt(contentLength) }),\n ...(clientIp && { 'client.address': clientIp }),\n },\n },\n parentContext,\n );\n\n endActiveRequest = recordHttpRequestStart(method);\n }\n\n let responseSize = 0;\n let firstByteTime: number | undefined;\n\n const originalWrite = res.write.bind(res);\n const originalEnd = res.end.bind(res);\n\n res.write = ((chunk: unknown, ...rest: unknown[]) => {\n firstByteTime ??= performance.now();\n responseSize += chunkSize(chunk);\n\n return (originalWrite as (...args: unknown[]) => boolean)(chunk, ...rest);\n }) as typeof res.write;\n\n res.end = ((chunk: unknown, ...rest: unknown[]) => {\n firstByteTime ??= performance.now();\n responseSize += chunkSize(chunk);\n\n return (originalEnd as (...args: unknown[]) => ServerResponse)(chunk, ...rest);\n }) as typeof res.end;\n\n let finalized = false;\n\n const finalize = (aborted: boolean): void => {\n if (finalized) return;\n\n finalized = true;\n\n const status = res.statusCode;\n const responseTime = performance.now() - startTime;\n\n if (requestLogger) {\n const level = status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';\n\n requestLogger[level](\n {\n res: { statusCode: status },\n responseTime: roundTime(responseTime),\n ttfb: roundTime((firstByteTime ?? performance.now()) - startTime),\n responseSize,\n ...(record.route && { route: record.route }),\n ...(aborted && { aborted: true }),\n },\n aborted ? 'request aborted' : 'request completed',\n );\n }\n\n if (span) {\n span.setAttribute('http.response.status_code', status);\n span.setAttribute('http.response.body.size', responseSize);\n\n if (aborted || status >= 400) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: aborted ? 'request aborted' : `HTTP ${status}` });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n\n span.end();\n }\n\n if (telemetry) {\n endActiveRequest?.();\n recordHttpRequestDuration({ method, route: record.route, status }, responseTime);\n\n if (record.actionName) {\n recordActionDuration({ name: record.actionName, status }, responseTime);\n }\n }\n };\n\n res.once('finish', () => finalize(false));\n res.once('close', () => finalize(!res.writableFinished));\n\n const run = (): void => store.requestStorage.run(record, inner);\n\n if (span) {\n context.with(trace.setSpan(context.active(), span), run);\n } else {\n run();\n }\n };\n}\n","import { isSpanContextValid, trace } from '@opentelemetry/api';\nimport pino, { type Bindings, type Logger, type LoggerOptions } from 'pino';\nimport { getLogStore } from './store.js';\n\n/**\n * Contract of the `src/log.ts` entry seam: pino logger options, or a factory\n * producing them. Never a logger instance — the platform constructs the\n * logger itself (after instrumentation, so trace correlation works).\n */\nexport type LoggerOptionsFactory = LoggerOptions | ((ctx: { dev: boolean }) => LoggerOptions | Promise<LoggerOptions>);\n\n/**\n * Compose the user mixin (if any) with platform trace correlation: when a\n * span is active, every entry carries `trace_id` / `span_id` / `trace_flags`.\n */\nfunction composeMixin(userMixin: LoggerOptions['mixin']): NonNullable<LoggerOptions['mixin']> {\n return (mergeObject, level, logger) => {\n const user = userMixin ? userMixin(mergeObject, level, logger) : {};\n const spanContext = trace.getActiveSpan()?.spanContext();\n\n if (!spanContext || !isSpanContextValid(spanContext)) return user;\n\n return {\n ...user,\n trace_id: spanContext.traceId,\n span_id: spanContext.spanId,\n trace_flags: `0${spanContext.traceFlags.toString(16)}`,\n };\n };\n}\n\n/**\n * Construct the root logger from the app's options seam and replay any logs\n * buffered before construction (original timestamps kept as `bufferedTime`).\n * In dev this runs once per generation; the buffer only exists the first time.\n */\nexport async function constructRootLogger(\n factory: LoggerOptionsFactory | undefined,\n ctx: { dev: boolean },\n): Promise<Logger> {\n const store = getLogStore();\n const options = (typeof factory === 'function' ? await factory(ctx) : factory) ?? {};\n const root = pino({ level: 'info', ...options, mixin: composeMixin(options.mixin) });\n\n store.root = root;\n\n for (const entry of store.buffer.splice(0)) {\n const bindings = entry.bindings.length ? (Object.assign({}, ...entry.bindings) as Bindings) : {};\n const target = root.child({ ...bindings, bufferedTime: new Date(entry.time).toISOString() });\n\n (target[entry.level] as (...args: unknown[]) => void)(...entry.args);\n }\n\n if (store.dropped > 0) {\n root.warn({ dropped: store.dropped }, 'early log buffer overflowed, entries dropped');\n store.dropped = 0;\n }\n\n return root;\n}\n\n/**\n * Failure path for startups that die before the logger exists: dump the\n * buffered entries to the console so no phase is silent.\n */\nexport function dumpEarlyLogs(): void {\n const store = getLogStore();\n\n if (store.root) return;\n\n for (const entry of store.buffer.splice(0)) {\n const bindings = entry.bindings.length ? Object.assign({}, ...entry.bindings) : undefined;\n\n console.error(\n new Date(entry.time).toISOString(),\n entry.level.toUpperCase(),\n ...(bindings ? [bindings] : []),\n ...entry.args,\n );\n }\n\n if (store.dropped > 0) {\n console.error(`(${store.dropped} early log entries dropped)`);\n store.dropped = 0;\n }\n}\n","import { log } from '../log/index.js';\n\n/**\n * Platform-owned telemetry bundle: NodeSDK with undici (fetch) and node\n * runtime instrumentation, host metrics, and a Prometheus reader. Trace\n * exporters are driven by standard `OTEL_*` env vars; without any of them\n * traces stay off (no failing localhost OTLP exports).\n *\n * Guarded per process (dev restarts are in-process; a re-created NodeSDK\n * would double-register instrumentations and leak the Prometheus port).\n */\n\nconst TELEMETRY_KEY = Symbol.for('@astroscope/node/telemetry');\n\ninterface TelemetryHandle {\n shutdown: () => Promise<void>;\n}\n\nexport interface TelemetrySdkOptions {\n prometheus: { host?: string | undefined; port?: number | undefined } | false;\n}\n\nfunction getHandle(): TelemetryHandle | undefined {\n return (globalThis as Record<symbol, unknown>)[TELEMETRY_KEY] as TelemetryHandle | undefined;\n}\n\nfunction defaultEnv(key: string, value: string): void {\n if (!process.env[key]) process.env[key] = value;\n}\n\nexport async function startTelemetry(options: TelemetrySdkOptions): Promise<void> {\n const g = globalThis as Record<symbol, unknown>;\n\n if (g[TELEMETRY_KEY]) return;\n\n if (process.env['OTEL_SDK_DISABLED'] === 'true') {\n log.debug('telemetry disabled via OTEL_SDK_DISABLED');\n\n return;\n }\n\n // without an explicitly configured exporter target, exporting traces to the\n // default localhost OTLP endpoint would fail on every flush\n if (!process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] && !process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT']) {\n defaultEnv('OTEL_TRACES_EXPORTER', 'none');\n }\n\n defaultEnv('OTEL_METRICS_EXPORTER', 'none');\n defaultEnv('OTEL_LOGS_EXPORTER', 'none');\n\n const [\n { NodeSDK },\n { UndiciInstrumentation },\n { RuntimeNodeInstrumentation },\n { PrometheusExporter },\n { HostMetrics },\n ] = await Promise.all([\n import('@opentelemetry/sdk-node'),\n import('@opentelemetry/instrumentation-undici'),\n import('@opentelemetry/instrumentation-runtime-node'),\n import('@opentelemetry/exporter-prometheus'),\n import('@opentelemetry/host-metrics'),\n ]);\n\n const prometheus = options.prometheus\n ? {\n host: process.env['OTEL_EXPORTER_PROMETHEUS_HOST'] ?? options.prometheus.host ?? '0.0.0.0',\n port: process.env['OTEL_EXPORTER_PROMETHEUS_PORT']\n ? Number(process.env['OTEL_EXPORTER_PROMETHEUS_PORT'])\n : (options.prometheus.port ?? 9464),\n }\n : false;\n\n const sdk = new NodeSDK({\n instrumentations: [new UndiciInstrumentation(), new RuntimeNodeInstrumentation()],\n ...(prometheus && { metricReaders: [new PrometheusExporter(prometheus)] }),\n });\n\n sdk.start();\n\n const hostMetrics = new HostMetrics();\n\n hostMetrics.start();\n\n g[TELEMETRY_KEY] = {\n shutdown: () => sdk.shutdown(),\n } satisfies TelemetryHandle;\n\n if (prometheus) {\n log.debug({ host: prometheus.host, port: prometheus.port }, 'prometheus metrics listening');\n }\n}\n\n/**\n * Flush and shut the SDK down. Prod-only (dev keeps the SDK for the process\n * lifetime across generations).\n */\nexport async function shutdownTelemetry(): Promise<void> {\n const handle = getHandle();\n\n if (!handle) return;\n\n delete (globalThis as Record<symbol, unknown>)[TELEMETRY_KEY];\n\n await handle.shutdown();\n}\n","import fs from 'node:fs';\nimport { log } from '../observability/log/index.js';\n\n/**\n * Platform env loading (position −1, before the config seam):\n * `CONFIG_PATH` → `./.env` → none. Existing process env vars win\n */\nexport function loadEnvFiles(): void {\n const configPath = process.env['CONFIG_PATH'];\n\n if (configPath) {\n process.loadEnvFile(configPath);\n\n log.debug({ path: configPath }, 'loaded env file from CONFIG_PATH');\n\n return;\n }\n\n if (fs.existsSync('.env')) {\n process.loadEnvFile('.env');\n log.debug({ path: '.env' }, 'loaded env file');\n\n return;\n }\n\n log.debug('no env file loaded');\n}\n","import { type LoggerOptionsFactory, constructRootLogger } from '../observability/log/construct.js';\nimport { type TelemetrySdkOptions, startTelemetry } from '../observability/telemetry/sdk.js';\nimport { loadEnvFiles } from './env.js';\n\nconst INSTRUMENTATION_KEY = Symbol.for('@astroscope/node/instrumentation');\n\nexport interface InstrumentationContext {\n dev: boolean;\n}\n\ninterface InstrumentationSeam {\n register?: ((ctx: InstrumentationContext) => void | Promise<void>) | undefined;\n}\n\ninterface LogSeam {\n default?: LoggerOptionsFactory | undefined;\n}\n\nexport interface PlatformSeams {\n /** `src/config.ts` — validation runs at import; a throw fails the startup */\n config?: (() => Promise<unknown>) | undefined;\n /** `src/instrumentation.ts` — extra instrumentation, once per process */\n instrumentation?: (() => Promise<InstrumentationSeam>) | undefined;\n /** `src/log.ts` — pino logger options (or a factory), never an instance */\n log?: (() => Promise<LogSeam>) | undefined;\n}\n\nexport interface PreparePlatformOptions {\n dev: boolean;\n telemetry: TelemetrySdkOptions | false;\n seams: PlatformSeams;\n}\n\n/**\n * The platform sequence in front of the boot lifecycle:\n * env → config → instrumentation (platform SDK + `register`, once per\n * process) → logger construction (after instrumentation, so entries carry\n * trace correlation). Prod runs it once in `startServer()`; dev re-runs it\n * per generation with the once-per-process parts guarded.\n */\nexport async function preparePlatform(options: PreparePlatformOptions): Promise<void> {\n loadEnvFiles();\n\n await options.seams.config?.();\n\n const g = globalThis as Record<symbol, unknown>;\n\n if (!g[INSTRUMENTATION_KEY]) {\n g[INSTRUMENTATION_KEY] = true;\n\n if (options.telemetry) {\n await startTelemetry(options.telemetry);\n }\n\n const instrumentation = await options.seams.instrumentation?.();\n\n await instrumentation?.register?.({ dev: options.dev });\n }\n\n const logSeam = await options.seams.log?.();\n\n await constructRootLogger(logSeam?.default, { dev: options.dev });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CreatePreviewServer } from "astro";
|
|
2
|
+
|
|
3
|
+
//#region src/server/preview.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `astro preview` support: imports the built server entry with autostart
|
|
6
|
+
* disabled and starts it on the preview host/port. The full production path
|
|
7
|
+
* runs — boot lifecycle, warmup, health probes, static serving.
|
|
8
|
+
*/
|
|
9
|
+
declare const createPreviewServer: CreatePreviewServer;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { createPreviewServer as default };
|
|
12
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","names":[],"sources":["../src/server/preview.ts"],"mappings":";;;;;AAAiD;;;cAQ3C,mBAAA,EAAqB,mBAe1B"}
|
package/dist/preview.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/server/preview.ts
|
|
2
|
+
/**
|
|
3
|
+
* `astro preview` support: imports the built server entry with autostart
|
|
4
|
+
* disabled and starts it on the preview host/port. The full production path
|
|
5
|
+
* runs — boot lifecycle, warmup, health probes, static serving.
|
|
6
|
+
*/
|
|
7
|
+
const createPreviewServer = async ({ serverEntrypoint, host, port }) => {
|
|
8
|
+
process.env["ASTROSCOPE_NODE_AUTOSTART"] = "disabled";
|
|
9
|
+
const handle = await (await import(serverEntrypoint.href)).startServer({
|
|
10
|
+
host,
|
|
11
|
+
port
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
host: handle.host,
|
|
15
|
+
port: handle.port,
|
|
16
|
+
stop: () => handle.stop(),
|
|
17
|
+
closed: () => handle.closed()
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { createPreviewServer as default };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","names":[],"sources":["../src/server/preview.ts"],"sourcesContent":["import type { CreatePreviewServer } from 'astro';\nimport type { ServerHandle } from './server.js';\n\n/**\n * `astro preview` support: imports the built server entry with autostart\n * disabled and starts it on the preview host/port. The full production path\n * runs — boot lifecycle, warmup, health probes, static serving.\n */\nconst createPreviewServer: CreatePreviewServer = async ({ serverEntrypoint, host, port }) => {\n process.env['ASTROSCOPE_NODE_AUTOSTART'] = 'disabled';\n\n const entry = (await import(serverEntrypoint.href)) as {\n startServer: (overrides?: { host?: string | undefined; port?: number | undefined }) => Promise<ServerHandle>;\n };\n\n const handle = await entry.startServer({ host, port });\n\n return {\n host: handle.host,\n port: handle.port,\n stop: () => handle.stop(),\n closed: () => handle.closed(),\n };\n};\n\nexport default createPreviewServer;\n"],"mappings":";;;;;;AAQA,MAAM,sBAA2C,OAAO,EAAE,kBAAkB,MAAM,WAAW;CAC3F,QAAQ,IAAI,+BAA+B;CAM3C,MAAM,SAAS,OAAM,MAJA,OAAO,iBAAiB,MAAA,CAIlB,YAAY;EAAE;EAAM;CAAK,CAAC;CAErD,OAAO;EACL,MAAM,OAAO;EACb,MAAM,OAAO;EACb,YAAY,OAAO,KAAK;EACxB,cAAc,OAAO,OAAO;CAC9B;AACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Restarting…</title>
|
|
6
|
+
<style>
|
|
7
|
+
html,
|
|
8
|
+
body {
|
|
9
|
+
height: 100%;
|
|
10
|
+
margin: 0;
|
|
11
|
+
font:
|
|
12
|
+
14px system-ui,
|
|
13
|
+
sans-serif;
|
|
14
|
+
background: #0b0d10;
|
|
15
|
+
color: #a8b1bd;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.center {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
height: 100%;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: 12px;
|
|
25
|
+
padding: 0 24px;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.message {
|
|
30
|
+
font-size: 18px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.error-title {
|
|
34
|
+
color: #ff6b6b;
|
|
35
|
+
font-size: 18px;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.error-body {
|
|
40
|
+
max-width: 800px;
|
|
41
|
+
white-space: pre-wrap;
|
|
42
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
43
|
+
font-size: 13px;
|
|
44
|
+
background: #15181d;
|
|
45
|
+
border: 1px solid #2a2f37;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
padding: 12px 16px;
|
|
48
|
+
overflow: auto;
|
|
49
|
+
max-height: 60vh;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.hint {
|
|
53
|
+
color: #6b7480;
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div id="root" class="center">
|
|
60
|
+
<p class="message">Reloading dev server…</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<script>
|
|
64
|
+
const root = document.getElementById('root');
|
|
65
|
+
const url = new URL(location.href);
|
|
66
|
+
|
|
67
|
+
function showError(message) {
|
|
68
|
+
document.title = 'Boot failed';
|
|
69
|
+
root.innerHTML = '';
|
|
70
|
+
|
|
71
|
+
const title = document.createElement('p');
|
|
72
|
+
title.className = 'error-title';
|
|
73
|
+
title.textContent = 'Boot failed';
|
|
74
|
+
|
|
75
|
+
const body = document.createElement('pre');
|
|
76
|
+
body.className = 'error-body';
|
|
77
|
+
body.textContent = message;
|
|
78
|
+
|
|
79
|
+
const hint = document.createElement('p');
|
|
80
|
+
hint.className = 'hint';
|
|
81
|
+
hint.textContent = 'Fix the error and save — this page will reload automatically.';
|
|
82
|
+
|
|
83
|
+
root.append(title, body, hint);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
(async () => {
|
|
87
|
+
for (;;) {
|
|
88
|
+
try {
|
|
89
|
+
const r = await fetch('/__astroscope_boot_ready', { cache: 'no-store' });
|
|
90
|
+
|
|
91
|
+
if (r.ok) {
|
|
92
|
+
location.replace(url.toString());
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (r.status === 503) {
|
|
97
|
+
const { error } = await r.json().catch(() => ({ error: 'Unknown error' }));
|
|
98
|
+
showError(error);
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
|
|
102
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
</script>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MiddlewareHandler } from "astro";
|
|
2
|
+
|
|
3
|
+
//#region src/observability/route-middleware-entrypoint.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Route enrichment: the native handler starts spans and request logging
|
|
6
|
+
* before routing, so the route pattern is the one thing only astro knows.
|
|
7
|
+
* Feeds it back into the request record (final log line, metrics) and the
|
|
8
|
+
* active server span. No-op outside instrumented requests.
|
|
9
|
+
*/
|
|
10
|
+
declare const onRequest: MiddlewareHandler;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { onRequest };
|
|
13
|
+
//# sourceMappingURL=route-middleware-entrypoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-middleware-entrypoint.d.ts","names":[],"sources":["../src/observability/route-middleware-entrypoint.ts"],"mappings":";;;;;AAUA;;;;cAAa,SAAA,EAAW,iBAmBvB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { n as getRequestRecord } from "./store-BIUF4lqk.js";
|
|
2
|
+
import { trace } from "@opentelemetry/api";
|
|
3
|
+
//#region src/observability/route-middleware-entrypoint.ts
|
|
4
|
+
/**
|
|
5
|
+
* Route enrichment: the native handler starts spans and request logging
|
|
6
|
+
* before routing, so the route pattern is the one thing only astro knows.
|
|
7
|
+
* Feeds it back into the request record (final log line, metrics) and the
|
|
8
|
+
* active server span. No-op outside instrumented requests.
|
|
9
|
+
*/
|
|
10
|
+
const onRequest = (ctx, next) => {
|
|
11
|
+
const record = getRequestRecord();
|
|
12
|
+
const route = ctx.routePattern;
|
|
13
|
+
if (record && !record.route) record.route = route;
|
|
14
|
+
const span = trace.getActiveSpan();
|
|
15
|
+
if (span?.isRecording() && route) {
|
|
16
|
+
span.setAttribute("http.route", route);
|
|
17
|
+
if (!record?.actionName) span.updateName(`${ctx.request.method} ${route}`);
|
|
18
|
+
}
|
|
19
|
+
return next();
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { onRequest };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=route-middleware-entrypoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-middleware-entrypoint.js","names":[],"sources":["../src/observability/route-middleware-entrypoint.ts"],"sourcesContent":["import { trace } from '@opentelemetry/api';\nimport type { MiddlewareHandler } from 'astro';\nimport { getRequestRecord } from './log/store.js';\n\n/**\n * Route enrichment: the native handler starts spans and request logging\n * before routing, so the route pattern is the one thing only astro knows.\n * Feeds it back into the request record (final log line, metrics) and the\n * active server span. No-op outside instrumented requests.\n */\nexport const onRequest: MiddlewareHandler = (ctx, next) => {\n const record = getRequestRecord();\n const route = ctx.routePattern;\n\n if (record && !record.route) {\n record.route = route;\n }\n\n const span = trace.getActiveSpan();\n\n if (span?.isRecording() && route) {\n span.setAttribute('http.route', route);\n\n if (!record?.actionName) {\n span.updateName(`${ctx.request.method} ${route}`);\n }\n }\n\n return next();\n};\n"],"mappings":";;;;;;;;;AAUA,MAAa,aAAgC,KAAK,SAAS;CACzD,MAAM,SAAS,iBAAiB;CAChC,MAAM,QAAQ,IAAI;CAElB,IAAI,UAAU,CAAC,OAAO,OACpB,OAAO,QAAQ;CAGjB,MAAM,OAAO,MAAM,cAAc;CAEjC,IAAI,MAAM,YAAY,KAAK,OAAO;EAChC,KAAK,aAAa,cAAc,KAAK;EAErC,IAAI,CAAC,QAAQ,YACX,KAAK,WAAW,GAAG,IAAI,QAAQ,OAAO,GAAG,OAAO;CAEpD;CAEA,OAAO,KAAK;AACd"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/server/server.d.ts
|
|
2
|
+
interface ServerHandle {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
stop(): Promise<void>;
|
|
6
|
+
closed(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
declare function startServer(overrides?: {
|
|
9
|
+
host?: string | undefined;
|
|
10
|
+
port?: number | undefined;
|
|
11
|
+
}): Promise<ServerHandle>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ServerHandle, startServer };
|
|
14
|
+
//# sourceMappingURL=server.d.ts.map
|