@bleedingdev/modern-js-prod-server 3.2.0-ultramodern.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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/cjs/apply.js +97 -0
  4. package/dist/cjs/index.js +89 -0
  5. package/dist/cjs/libs/contractGateAutopilot.js +36 -0
  6. package/dist/cjs/libs/loadConfig.js +72 -0
  7. package/dist/cjs/libs/metrics.js +41 -0
  8. package/dist/cjs/libs/render/index.js +125 -0
  9. package/dist/cjs/libs/render/ssr.js +118 -0
  10. package/dist/cjs/libs/render/utils.js +72 -0
  11. package/dist/cjs/libs/runtimeFallbackWorkerLane.js +167 -0
  12. package/dist/cjs/libs/telemetry.js +87 -0
  13. package/dist/cjs/netlify.js +56 -0
  14. package/dist/cjs/server/index.js +579 -0
  15. package/dist/cjs/server/modernServer.js +472 -0
  16. package/dist/cjs/server/modernServerSplit.js +38 -0
  17. package/dist/cjs/types.js +18 -0
  18. package/dist/cjs/utils.js +38 -0
  19. package/dist/esm/apply.mjs +63 -0
  20. package/dist/esm/index.mjs +26 -0
  21. package/dist/esm/libs/contractGateAutopilot.mjs +1 -0
  22. package/dist/esm/libs/loadConfig.mjs +22 -0
  23. package/dist/esm/libs/metrics.mjs +7 -0
  24. package/dist/esm/libs/render/index.mjs +81 -0
  25. package/dist/esm/libs/render/ssr.mjs +73 -0
  26. package/dist/esm/libs/render/utils.mjs +35 -0
  27. package/dist/esm/libs/runtimeFallbackWorkerLane.mjs +130 -0
  28. package/dist/esm/libs/telemetry.mjs +1 -0
  29. package/dist/esm/netlify.mjs +22 -0
  30. package/dist/esm/rslib-runtime.mjs +18 -0
  31. package/dist/esm/server/index.mjs +535 -0
  32. package/dist/esm/server/modernServer.mjs +419 -0
  33. package/dist/esm/server/modernServerSplit.mjs +4 -0
  34. package/dist/esm/types.mjs +0 -0
  35. package/dist/esm/utils.mjs +4 -0
  36. package/dist/esm-node/apply.mjs +64 -0
  37. package/dist/esm-node/index.mjs +27 -0
  38. package/dist/esm-node/libs/contractGateAutopilot.mjs +2 -0
  39. package/dist/esm-node/libs/loadConfig.mjs +23 -0
  40. package/dist/esm-node/libs/metrics.mjs +8 -0
  41. package/dist/esm-node/libs/render/index.mjs +82 -0
  42. package/dist/esm-node/libs/render/ssr.mjs +75 -0
  43. package/dist/esm-node/libs/render/utils.mjs +36 -0
  44. package/dist/esm-node/libs/runtimeFallbackWorkerLane.mjs +131 -0
  45. package/dist/esm-node/libs/telemetry.mjs +2 -0
  46. package/dist/esm-node/netlify.mjs +23 -0
  47. package/dist/esm-node/rslib-runtime.mjs +19 -0
  48. package/dist/esm-node/server/index.mjs +536 -0
  49. package/dist/esm-node/server/modernServer.mjs +421 -0
  50. package/dist/esm-node/server/modernServerSplit.mjs +5 -0
  51. package/dist/esm-node/types.mjs +1 -0
  52. package/dist/esm-node/utils.mjs +5 -0
  53. package/dist/types/apply.d.ts +6 -0
  54. package/dist/types/index.d.ts +13 -0
  55. package/dist/types/libs/metrics.d.ts +8 -0
  56. package/dist/types/libs/telemetry.d.ts +2 -0
  57. package/dist/types/netlify.d.ts +3 -0
  58. package/dist/types/types.d.ts +15 -0
  59. package/package.json +79 -0
  60. package/rslib.config.mts +4 -0
  61. package/rstest.config.mts +7 -0
@@ -0,0 +1,536 @@
1
+ import "node:module";
2
+ import { AppContext, ConfigContext, loadPlugins, serverManager } from "@modern-js/server-core";
3
+ import { INTERNAL_SERVER_PLUGINS, OUTPUT_CONFIG_FILE, SHARED_DIR, createLogger, dotenv, dotenvExpand, ensureAbsolutePath, fs } from "@modern-js/utils";
4
+ import { promises } from "fs";
5
+ import path from "path";
6
+ import { ContractGateAutopilot } from "../libs/contractGateAutopilot.mjs";
7
+ import { getServerConfigPath, loadConfig, requireConfig } from "../libs/loadConfig.mjs";
8
+ import { metrics } from "../libs/metrics.mjs";
9
+ import { DEFAULT_RUNTIME_FALLBACK_WORKER_TIMEOUT_MS, persistRuntimeFallbackContractGateInWorker } from "../libs/runtimeFallbackWorkerLane.mjs";
10
+ import { DEFAULT_RUNTIME_STATUS_ENDPOINT, TelemetryCanaryOrchestrator, TelemetryRegistry, createOtlpTelemetryExporter, createRuntimeFallbackSignalRuntimeState, createTelemetryAwareMetrics, createVictoriaMetricsTelemetryExporter, enforceRuntimeFallbackSignalAuthToken, enforceRuntimeFallbackSignalTrustPolicy, getRuntimeSignalErrorStatusCode, hasEnabledTelemetryExporters, normalizeRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackTrustPolicy, parseRuntimeFallbackSignalPayloadFromRawBody, resolveRuntimeFallbackSignalEndpoint } from "../libs/telemetry.mjs";
11
+ import { debug } from "../utils.mjs";
12
+ import { createProdServer } from "./modernServerSplit.mjs";
13
+ const CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION = 1;
14
+ const DEFAULT_RUNTIME_FALLBACK_GATE_NAME = 'runtime-mf-fallback-health';
15
+ const DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS = 300000;
16
+ const DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES = 16384;
17
+ class Server {
18
+ async init({ disableHttpServer = false } = {
19
+ disableHttpServer: false
20
+ }) {
21
+ const { options } = this;
22
+ await this.loadServerEnv(options);
23
+ this.initServerConfig(options);
24
+ await this.injectContext(this.runner, options);
25
+ this.runner = await this.createHookRunner();
26
+ await this.initConfig(this.runner, options);
27
+ await this.initTelemetry(options);
28
+ await this.injectContext(this.runner, options);
29
+ this.server = this.serverImpl(options);
30
+ await this.runPrepareHook(this.runner);
31
+ if (!disableHttpServer) this.app = await this.server.createHTTPServer(this.getRequestHandler());
32
+ {
33
+ const result = await this.runner.beforeServerInit({
34
+ app: this.app,
35
+ server: this.server
36
+ });
37
+ ({ app: this.app = this.app, server: this.server } = result);
38
+ }
39
+ await this.server.onInit(this.runner, this.app);
40
+ {
41
+ const result = await this.runner.afterServerInit({
42
+ app: this.app,
43
+ server: this.server
44
+ });
45
+ ({ app: this.app = this.app, server: this.server } = result);
46
+ }
47
+ return this;
48
+ }
49
+ runConfigHook(runner, serverConfig) {
50
+ const newServerConfig = runner.config(serverConfig || {});
51
+ return newServerConfig;
52
+ }
53
+ async runPrepareHook(runner) {
54
+ runner.prepare();
55
+ }
56
+ initServerConfig(options) {
57
+ const { pwd, serverConfigFile } = options;
58
+ const distDirectory = path.join(pwd, options.config.output.path || 'dist');
59
+ const serverConfigPath = getServerConfigPath(distDirectory, serverConfigFile);
60
+ const serverConfig = requireConfig(serverConfigPath);
61
+ this.serverConfig = serverConfig;
62
+ }
63
+ async initConfig(runner, options) {
64
+ const { pwd, config } = options;
65
+ const { serverConfig } = this;
66
+ const finalServerConfig = this.runConfigHook(runner, serverConfig);
67
+ const resolvedConfigPath = ensureAbsolutePath(pwd, path.join(config.output.path || 'dist', OUTPUT_CONFIG_FILE));
68
+ options.config = loadConfig({
69
+ cliConfig: config,
70
+ serverConfig: finalServerConfig,
71
+ resolvedConfigPath
72
+ });
73
+ }
74
+ async initTelemetry(options) {
75
+ const telemetryConfig = options.config.server?.telemetry;
76
+ if (!telemetryConfig) return;
77
+ const hasEnabledExporters = hasEnabledTelemetryExporters(telemetryConfig);
78
+ if (true !== telemetryConfig.enabled && !hasEnabledExporters) return;
79
+ const registry = new TelemetryRegistry({
80
+ service: telemetryConfig.service || options.appContext?.metaName || 'modern-js',
81
+ module: telemetryConfig.module || 'server',
82
+ environment: telemetryConfig.environment || process.env.MODERN_ENV || process.env.NODE_ENV || 'development',
83
+ samplingRate: telemetryConfig.samplingRate,
84
+ flushIntervalMs: telemetryConfig.flushIntervalMs,
85
+ maxBatchSize: telemetryConfig.maxBatchSize,
86
+ maxQueueSize: telemetryConfig.maxQueueSize,
87
+ redactionKeys: telemetryConfig.redactionKeys,
88
+ slo: {
89
+ queueUtilizationWarnThreshold: telemetryConfig.slo?.queueUtilizationWarnThreshold,
90
+ queueDroppedWarnThreshold: telemetryConfig.slo?.queueDroppedWarnThreshold,
91
+ alertCooldownMs: telemetryConfig.slo?.alertCooldownMs,
92
+ onAlert: (alert)=>{
93
+ options.logger?.warn(`[telemetry.slo] ${alert.type} threshold=${alert.threshold} value=${alert.value} depth=${alert.queueDepth}/${alert.queueCapacity} dropped=${alert.totalDropped}`);
94
+ }
95
+ }
96
+ });
97
+ if (telemetryConfig.exporters?.otlp?.enabled) await registry.register(createOtlpTelemetryExporter(telemetryConfig.exporters.otlp));
98
+ if (telemetryConfig.exporters?.victoriaMetrics?.enabled) await registry.register(createVictoriaMetricsTelemetryExporter(telemetryConfig.exporters.victoriaMetrics));
99
+ try {
100
+ await registry.startupHealthCheck({
101
+ failLoud: telemetryConfig.failLoudStartup ?? true
102
+ });
103
+ } catch (error) {
104
+ await registry.shutdown();
105
+ throw error;
106
+ }
107
+ options.metrics = createTelemetryAwareMetrics(options.metrics || metrics, registry);
108
+ this.telemetryRegistry = registry;
109
+ const canaryConfig = telemetryConfig.canary;
110
+ if (canaryConfig?.enabled) {
111
+ const contractGates = canaryConfig.contractGates;
112
+ const orchestrator = new TelemetryCanaryOrchestrator({
113
+ registry,
114
+ evaluationIntervalMs: canaryConfig.evaluationIntervalMs,
115
+ minConsecutiveHealthyEvaluations: canaryConfig.minConsecutiveHealthyEvaluations,
116
+ rollbackConsecutiveFailures: canaryConfig.rollbackConsecutiveFailures,
117
+ maxQueueUtilization: canaryConfig.maxQueueUtilization,
118
+ maxTotalDropped: canaryConfig.maxTotalDropped,
119
+ maxUnhealthyExporters: canaryConfig.maxUnhealthyExporters,
120
+ requiredContractGates: Object.keys(contractGates || {}),
121
+ onPromote: (decision)=>{
122
+ options.logger?.info(`[telemetry.canary] promoted after ${decision.consecutiveHealthy} healthy evaluations`);
123
+ this.emitCanaryDecisionMetric(registry, decision, 'promote');
124
+ },
125
+ onRollback: (decision)=>{
126
+ options.logger?.error(`[telemetry.canary] rollback triggered failures=${decision.failures.map((item)=>item.reason).join(',')}`);
127
+ this.emitCanaryDecisionMetric(registry, decision, 'rollback');
128
+ }
129
+ });
130
+ if (contractGates) orchestrator.setContractGates(contractGates);
131
+ this.canaryOrchestrator = orchestrator;
132
+ orchestrator.start();
133
+ const autopilotEnabled = canaryConfig.autopilot?.enabled ?? true;
134
+ if (autopilotEnabled) {
135
+ const gateSnapshotPath = this.resolveContractGateSnapshotPath(options, canaryConfig.autopilot?.gateSnapshotPath);
136
+ this.contractGateAutopilot = new ContractGateAutopilot({
137
+ orchestrator,
138
+ gateSnapshotPath,
139
+ pollIntervalMs: canaryConfig.autopilot?.pollIntervalMs,
140
+ gateStaleAfterMs: canaryConfig.autopilot?.gateStaleAfterMs,
141
+ logger: {
142
+ info: (message)=>{
143
+ options.logger?.info(message);
144
+ },
145
+ warn: (message)=>{
146
+ options.logger?.warn(message);
147
+ }
148
+ }
149
+ });
150
+ await this.contractGateAutopilot.start();
151
+ const runtimeSignalConfig = canaryConfig.autopilot?.runtimeFallbackSignal;
152
+ const runtimeSignalEnabled = runtimeSignalConfig?.enabled ?? true;
153
+ if (runtimeSignalEnabled) {
154
+ const workerLaneConfig = runtimeSignalConfig?.workerLane;
155
+ const workerLaneEnabledFromEnv = 'true' === process.env.MODERN_RUNTIME_FALLBACK_WORKER_LANE;
156
+ const workerLaneEnabled = 'boolean' == typeof workerLaneConfig?.enabled ? workerLaneConfig.enabled : workerLaneEnabledFromEnv;
157
+ this.runtimeFallbackSignalConfig = {
158
+ endpoint: resolveRuntimeFallbackSignalEndpoint(runtimeSignalConfig?.endpoint),
159
+ gateName: runtimeSignalConfig?.gateName?.trim() || DEFAULT_RUNTIME_FALLBACK_GATE_NAME,
160
+ gateSnapshotPath,
161
+ failureHoldMs: Math.max(1000, runtimeSignalConfig?.failureHoldMs ?? DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS),
162
+ maxBodyBytes: Math.max(512, runtimeSignalConfig?.maxBodyBytes ?? DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES),
163
+ auth: normalizeRuntimeFallbackSignalAuthConfig(runtimeSignalConfig?.auth),
164
+ trustPolicy: normalizeRuntimeFallbackTrustPolicy(runtimeSignalConfig?.trustPolicy),
165
+ runtimeState: createRuntimeFallbackSignalRuntimeState(),
166
+ workerLane: {
167
+ enabled: workerLaneEnabled,
168
+ timeoutMs: Math.max(25, workerLaneConfig?.timeoutMs ?? DEFAULT_RUNTIME_FALLBACK_WORKER_TIMEOUT_MS),
169
+ workerSuccessCount: 0,
170
+ fallbackToMainThreadCount: 0
171
+ }
172
+ };
173
+ }
174
+ }
175
+ orchestrator.evaluate();
176
+ }
177
+ }
178
+ resolveContractGateSnapshotPath(options, configuredPath) {
179
+ const rawPath = configuredPath || process.env.MODERN_CONTRACT_GATES_FILE || '.modern/contract-gates.json';
180
+ if (path.isAbsolute(rawPath)) return rawPath;
181
+ return path.resolve(options.pwd, rawPath);
182
+ }
183
+ emitCanaryDecisionMetric(registry, decision, action) {
184
+ try {
185
+ registry.enqueueMetric({
186
+ name: `telemetry.canary.${action}`,
187
+ value: 1,
188
+ unit: 'count',
189
+ tags: {
190
+ action,
191
+ state: decision.state,
192
+ failures: String(decision.failures.length)
193
+ }
194
+ });
195
+ } catch (_error) {}
196
+ }
197
+ async close() {
198
+ if (this.contractGateAutopilot) {
199
+ this.contractGateAutopilot.stop();
200
+ this.contractGateAutopilot = void 0;
201
+ }
202
+ this.runtimeFallbackSignalConfig = void 0;
203
+ if (this.canaryOrchestrator) this.canaryOrchestrator.stop();
204
+ if (this.telemetryRegistry) await this.telemetryRegistry.shutdown();
205
+ if (!this.app) return;
206
+ await new Promise((resolve)=>{
207
+ this.app.close(()=>resolve());
208
+ });
209
+ }
210
+ listen(options, listener) {
211
+ const callback = ()=>{
212
+ listener?.();
213
+ };
214
+ if ('object' == typeof options) {
215
+ if (process.env.PORT) Object.assign(options, {
216
+ port: process.env.PORT
217
+ });
218
+ this.app.listen(options, callback);
219
+ } else this.app.listen(process.env.PORT || options || 8080, callback);
220
+ }
221
+ getRequestHandler() {
222
+ const requestHandler = this.server.getRequestHandler();
223
+ return (req, res, next)=>{
224
+ if (this.shouldHandleRuntimeStatus(req)) return void this.handleRuntimeStatus(req, res);
225
+ if (this.shouldHandleRuntimeFallbackSignal(req)) return void this.handleRuntimeFallbackSignal(req, res);
226
+ return requestHandler(req, res, next);
227
+ };
228
+ }
229
+ shouldHandleRuntimeFallbackSignal(req) {
230
+ const runtimeSignalConfig = this.runtimeFallbackSignalConfig;
231
+ if (!runtimeSignalConfig) return false;
232
+ if ('POST' !== (req.method || 'GET').toUpperCase()) return false;
233
+ const pathName = this.getRequestPath(req.url);
234
+ return pathName === runtimeSignalConfig.endpoint;
235
+ }
236
+ shouldHandleRuntimeStatus(req) {
237
+ if ('GET' !== (req.method || 'GET').toUpperCase()) return false;
238
+ const pathName = this.getRequestPath(req.url);
239
+ return pathName === this.runtimeStatusEndpoint;
240
+ }
241
+ getRequestPath(urlValue) {
242
+ try {
243
+ const requestUrl = new URL(urlValue || '/', 'http://127.0.0.1');
244
+ return requestUrl.pathname;
245
+ } catch (_error) {
246
+ return '/';
247
+ }
248
+ }
249
+ async readRequestBody(req, maxBodyBytes) {
250
+ return new Promise((resolve, reject)=>{
251
+ const chunks = [];
252
+ let totalBytes = 0;
253
+ let done = false;
254
+ const cleanup = ()=>{
255
+ req.off('data', onData);
256
+ req.off('end', onEnd);
257
+ req.off('error', onError);
258
+ };
259
+ const onData = (chunk)=>{
260
+ if (done) return;
261
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
262
+ totalBytes += buffer.length;
263
+ if (totalBytes > maxBodyBytes) {
264
+ const error = new Error('runtime fallback signal payload too large');
265
+ error.code = 'PAYLOAD_TOO_LARGE';
266
+ done = true;
267
+ cleanup();
268
+ reject(error);
269
+ return;
270
+ }
271
+ chunks.push(buffer);
272
+ };
273
+ const onEnd = ()=>{
274
+ if (done) return;
275
+ done = true;
276
+ cleanup();
277
+ resolve(Buffer.concat(chunks).toString('utf8'));
278
+ };
279
+ const onError = (error)=>{
280
+ if (done) return;
281
+ done = true;
282
+ cleanup();
283
+ reject(error);
284
+ };
285
+ req.on('data', onData);
286
+ req.on('end', onEnd);
287
+ req.on('error', onError);
288
+ });
289
+ }
290
+ async handleRuntimeFallbackSignal(req, res) {
291
+ const runtimeSignalConfig = this.runtimeFallbackSignalConfig;
292
+ if (!runtimeSignalConfig) {
293
+ res.statusCode = 404;
294
+ res.end();
295
+ return;
296
+ }
297
+ try {
298
+ enforceRuntimeFallbackSignalAuthToken(this.getRequestHeader(req, runtimeSignalConfig.auth.headerName), runtimeSignalConfig.auth);
299
+ const rawBody = await this.readRequestBody(req, runtimeSignalConfig.maxBodyBytes);
300
+ const payload = parseRuntimeFallbackSignalPayloadFromRawBody(rawBody, runtimeSignalConfig.maxBodyBytes);
301
+ const trustResult = enforceRuntimeFallbackSignalTrustPolicy(payload, {
302
+ trustPolicy: runtimeSignalConfig.trustPolicy,
303
+ runtimeState: runtimeSignalConfig.runtimeState
304
+ });
305
+ if (trustResult.deduped) {
306
+ res.statusCode = 202;
307
+ res.setHeader('content-type', 'application/json');
308
+ res.end('{"ok":true,"deduped":true}');
309
+ return;
310
+ }
311
+ let persistedByWorkerLane = false;
312
+ if (runtimeSignalConfig.workerLane.enabled) {
313
+ const workerResult = await persistRuntimeFallbackContractGateInWorker({
314
+ snapshotPath: runtimeSignalConfig.gateSnapshotPath,
315
+ gateName: runtimeSignalConfig.gateName,
316
+ failureHoldMs: runtimeSignalConfig.failureHoldMs,
317
+ payload: payload,
318
+ schemaVersion: CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION
319
+ }, {
320
+ enabled: true,
321
+ timeoutMs: runtimeSignalConfig.workerLane.timeoutMs
322
+ });
323
+ if (workerResult.ok) {
324
+ persistedByWorkerLane = true;
325
+ runtimeSignalConfig.workerLane.workerSuccessCount += 1;
326
+ runtimeSignalConfig.workerLane.lastError = void 0;
327
+ const payloadRecord = payload;
328
+ const reason = 'string' == typeof payloadRecord.reason ? payloadRecord.reason : 'runtime_fallback';
329
+ const phase = 'string' == typeof payloadRecord.phase ? payloadRecord.phase : 'unknown';
330
+ const appName = 'string' == typeof payloadRecord.appName ? payloadRecord.appName : 'unknown';
331
+ this.options.logger?.warn(`[telemetry.canary.autopilot] runtime fallback signal gate=${runtimeSignalConfig.gateName} reason=${reason} phase=${phase} app=${appName} workerLane=true`);
332
+ } else {
333
+ runtimeSignalConfig.workerLane.fallbackToMainThreadCount += 1;
334
+ runtimeSignalConfig.workerLane.lastError = workerResult.error;
335
+ this.options.logger?.warn(`[telemetry.canary.autopilot] runtime fallback worker lane fallback: ${workerResult.error || 'unknown_error'}`);
336
+ }
337
+ }
338
+ if (!persistedByWorkerLane) await this.persistRuntimeFallbackContractGate(payload, runtimeSignalConfig);
339
+ res.statusCode = 202;
340
+ res.setHeader('content-type', 'application/json');
341
+ res.end('{"ok":true}');
342
+ } catch (error) {
343
+ const signalError = error;
344
+ res.statusCode = getRuntimeSignalErrorStatusCode(signalError);
345
+ res.setHeader('content-type', 'application/json');
346
+ res.end(`{"ok":false,"error":${JSON.stringify(signalError instanceof Error ? signalError.message : String(signalError))}}`);
347
+ this.options.logger?.warn(`[telemetry.canary.autopilot] runtime fallback signal rejected: ${error instanceof Error ? error.message : String(error)}`);
348
+ }
349
+ }
350
+ async handleRuntimeStatus(req, res) {
351
+ try {
352
+ if (this.runtimeFallbackSignalConfig?.auth.enabled) enforceRuntimeFallbackSignalAuthToken(this.getRequestHeader(req, this.runtimeFallbackSignalConfig.auth.headerName), this.runtimeFallbackSignalConfig.auth);
353
+ res.statusCode = 200;
354
+ res.setHeader('content-type', 'application/json');
355
+ res.end(JSON.stringify(this.buildRuntimeStatusPayload()));
356
+ } catch (error) {
357
+ const signalError = error;
358
+ res.statusCode = getRuntimeSignalErrorStatusCode(signalError);
359
+ res.setHeader('content-type', 'application/json');
360
+ res.end(`{"ok":false,"error":${JSON.stringify(signalError instanceof Error ? signalError.message : String(signalError))}}`);
361
+ }
362
+ }
363
+ buildRuntimeStatusPayload() {
364
+ const telemetry = this.telemetryRegistry ? {
365
+ enabled: true,
366
+ queueStats: this.telemetryRegistry.getQueueStats(),
367
+ exporterHealth: this.telemetryRegistry.getExporterHealth()
368
+ } : {
369
+ enabled: false,
370
+ queueStats: null,
371
+ exporterHealth: []
372
+ };
373
+ const canary = this.canaryOrchestrator ? {
374
+ enabled: true,
375
+ ...this.canaryOrchestrator.getStatusSnapshot()
376
+ } : {
377
+ enabled: false
378
+ };
379
+ const runtimeFallbackSignal = this.runtimeFallbackSignalConfig ? {
380
+ enabled: true,
381
+ endpoint: this.runtimeFallbackSignalConfig.endpoint,
382
+ gateName: this.runtimeFallbackSignalConfig.gateName,
383
+ failureHoldMs: this.runtimeFallbackSignalConfig.failureHoldMs,
384
+ maxBodyBytes: this.runtimeFallbackSignalConfig.maxBodyBytes,
385
+ auth: {
386
+ enabled: this.runtimeFallbackSignalConfig.auth.enabled,
387
+ headerName: this.runtimeFallbackSignalConfig.auth.headerName
388
+ },
389
+ trustPolicy: {
390
+ allowedApps: this.runtimeFallbackSignalConfig.trustPolicy.allowedApps,
391
+ allowedEntryOrigins: this.runtimeFallbackSignalConfig.trustPolicy.allowedEntryOrigins,
392
+ enforceRuntimeDigest: this.runtimeFallbackSignalConfig.trustPolicy.enforceRuntimeDigest,
393
+ expectedRuntimeDigestsCount: Object.keys(this.runtimeFallbackSignalConfig.trustPolicy.expectedRuntimeDigests).length,
394
+ maxSignalsPerWindow: this.runtimeFallbackSignalConfig.trustPolicy.maxSignalsPerWindow,
395
+ windowMs: this.runtimeFallbackSignalConfig.trustPolicy.windowMs,
396
+ dedupeWindowMs: this.runtimeFallbackSignalConfig.trustPolicy.dedupeWindowMs
397
+ },
398
+ workerLane: {
399
+ enabled: this.runtimeFallbackSignalConfig.workerLane.enabled,
400
+ timeoutMs: this.runtimeFallbackSignalConfig.workerLane.timeoutMs,
401
+ workerSuccessCount: this.runtimeFallbackSignalConfig.workerLane.workerSuccessCount,
402
+ fallbackToMainThreadCount: this.runtimeFallbackSignalConfig.workerLane.fallbackToMainThreadCount,
403
+ lastError: this.runtimeFallbackSignalConfig.workerLane.lastError
404
+ }
405
+ } : {
406
+ enabled: false
407
+ };
408
+ return {
409
+ ok: true,
410
+ timestamp: Date.now(),
411
+ telemetry,
412
+ canary,
413
+ runtimeFallbackSignal
414
+ };
415
+ }
416
+ getRequestHeader(req, headerName) {
417
+ const raw = req.headers[headerName.toLowerCase()];
418
+ if (Array.isArray(raw)) return raw[0];
419
+ if ('string' == typeof raw) return raw;
420
+ }
421
+ async persistRuntimeFallbackContractGate(payload, runtimeSignalConfig) {
422
+ const now = Date.now();
423
+ const snapshotPath = runtimeSignalConfig.gateSnapshotPath;
424
+ let snapshot = {
425
+ schemaVersion: CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION,
426
+ updatedAt: now,
427
+ gates: {}
428
+ };
429
+ if (await fs.pathExists(snapshotPath)) try {
430
+ const raw = await promises.readFile(snapshotPath, 'utf8');
431
+ const parsed = JSON.parse(raw);
432
+ if (parsed && 'object' == typeof parsed) snapshot = {
433
+ schemaVersion: 'number' == typeof parsed.schemaVersion ? parsed.schemaVersion : CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION,
434
+ updatedAt: 'number' == typeof parsed.updatedAt ? parsed.updatedAt : now,
435
+ gates: parsed.gates && 'object' == typeof parsed.gates ? parsed.gates : {}
436
+ };
437
+ } catch (_error) {
438
+ snapshot = {
439
+ schemaVersion: CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION,
440
+ updatedAt: now,
441
+ gates: {}
442
+ };
443
+ }
444
+ const reason = 'string' == typeof payload.reason ? payload.reason : 'runtime_fallback';
445
+ const phase = 'string' == typeof payload.phase ? payload.phase : 'unknown';
446
+ const appName = 'string' == typeof payload.appName ? payload.appName : 'unknown';
447
+ const entry = 'string' == typeof payload.entry ? payload.entry : void 0;
448
+ snapshot.schemaVersion = CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION;
449
+ snapshot.updatedAt = now;
450
+ snapshot.gates = snapshot.gates || {};
451
+ snapshot.gates[runtimeSignalConfig.gateName] = {
452
+ passed: false,
453
+ reason: `runtime_fallback:${reason} phase=${phase} app=${appName}${entry ? ` entry=${entry}` : ''}`,
454
+ updatedAt: now,
455
+ expiresAt: now + runtimeSignalConfig.failureHoldMs,
456
+ source: 'runtime-mf-fallback-signal',
457
+ metadata: payload
458
+ };
459
+ await promises.mkdir(path.dirname(snapshotPath), {
460
+ recursive: true
461
+ });
462
+ await promises.writeFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`);
463
+ this.options.logger?.warn(`[telemetry.canary.autopilot] runtime fallback signal gate=${runtimeSignalConfig.gateName} reason=${reason} phase=${phase} app=${appName}`);
464
+ }
465
+ async render(req, res, url) {
466
+ return this.server.render(req, res, url);
467
+ }
468
+ async createHookRunner() {
469
+ serverManager.clear();
470
+ const { options } = this;
471
+ const { internalPlugins = INTERNAL_SERVER_PLUGINS, pwd, plugins = [] } = options;
472
+ const serverPlugins = this.serverConfig.plugins || [];
473
+ const loadedPlugins = loadPlugins(pwd, [
474
+ ...serverPlugins,
475
+ ...plugins
476
+ ], {
477
+ internalPlugins
478
+ });
479
+ debug('plugins', loadedPlugins);
480
+ loadedPlugins.forEach((p)=>{
481
+ serverManager.usePlugin(p);
482
+ });
483
+ const hooksRunner = await serverManager.init();
484
+ return hooksRunner;
485
+ }
486
+ async injectContext(runner, options) {
487
+ const appContext = this.initAppContext();
488
+ const { config, pwd } = options;
489
+ ConfigContext.set(config);
490
+ AppContext.set({
491
+ ...appContext,
492
+ distDirectory: path.join(pwd, config.output.path || 'dist')
493
+ });
494
+ }
495
+ initAppContext() {
496
+ const { options } = this;
497
+ const { pwd: appDirectory, plugins = [], config, appContext } = options;
498
+ const serverPlugins = plugins.map((p)=>({
499
+ server: p
500
+ }));
501
+ return {
502
+ appDirectory,
503
+ apiDirectory: appContext?.apiDirectory,
504
+ lambdaDirectory: appContext?.lambdaDirectory,
505
+ sharedDirectory: appContext?.sharedDirectory || path.resolve(appDirectory, SHARED_DIR),
506
+ distDirectory: path.join(appDirectory, config.output.path || 'dist'),
507
+ plugins: serverPlugins
508
+ };
509
+ }
510
+ async loadServerEnv(options) {
511
+ const { pwd: appDirectory } = options;
512
+ const serverEnv = process.env.MODERN_ENV;
513
+ const defaultEnvPath = path.resolve(appDirectory, ".env");
514
+ const serverEnvPath = path.resolve(appDirectory, `.env.${serverEnv}`);
515
+ for (const envPath of [
516
+ serverEnvPath,
517
+ defaultEnvPath
518
+ ])if (await fs.pathExists(envPath) && !(await fs.stat(envPath)).isDirectory()) {
519
+ const envConfig = dotenv.config({
520
+ path: envPath
521
+ });
522
+ dotenvExpand(envConfig);
523
+ }
524
+ }
525
+ constructor(options){
526
+ this.serverImpl = createProdServer;
527
+ this.runtimeStatusEndpoint = DEFAULT_RUNTIME_STATUS_ENDPOINT;
528
+ options.logger = options.logger || createLogger({
529
+ level: 'warn'
530
+ });
531
+ options.metrics = options.metrics || metrics;
532
+ this.options = options;
533
+ this.serverConfig = {};
534
+ }
535
+ }
536
+ export { Server };