@taujs/server 0.4.0 → 0.4.2

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/dist/index.js CHANGED
@@ -24,6 +24,79 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  mod
25
25
  ));
26
26
 
27
+ // node_modules/picocolors/picocolors.js
28
+ var require_picocolors = __commonJS({
29
+ "node_modules/picocolors/picocolors.js"(exports, module) {
30
+ "use strict";
31
+ var p = process || {};
32
+ var argv = p.argv || [];
33
+ var env = p.env || {};
34
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
35
+ var formatter = (open, close, replace = open) => (input) => {
36
+ let string = "" + input, index = string.indexOf(close, open.length);
37
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
38
+ };
39
+ var replaceClose = (string, close, replace, index) => {
40
+ let result = "", cursor = 0;
41
+ do {
42
+ result += string.substring(cursor, index) + replace;
43
+ cursor = index + close.length;
44
+ index = string.indexOf(close, cursor);
45
+ } while (~index);
46
+ return result + string.substring(cursor);
47
+ };
48
+ var createColors = (enabled = isColorSupported) => {
49
+ let f = enabled ? formatter : () => String;
50
+ return {
51
+ isColorSupported: enabled,
52
+ reset: f("\x1B[0m", "\x1B[0m"),
53
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
54
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
55
+ italic: f("\x1B[3m", "\x1B[23m"),
56
+ underline: f("\x1B[4m", "\x1B[24m"),
57
+ inverse: f("\x1B[7m", "\x1B[27m"),
58
+ hidden: f("\x1B[8m", "\x1B[28m"),
59
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
60
+ black: f("\x1B[30m", "\x1B[39m"),
61
+ red: f("\x1B[31m", "\x1B[39m"),
62
+ green: f("\x1B[32m", "\x1B[39m"),
63
+ yellow: f("\x1B[33m", "\x1B[39m"),
64
+ blue: f("\x1B[34m", "\x1B[39m"),
65
+ magenta: f("\x1B[35m", "\x1B[39m"),
66
+ cyan: f("\x1B[36m", "\x1B[39m"),
67
+ white: f("\x1B[37m", "\x1B[39m"),
68
+ gray: f("\x1B[90m", "\x1B[39m"),
69
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
70
+ bgRed: f("\x1B[41m", "\x1B[49m"),
71
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
72
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
73
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
74
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
75
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
76
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
77
+ blackBright: f("\x1B[90m", "\x1B[39m"),
78
+ redBright: f("\x1B[91m", "\x1B[39m"),
79
+ greenBright: f("\x1B[92m", "\x1B[39m"),
80
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
81
+ blueBright: f("\x1B[94m", "\x1B[39m"),
82
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
83
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
84
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
85
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
86
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
87
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
88
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
89
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
90
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
91
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
92
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
93
+ };
94
+ };
95
+ module.exports = createColors();
96
+ module.exports.createColors = createColors;
97
+ }
98
+ });
99
+
27
100
  // node_modules/fastify-plugin/lib/getPluginName.js
28
101
  var require_getPluginName = __commonJS({
29
102
  "node_modules/fastify-plugin/lib/getPluginName.js"(exports, module) {
@@ -114,84 +187,15 @@ var require_plugin = __commonJS({
114
187
  }
115
188
  });
116
189
 
117
- // node_modules/picocolors/picocolors.js
118
- var require_picocolors = __commonJS({
119
- "node_modules/picocolors/picocolors.js"(exports, module) {
120
- "use strict";
121
- var p = process || {};
122
- var argv = p.argv || [];
123
- var env = p.env || {};
124
- var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
125
- var formatter = (open, close, replace = open) => (input) => {
126
- let string = "" + input, index = string.indexOf(close, open.length);
127
- return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
128
- };
129
- var replaceClose = (string, close, replace, index) => {
130
- let result = "", cursor = 0;
131
- do {
132
- result += string.substring(cursor, index) + replace;
133
- cursor = index + close.length;
134
- index = string.indexOf(close, cursor);
135
- } while (~index);
136
- return result + string.substring(cursor);
137
- };
138
- var createColors = (enabled = isColorSupported) => {
139
- let f = enabled ? formatter : () => String;
140
- return {
141
- isColorSupported: enabled,
142
- reset: f("\x1B[0m", "\x1B[0m"),
143
- bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
144
- dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
145
- italic: f("\x1B[3m", "\x1B[23m"),
146
- underline: f("\x1B[4m", "\x1B[24m"),
147
- inverse: f("\x1B[7m", "\x1B[27m"),
148
- hidden: f("\x1B[8m", "\x1B[28m"),
149
- strikethrough: f("\x1B[9m", "\x1B[29m"),
150
- black: f("\x1B[30m", "\x1B[39m"),
151
- red: f("\x1B[31m", "\x1B[39m"),
152
- green: f("\x1B[32m", "\x1B[39m"),
153
- yellow: f("\x1B[33m", "\x1B[39m"),
154
- blue: f("\x1B[34m", "\x1B[39m"),
155
- magenta: f("\x1B[35m", "\x1B[39m"),
156
- cyan: f("\x1B[36m", "\x1B[39m"),
157
- white: f("\x1B[37m", "\x1B[39m"),
158
- gray: f("\x1B[90m", "\x1B[39m"),
159
- bgBlack: f("\x1B[40m", "\x1B[49m"),
160
- bgRed: f("\x1B[41m", "\x1B[49m"),
161
- bgGreen: f("\x1B[42m", "\x1B[49m"),
162
- bgYellow: f("\x1B[43m", "\x1B[49m"),
163
- bgBlue: f("\x1B[44m", "\x1B[49m"),
164
- bgMagenta: f("\x1B[45m", "\x1B[49m"),
165
- bgCyan: f("\x1B[46m", "\x1B[49m"),
166
- bgWhite: f("\x1B[47m", "\x1B[49m"),
167
- blackBright: f("\x1B[90m", "\x1B[39m"),
168
- redBright: f("\x1B[91m", "\x1B[39m"),
169
- greenBright: f("\x1B[92m", "\x1B[39m"),
170
- yellowBright: f("\x1B[93m", "\x1B[39m"),
171
- blueBright: f("\x1B[94m", "\x1B[39m"),
172
- magentaBright: f("\x1B[95m", "\x1B[39m"),
173
- cyanBright: f("\x1B[96m", "\x1B[39m"),
174
- whiteBright: f("\x1B[97m", "\x1B[39m"),
175
- bgBlackBright: f("\x1B[100m", "\x1B[49m"),
176
- bgRedBright: f("\x1B[101m", "\x1B[49m"),
177
- bgGreenBright: f("\x1B[102m", "\x1B[49m"),
178
- bgYellowBright: f("\x1B[103m", "\x1B[49m"),
179
- bgBlueBright: f("\x1B[104m", "\x1B[49m"),
180
- bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
181
- bgCyanBright: f("\x1B[106m", "\x1B[49m"),
182
- bgWhiteBright: f("\x1B[107m", "\x1B[49m")
183
- };
184
- };
185
- module.exports = createColors();
186
- module.exports.createColors = createColors;
187
- }
188
- });
189
-
190
- // src/fastify.d.ts
191
- import "fastify";
190
+ // src/CreateServer.ts
191
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
192
+ import path5 from "path";
193
+ import { performance as performance3 } from "perf_hooks";
194
+ import fastifyStatic from "@fastify/static";
195
+ import Fastify from "fastify";
192
196
 
193
- // src/SSRServer.ts
194
- var import_fastify_plugin3 = __toESM(require_plugin(), 1);
197
+ // src/Setup.ts
198
+ import { performance } from "perf_hooks";
195
199
 
196
200
  // src/constants.ts
197
201
  var import_picocolors = __toESM(require_picocolors(), 1);
@@ -231,6 +235,119 @@ var REGEX = {
231
235
  SAFE_TRACE: /^[a-zA-Z0-9-_:.]{1,128}$/
232
236
  };
233
237
 
238
+ // src/Setup.ts
239
+ var extractBuildConfigs = (config) => {
240
+ return config.apps.map(({ appId, entryPoint, plugins }) => ({
241
+ appId,
242
+ entryPoint,
243
+ plugins
244
+ }));
245
+ };
246
+ var extractRoutes = (taujsConfig) => {
247
+ const t0 = performance.now();
248
+ const allRoutes = [];
249
+ const apps = [];
250
+ const warnings = [];
251
+ const pathTracker = /* @__PURE__ */ new Map();
252
+ for (const app of taujsConfig.apps) {
253
+ const appRoutes = (app.routes ?? []).map((route) => {
254
+ const fullRoute = { ...route, appId: app.appId };
255
+ if (!pathTracker.has(route.path)) pathTracker.set(route.path, []);
256
+ pathTracker.get(route.path).push(app.appId);
257
+ return fullRoute;
258
+ });
259
+ apps.push({ appId: app.appId, routeCount: appRoutes.length });
260
+ allRoutes.push(...appRoutes);
261
+ }
262
+ for (const [path7, appIds] of pathTracker.entries()) {
263
+ if (appIds.length > 1) warnings.push(`Route path "${path7}" is declared in multiple apps: ${appIds.join(", ")}`);
264
+ }
265
+ const sortedRoutes = allRoutes.sort((a, b) => computeScore(b.path) - computeScore(a.path));
266
+ const durationMs = performance.now() - t0;
267
+ return {
268
+ routes: sortedRoutes,
269
+ apps,
270
+ totalRoutes: allRoutes.length,
271
+ durationMs,
272
+ warnings
273
+ };
274
+ };
275
+ var extractSecurity = (taujsConfig) => {
276
+ const t0 = performance.now();
277
+ const user = taujsConfig.security ?? {};
278
+ const userCsp = user.csp;
279
+ const hasExplicitCSP = !!userCsp;
280
+ const normalisedCsp = userCsp ? {
281
+ defaultMode: userCsp.defaultMode ?? "merge",
282
+ directives: userCsp.directives,
283
+ generateCSP: userCsp.generateCSP,
284
+ reporting: userCsp.reporting ? {
285
+ endpoint: userCsp.reporting.endpoint,
286
+ onViolation: userCsp.reporting.onViolation,
287
+ reportOnly: userCsp.reporting.reportOnly ?? false
288
+ } : void 0
289
+ } : void 0;
290
+ const security = { csp: normalisedCsp };
291
+ const summary = {
292
+ mode: hasExplicitCSP ? "explicit" : "dev-defaults",
293
+ defaultMode: normalisedCsp?.defaultMode ?? "merge",
294
+ hasReporting: !!normalisedCsp?.reporting?.endpoint,
295
+ reportOnly: !!normalisedCsp?.reporting?.reportOnly
296
+ };
297
+ const durationMs = performance.now() - t0;
298
+ return {
299
+ security,
300
+ durationMs,
301
+ hasExplicitCSP,
302
+ summary
303
+ };
304
+ };
305
+ function printConfigSummary(logger, apps, configsCount, totalRoutes, durationMs, warnings) {
306
+ logger.info({}, `${CONTENT.TAG} [config] Loaded ${configsCount} app(s), ${totalRoutes} route(s) in ${durationMs.toFixed(1)}ms`);
307
+ apps.forEach((a) => logger.debug("routes", {}, `\u2022 ${a.appId}: ${a.routeCount} route(s)`));
308
+ warnings.forEach((w) => logger.warn({}, `${CONTENT.TAG} [warn] ${w}`));
309
+ }
310
+ function printSecuritySummary(logger, routes, security, hasExplicitCSP, securityDurationMs) {
311
+ const total = routes.length;
312
+ const disabled = routes.filter((r) => r.attr?.middleware?.csp === false).length;
313
+ const custom = routes.filter((r) => {
314
+ const v = r.attr?.middleware?.csp;
315
+ return v !== void 0 && v !== false;
316
+ }).length;
317
+ const enabled = total - disabled;
318
+ const hasReporting = !!security.csp?.reporting?.endpoint;
319
+ const mode = security.csp?.defaultMode ?? "merge";
320
+ let status = "configured";
321
+ let detail = "";
322
+ if (hasExplicitCSP) {
323
+ detail = `explicit, mode=${mode}`;
324
+ if (hasReporting) detail += ", reporting";
325
+ if (custom > 0) detail += `, ${custom} route override(s)`;
326
+ } else {
327
+ if (process.env.NODE_ENV === "production") {
328
+ logger.warn({}, "(consider explicit config for production)");
329
+ }
330
+ }
331
+ logger.info({}, `${CONTENT.TAG} [security] CSP ${status} (${enabled}/${total} routes) in ${securityDurationMs.toFixed(1)}ms`);
332
+ }
333
+ function printContractReport(logger, report) {
334
+ for (const r of report.items) {
335
+ const line = `${CONTENT.TAG} [security][${r.key}] ${r.message}`;
336
+ if (r.status === "error") {
337
+ logger.error({}, line);
338
+ } else if (r.status === "warning") {
339
+ logger.warn({}, line);
340
+ } else if (r.status === "skipped") {
341
+ logger.debug(r.key, {}, line);
342
+ } else {
343
+ logger.info({}, line);
344
+ }
345
+ }
346
+ }
347
+ var computeScore = (path7) => {
348
+ return path7.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
349
+ };
350
+
234
351
  // src/logging/AppError.ts
235
352
  var HTTP_STATUS = {
236
353
  infra: 500,
@@ -355,202 +472,6 @@ function toReason(e) {
355
472
  return new Error(String(e));
356
473
  }
357
474
 
358
- // src/logging/utils/index.ts
359
- var httpStatusFrom = (err, fallback = 500) => err instanceof AppError ? err.httpStatus : fallback;
360
- var toHttp = (err) => {
361
- const app = AppError.from(err);
362
- const status = httpStatusFrom(app);
363
- const errorMessage = app.safeMessage;
364
- return {
365
- status,
366
- body: {
367
- error: errorMessage,
368
- ...app.code && { code: app.code },
369
- statusText: statusText(status)
370
- }
371
- };
372
- };
373
- var statusText = (status) => {
374
- const map = {
375
- 400: "Bad Request",
376
- 401: "Unauthorized",
377
- 403: "Forbidden",
378
- 404: "Not Found",
379
- 405: "Method Not Allowed",
380
- 408: "Request Timeout",
381
- 422: "Unprocessable Entity",
382
- 429: "Too Many Requests",
383
- 499: "Client Closed Request",
384
- 500: "Internal Server Error",
385
- 502: "Bad Gateway",
386
- 503: "Service Unavailable",
387
- 504: "Gateway Timeout"
388
- };
389
- return map[status] ?? "Error";
390
- };
391
-
392
- // src/utils/DataRoutes.ts
393
- import { match } from "path-to-regexp";
394
-
395
- // src/utils/DataServices.ts
396
- async function callServiceMethod(registry, serviceName, methodName, params, ctx) {
397
- if (ctx.signal?.aborted) throw AppError.timeout("Request canceled");
398
- const service = registry[serviceName];
399
- if (!service) throw AppError.notFound(`Unknown service: ${serviceName}`);
400
- const desc = service[methodName];
401
- if (!desc) throw AppError.notFound(`Unknown method: ${serviceName}.${methodName}`);
402
- const logger = ctx.logger?.child({
403
- component: "service-call",
404
- service: serviceName,
405
- method: methodName,
406
- traceId: ctx.traceId
407
- });
408
- try {
409
- const p = desc.parsers?.params ? desc.parsers.params(params) : params;
410
- const data = await desc.handler(p, ctx);
411
- const out = desc.parsers?.result ? desc.parsers.result(data) : data;
412
- if (typeof out !== "object" || out === null) throw AppError.internal(`Non-object result from ${serviceName}.${methodName}`);
413
- return out;
414
- } catch (err) {
415
- logger?.error("Service method failed", {
416
- params,
417
- error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err)
418
- });
419
- throw err;
420
- }
421
- }
422
- var isServiceDescriptor = (obj) => {
423
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
424
- const maybe = obj;
425
- return typeof maybe.serviceName === "string" && typeof maybe.serviceMethod === "string";
426
- };
427
-
428
- // src/utils/DataRoutes.ts
429
- var safeDecode = (value) => {
430
- try {
431
- return decodeURIComponent(value);
432
- } catch {
433
- return value;
434
- }
435
- };
436
- var cleanPath = (path6) => {
437
- if (!path6) return "/";
438
- const basePart = path6.split("?")[0];
439
- const base = basePart ? basePart.split("#")[0] : "/";
440
- return base || "/";
441
- };
442
- var calculateSpecificity = (path6) => {
443
- let score = 0;
444
- const segments = path6.split("/").filter(Boolean);
445
- for (const segment of segments) {
446
- if (segment.startsWith(":")) {
447
- score += 1;
448
- if (/[?+*]$/.test(segment)) score -= 0.5;
449
- } else if (segment === "*") {
450
- score += 0.1;
451
- } else {
452
- score += 10;
453
- }
454
- }
455
- score += segments.length * 0.1;
456
- return score;
457
- };
458
- var isPlainObject = (v) => !!v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
459
- var createRouteMatchers = (routes) => {
460
- const sortedRoutes = [...routes].sort((a, b) => calculateSpecificity(b.path) - calculateSpecificity(a.path));
461
- return sortedRoutes.map((route) => {
462
- const matcher = match(route.path, { decode: safeDecode });
463
- const specificity = calculateSpecificity(route.path);
464
- const keys = [];
465
- return { route, matcher, keys, specificity };
466
- });
467
- };
468
- var matchRoute = (url, routeMatchers) => {
469
- const path6 = cleanPath(url);
470
- for (const { route, matcher, keys } of routeMatchers) {
471
- const match2 = matcher(path6);
472
- if (match2) {
473
- return {
474
- route,
475
- params: match2.params,
476
- keys
477
- };
478
- }
479
- }
480
- return null;
481
- };
482
- var fetchInitialData = async (attr, params, serviceRegistry, ctx, callServiceMethodImpl = callServiceMethod) => {
483
- const dataHandler = attr?.data;
484
- if (!dataHandler || typeof dataHandler !== "function") return {};
485
- try {
486
- const result = await dataHandler(params, {
487
- ...ctx,
488
- headers: ctx.headers ?? {}
489
- });
490
- if (isServiceDescriptor(result)) {
491
- const { serviceName, serviceMethod, args } = result;
492
- return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctx);
493
- }
494
- if (isPlainObject(result)) return result;
495
- throw AppError.badRequest("attr.data must return a plain object or a ServiceDescriptor");
496
- } catch (err) {
497
- const e = AppError.from(err);
498
- const level = e.kind === "domain" || e.kind === "validation" || e.kind === "auth" ? "warn" : "error";
499
- ctx.logger?.[level](e.message, {
500
- component: "fetch-initial-data",
501
- kind: e.kind,
502
- httpStatus: e.httpStatus,
503
- ...e.code && { code: e.code },
504
- details: e.details,
505
- params,
506
- traceId: ctx.traceId
507
- });
508
- throw e;
509
- }
510
- };
511
-
512
- // src/security/Auth.ts
513
- var createAuthHook = (routeMatchers, logger) => {
514
- return async function authHook(req, reply) {
515
- const url = new URL(req.url, `http://${req.headers.host}`).pathname;
516
- const match2 = matchRoute(url, routeMatchers);
517
- if (!match2) return;
518
- const { route } = match2;
519
- const authConfig = route.attr?.middleware?.auth;
520
- if (!authConfig) {
521
- logger.debug("auth", "(none)", { method: req.method, url: req.url });
522
- return;
523
- }
524
- if (typeof req.server.authenticate !== "function") {
525
- logger.warn("Route requires auth but Fastify authenticate decorator is missing", {
526
- path: url,
527
- appId: route.appId
528
- });
529
- return reply.status(500).send("Server misconfiguration: auth decorator missing.");
530
- }
531
- try {
532
- logger.debug("auth", "Invoking authenticate(...)", { method: req.method, url: req.url });
533
- await req.server.authenticate(req, reply);
534
- logger.debug("auth", "Authentication successful", { method: req.method, url: req.url });
535
- } catch (err) {
536
- logger.debug("auth", "Authentication failed", { method: req.method, url: req.url });
537
- return reply.send(err);
538
- }
539
- };
540
- };
541
-
542
- // src/security/CSP.ts
543
- var import_fastify_plugin = __toESM(require_plugin(), 1);
544
- import crypto from "crypto";
545
-
546
- // src/utils/System.ts
547
- import { dirname, join } from "path";
548
- import "path";
549
- import { fileURLToPath } from "url";
550
- var isDevelopment = process.env.NODE_ENV === "development";
551
- var __filename = fileURLToPath(import.meta.url);
552
- var __dirname = join(dirname(__filename), !isDevelopment ? "./" : "..");
553
-
554
475
  // src/logging/Logger.ts
555
476
  var import_picocolors2 = __toESM(require_picocolors(), 1);
556
477
 
@@ -625,10 +546,8 @@ var Logger = class _Logger {
625
546
  debugEnabled = /* @__PURE__ */ new Set();
626
547
  context = {};
627
548
  child(context) {
628
- const child = new _Logger({
629
- ...this.config,
630
- context: { ...this.context, ...context }
631
- });
549
+ const customChild = this.config.custom?.child?.(context) ?? this.config.custom;
550
+ const child = new _Logger({ ...this.config, custom: customChild, context: { ...this.context, ...context } });
632
551
  child.debugEnabled = new Set(this.debugEnabled);
633
552
  return child;
634
553
  }
@@ -690,54 +609,55 @@ var Logger = class _Logger {
690
609
  if (!this.shouldEmit(level)) return;
691
610
  const timestamp = this.formatTimestamp();
692
611
  const wantCtx = this.config.includeContext === void 0 ? false : typeof this.config.includeContext === "function" ? this.config.includeContext(level) : this.config.includeContext;
693
- const customSink = this.config.custom?.[level] ?? (level === "debug" ? this.config.custom?.info : void 0) ?? this.config.custom?.log;
612
+ const customSink = this.config.custom?.[level];
694
613
  const consoleFallback = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
695
614
  const sink = customSink ?? consoleFallback;
696
615
  const merged = meta ?? {};
697
616
  const withCtx = wantCtx && Object.keys(this.context).length > 0 ? { context: this.context, ...merged } : merged;
698
617
  const finalMeta = this.shouldIncludeStack(level) ? withCtx : this.stripStacks(withCtx);
699
618
  const hasMeta = finalMeta && typeof finalMeta === "object" ? Object.keys(finalMeta).length > 0 : false;
700
- const coloredLevel = (() => {
701
- const levelText = level.toLowerCase() + (category ? `:${category.toLowerCase()}` : "");
619
+ const levelText = level.toLowerCase() + (category ? `:${category.toLowerCase()}` : "");
620
+ const plainTag = `[${levelText}]`;
621
+ const coloredTag = (() => {
702
622
  switch (level) {
703
623
  case "debug":
704
- return import_picocolors2.default.gray(`[${levelText}]`);
624
+ return import_picocolors2.default.gray(plainTag);
705
625
  case "info":
706
- return import_picocolors2.default.cyan(`[${levelText}]`);
626
+ return import_picocolors2.default.cyan(plainTag);
707
627
  case "warn":
708
- return import_picocolors2.default.yellow(`[${levelText}]`);
628
+ return import_picocolors2.default.yellow(plainTag);
709
629
  case "error":
710
- return import_picocolors2.default.red(`[${levelText}]`);
630
+ return import_picocolors2.default.red(plainTag);
711
631
  default:
712
- return `[${levelText}]`;
632
+ return plainTag;
713
633
  }
714
634
  })();
715
- const formatted = `${timestamp} ${coloredLevel} ${message}`;
716
- if (this.config.singleLine && hasMeta) {
635
+ const tagForOutput = customSink ? plainTag : coloredTag;
636
+ const formatted = `${timestamp} ${tagForOutput} ${message}`;
637
+ if (this.config.singleLine && hasMeta && !customSink) {
717
638
  const metaStr = JSON.stringify(finalMeta).replace(/\n/g, "\\n");
718
- sink(`${formatted} ${metaStr}`);
639
+ consoleFallback(`${formatted} ${metaStr}`);
719
640
  return;
720
641
  }
721
642
  if (customSink) {
722
- if (hasMeta) sink(formatted, finalMeta);
723
- else sink(formatted);
643
+ const obj = hasMeta ? finalMeta : {};
644
+ customSink(obj, formatted);
724
645
  } else {
725
- if (hasMeta) consoleFallback(formatted, finalMeta);
726
- else consoleFallback(formatted);
646
+ hasMeta ? consoleFallback(formatted, finalMeta) : consoleFallback(formatted);
727
647
  }
728
648
  }
729
- info(message, meta) {
730
- this.emit("info", message, meta);
649
+ info(meta, message) {
650
+ this.emit("info", message ?? "", meta);
731
651
  }
732
- warn(message, meta) {
733
- this.emit("warn", message, meta);
652
+ warn(meta, message) {
653
+ this.emit("warn", message ?? "", meta);
734
654
  }
735
- error(message, meta) {
736
- this.emit("error", message, meta);
655
+ error(meta, message) {
656
+ this.emit("error", message ?? "", meta);
737
657
  }
738
- debug(category, message, meta) {
658
+ debug(category, meta, message) {
739
659
  if (!this.debugEnabled.has(category)) return;
740
- this.emit("debug", message, meta, category);
660
+ this.emit("debug", message ?? "", meta, category);
741
661
  }
742
662
  };
743
663
  function createLogger(opts) {
@@ -754,1209 +674,1372 @@ function createLogger(opts) {
754
674
  return logger;
755
675
  }
756
676
 
757
- // src/security/CSP.ts
758
- var defaultGenerateCSP = (directives, nonce, req) => {
759
- const merged = { ...directives };
760
- merged["script-src"] = merged["script-src"] || ["'self'"];
761
- if (!merged["script-src"].some((v) => v.startsWith("'nonce-"))) {
762
- merged["script-src"].push(`'nonce-${nonce}'`);
763
- }
764
- if (isDevelopment) {
765
- const connect = merged["connect-src"] || ["'self'"];
766
- if (!connect.includes("ws:")) connect.push("ws:");
767
- if (!connect.includes("http:")) connect.push("http:");
768
- merged["connect-src"] = connect;
769
- const style = merged["style-src"] || ["'self'"];
770
- if (!style.includes("'unsafe-inline'")) style.push("'unsafe-inline'");
771
- merged["style-src"] = style;
772
- }
773
- return Object.entries(merged).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
774
- };
775
- var generateNonce = () => crypto.randomBytes(16).toString("base64");
776
- var mergeDirectives = (base, override) => {
777
- const merged = { ...base };
778
- for (const [directive, values] of Object.entries(override)) {
779
- if (merged[directive]) {
780
- merged[directive] = [.../* @__PURE__ */ new Set([...merged[directive], ...values])];
781
- } else {
782
- merged[directive] = [...values];
677
+ // src/network/CLI.ts
678
+ function readFlag(argv, keys, bareValue) {
679
+ const end = argv.indexOf("--");
680
+ const limit = end === -1 ? argv.length : end;
681
+ for (let i = 0; i < limit; i++) {
682
+ const arg = argv[i];
683
+ for (const key of keys) {
684
+ if (arg === key) {
685
+ const next = argv[i + 1];
686
+ if (!next || next.startsWith("-")) return bareValue;
687
+ return next.trim();
688
+ }
689
+ const pref = `${key}=`;
690
+ if (arg && arg.startsWith(pref)) {
691
+ const v = arg.slice(pref.length).trim();
692
+ return v || bareValue;
693
+ }
783
694
  }
784
695
  }
785
- return merged;
786
- };
787
- var findMatchingRoute = (routeMatchers, path6) => {
788
- if (!routeMatchers) return null;
789
- const match2 = matchRoute(path6, routeMatchers);
790
- return match2 ? { route: match2.route, params: match2.params } : null;
696
+ return void 0;
697
+ }
698
+ function resolveNet(input) {
699
+ const env = process.env;
700
+ const argv = process.argv;
701
+ let host = "localhost";
702
+ let port = 5173;
703
+ let hmrPort = 5174;
704
+ if (input?.host) host = input.host;
705
+ if (Number.isFinite(input?.port)) port = Number(input.port);
706
+ if (Number.isFinite(input?.hmrPort)) hmrPort = Number(input.hmrPort);
707
+ if (env.HOST?.trim()) host = env.HOST.trim();
708
+ else if (env.FASTIFY_ADDRESS?.trim()) host = env.FASTIFY_ADDRESS.trim();
709
+ if (env.PORT) port = Number(env.PORT) || port;
710
+ if (env.FASTIFY_PORT) port = Number(env.FASTIFY_PORT) || port;
711
+ if (env.HMR_PORT) hmrPort = Number(env.HMR_PORT) || hmrPort;
712
+ const cliHost = readFlag(argv, ["--host", "--hostname", "-H"], "0.0.0.0");
713
+ const cliPort = readFlag(argv, ["--port", "-p"]);
714
+ const cliHMR = readFlag(argv, ["--hmr-port"]);
715
+ if (cliHost) host = cliHost;
716
+ if (cliPort) port = Number(cliPort) || port;
717
+ if (cliHMR) hmrPort = Number(cliHMR) || hmrPort;
718
+ if (host === "true" || host === "") host = "0.0.0.0";
719
+ return { host, port, hmrPort };
720
+ }
721
+
722
+ // src/network/Network.ts
723
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
724
+ import { networkInterfaces } from "os";
725
+ var isPrivateIPv4 = (addr) => {
726
+ if (!/^\d+\.\d+\.\d+\.\d+$/.test(addr)) return false;
727
+ const [a, b, _c, _d] = addr.split(".").map(Number);
728
+ if (a === 10) return true;
729
+ if (a === 192 && b === 168) return true;
730
+ if (a === 172 && b >= 16 && b <= 31) return true;
731
+ return false;
791
732
  };
792
- var cspPlugin = (0, import_fastify_plugin.default)(
793
- async (fastify, opts) => {
794
- const { generateCSP = defaultGenerateCSP, routes = [], routeMatchers, debug } = opts;
795
- const globalDirectives = opts.directives || DEV_CSP_DIRECTIVES;
796
- const matchers = routeMatchers || (routes.length > 0 ? createRouteMatchers(routes) : null);
797
- const logger = createLogger({
798
- debug,
799
- context: { component: "csp-plugin" }
800
- });
801
- fastify.addHook("onRequest", (req, reply, done) => {
802
- const nonce = generateNonce();
803
- req.cspNonce = nonce;
804
- try {
805
- const routeMatch = findMatchingRoute(matchers, req.url);
806
- const routeCSP = routeMatch?.route.attr?.middleware?.csp;
807
- if (routeCSP === false) {
808
- done();
809
- return;
810
- }
811
- let finalDirectives = globalDirectives;
812
- if (routeCSP && typeof routeCSP === "object") {
813
- if (!routeCSP.disabled) {
814
- let routeDirectives;
815
- if (typeof routeCSP.directives === "function") {
816
- const params = routeMatch?.params || {};
817
- routeDirectives = routeCSP.directives({
818
- url: req.url,
819
- params,
820
- headers: req.headers,
821
- req
822
- });
823
- } else {
824
- routeDirectives = routeCSP.directives || {};
825
- }
826
- if (routeCSP.mode === "replace") {
827
- finalDirectives = routeDirectives;
828
- } else {
829
- finalDirectives = mergeDirectives(globalDirectives, routeDirectives);
830
- }
831
- }
832
- }
833
- let cspHeader;
834
- if (routeCSP?.generateCSP) {
835
- cspHeader = routeCSP.generateCSP(finalDirectives, nonce, req);
836
- } else {
837
- cspHeader = generateCSP(finalDirectives, nonce, req);
733
+ var bannerPlugin = async (fastify, options) => {
734
+ const logger = createLogger({ debug: options.debug });
735
+ const dbgNetwork = logger.isDebugEnabled("network");
736
+ fastify.decorate("showBanner", function showBanner() {
737
+ const addr = this.server.address();
738
+ if (!addr || typeof addr === "string") return;
739
+ const { address, port } = addr;
740
+ const boundHost = address === "::1" ? "localhost" : address === "::" ? "::" : address === "0.0.0.0" ? "0.0.0.0" : address;
741
+ console.log(`\u2503 Local ${import_picocolors3.default.bold(`http://localhost:${port}/`)}`);
742
+ if (boundHost === "localhost" || boundHost === "127.0.0.1") {
743
+ console.log("\u2503 Network use --host to expose\n");
744
+ return;
745
+ }
746
+ const nets = networkInterfaces();
747
+ let networkAddress = null;
748
+ for (const ifaces of Object.values(nets)) {
749
+ if (!ifaces) continue;
750
+ for (const iface of ifaces) {
751
+ if (iface.internal || iface.family !== "IPv4") continue;
752
+ if (isPrivateIPv4(iface.address)) {
753
+ networkAddress = iface.address;
754
+ break;
838
755
  }
839
- reply.header("Content-Security-Policy", cspHeader);
840
- } catch (error) {
841
- logger.error("CSP plugin error", {
842
- url: req.url,
843
- error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error)
844
- });
845
- const fallbackHeader = generateCSP(globalDirectives, nonce, req);
846
- reply.header("Content-Security-Policy", fallbackHeader);
756
+ if (!networkAddress) networkAddress = iface.address;
847
757
  }
848
- done();
849
- });
850
- },
851
- { name: "taujs-csp-plugin" }
852
- );
853
-
854
- // src/utils/AssetManager.ts
855
- import { readFile } from "fs/promises";
856
- import path2 from "path";
857
- import { pathToFileURL } from "url";
758
+ if (networkAddress && isPrivateIPv4(networkAddress)) break;
759
+ }
760
+ if (networkAddress) {
761
+ console.log(`\u2503 Network http://${networkAddress}:${port}/
762
+ `);
763
+ if (dbgNetwork) logger.warn({}, import_picocolors3.default.yellow(`${CONTENT.TAG} [network] Dev server exposed on network - for local testing only.`));
764
+ }
765
+ logger.info({}, import_picocolors3.default.green(`${CONTENT.TAG} [network] Bound to host: ${boundHost}`));
766
+ });
767
+ fastify.addHook("onReady", async function() {
768
+ if (this.server.listening) {
769
+ this.showBanner();
770
+ return;
771
+ }
772
+ this.server.once("listening", () => this.showBanner());
773
+ });
774
+ };
858
775
 
859
- // src/utils/Templates.ts
860
- var CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
861
- async function collectStyle(server, entries) {
862
- const urls = await collectStyleUrls(server, entries);
863
- const codes = await Promise.all(
864
- urls.map(async (url) => {
865
- const res = await server.transformRequest(url + "?direct");
866
- return [`/* [collectStyle] ${url} */`, res?.code];
867
- })
868
- );
869
- return codes.flat().filter(Boolean).join("\n\n");
870
- }
871
- async function collectStyleUrls(server, entries) {
872
- const visited = /* @__PURE__ */ new Set();
873
- async function traverse(url) {
874
- const [, id] = await server.moduleGraph.resolveUrl(url);
875
- if (visited.has(id)) return;
876
- visited.add(id);
877
- const mod = server.moduleGraph.getModuleById(id);
878
- if (!mod) return;
879
- await Promise.all([...mod.importedModules].map((childMod) => traverse(childMod.url)));
776
+ // src/security/VerifyMiddleware.ts
777
+ var isAuthRequired = (route) => Boolean(route.attr?.middleware?.auth);
778
+ var hasAuthenticate = (app) => typeof app.authenticate === "function";
779
+ function formatCspLoadedMsg(hasGlobal, custom) {
780
+ if (hasGlobal) {
781
+ return custom > 0 ? `Loaded global config with ${custom} route override(s)` : "Loaded global config";
880
782
  }
881
- await Promise.all(entries.map((e) => server.transformRequest(e)));
882
- await Promise.all(entries.map((url) => traverse(url)));
883
- return [...visited].filter((url) => url.match(CSS_LANGS_RE));
783
+ return custom > 0 ? `Loaded development defaults with ${custom} route override(s)` : "Loaded development defaults";
884
784
  }
885
- function renderPreloadLinks(ssrManifest, basePath = "") {
886
- const seen = /* @__PURE__ */ new Set();
887
- let links = "";
888
- for (const moduleId in ssrManifest) {
889
- const files = ssrManifest[moduleId];
890
- if (files) {
891
- files.forEach((file) => {
892
- if (!seen.has(file)) {
893
- seen.add(file);
894
- links += renderPreloadLink(basePath ? `${basePath}/${file}` : `${file}`);
895
- }
785
+ var verifyContracts = (app, routes, contracts, security) => {
786
+ const items = [];
787
+ for (const contract of contracts) {
788
+ const isRequired = contract.required(routes, security);
789
+ if (!isRequired) {
790
+ items.push({
791
+ key: contract.key,
792
+ status: "skipped",
793
+ message: `No routes require "${contract.key}"`
896
794
  });
795
+ continue;
897
796
  }
898
- }
899
- return links;
900
- }
901
- function renderPreloadLink(file) {
902
- const fileType = file.match(/\.(js|css|woff2?|gif|jpe?g|png|svg)$/)?.[1];
903
- switch (fileType) {
904
- case "js":
905
- return `<link rel="modulepreload" href="${file}">`;
906
- case "css":
907
- return `<link rel="stylesheet" href="${file}">`;
908
- case "woff":
909
- case "woff2":
910
- return `<link rel="preload" href="${file}" as="font" type="font/${fileType}" crossorigin>`;
911
- case "gif":
912
- case "jpeg":
913
- case "jpg":
914
- case "png":
915
- return `<link rel="preload" href="${file}" as="image" type="image/${fileType}">`;
916
- case "svg":
917
- return `<link rel="preload" href="${file}" as="image" type="image/svg+xml">`;
918
- default:
919
- return "";
920
- }
921
- }
922
- function getCssLinks(manifest, basePath = "") {
923
- const seen = /* @__PURE__ */ new Set();
924
- const styles = [];
925
- for (const key in manifest) {
926
- const entry = manifest[key];
927
- if (entry && entry.css) {
928
- for (const cssFile of entry.css) {
929
- if (!seen.has(cssFile)) {
930
- seen.add(cssFile);
931
- styles.push(`<link rel="preload stylesheet" as="style" type="text/css" href="${basePath}/${cssFile}">`);
932
- }
797
+ if (!contract.verify(app)) {
798
+ const msg = `[\u03C4js] ${contract.errorMessage}`;
799
+ items.push({ key: contract.key, status: "error", message: msg });
800
+ throw new Error(msg);
801
+ }
802
+ if (contract.key === "csp") {
803
+ const total = routes.length;
804
+ const disabled = routes.filter((r) => r.attr?.middleware?.csp === false).length;
805
+ const custom = routes.filter((r) => {
806
+ const v = r.attr?.middleware?.csp;
807
+ return v !== void 0 && v !== false;
808
+ }).length;
809
+ const enabled = total - disabled;
810
+ const hasGlobal = !!security?.csp;
811
+ let status = "verified";
812
+ let tail = "";
813
+ if (!hasGlobal && process.env.NODE_ENV === "production") {
814
+ status = "warning";
815
+ tail = " (consider adding global CSP for production)";
933
816
  }
817
+ const baseMsg = formatCspLoadedMsg(hasGlobal, custom);
818
+ items.push({
819
+ key: "csp",
820
+ status,
821
+ message: baseMsg + tail
822
+ });
823
+ items.push({
824
+ key: "csp",
825
+ status,
826
+ message: `\u2713 Verified (${enabled} enabled, ${disabled} disabled, ${total} total). ` + tail
827
+ });
828
+ } else {
829
+ const count = routes.filter((r) => contract.required([r], security)).length;
830
+ items.push({
831
+ key: contract.key,
832
+ status: "verified",
833
+ message: `\u2713 ${count} route(s)`
834
+ });
934
835
  }
935
836
  }
936
- return styles.join("\n");
937
- }
938
- var overrideCSSHMRConsoleError = () => {
939
- const originalConsoleError = console.error;
940
- console.error = function(message, ...optionalParams) {
941
- if (typeof message === "string" && message.includes("css hmr is not supported in runtime mode")) return;
942
- originalConsoleError.apply(console, [message, ...optionalParams]);
943
- };
944
- };
945
- var ensureNonNull = (value, errorMessage) => {
946
- if (value === void 0 || value === null) throw new Error(errorMessage);
947
- return value;
837
+ return { items };
948
838
  };
949
- function processTemplate(template) {
950
- const [headSplit, bodySplit] = template.split(SSRTAG.ssrHead);
951
- if (typeof bodySplit === "undefined") throw new Error(`Template is missing ${SSRTAG.ssrHead} marker.`);
952
- const [beforeBody, afterBody] = bodySplit.split(SSRTAG.ssrHtml);
953
- if (typeof beforeBody === "undefined" || typeof afterBody === "undefined") throw new Error(`Template is missing ${SSRTAG.ssrHtml} marker.`);
839
+
840
+ // src/SSRServer.ts
841
+ var import_fastify_plugin3 = __toESM(require_plugin(), 1);
842
+
843
+ // src/logging/utils/index.ts
844
+ var httpStatusFrom = (err, fallback = 500) => err instanceof AppError ? err.httpStatus : fallback;
845
+ var toHttp = (err) => {
846
+ const app = AppError.from(err);
847
+ const status = httpStatusFrom(app);
848
+ const errorMessage = app.safeMessage;
954
849
  return {
955
- beforeHead: headSplit,
956
- afterHead: "",
957
- beforeBody: beforeBody.replace(/\s*$/, ""),
958
- afterBody: afterBody.replace(/^\s*/, "")
850
+ status,
851
+ body: {
852
+ error: errorMessage,
853
+ ...app.code && { code: app.code },
854
+ statusText: statusText(status)
855
+ }
959
856
  };
960
- }
961
- var rebuildTemplate = (parts, headContent, bodyContent) => {
962
- return `${parts.beforeHead}${headContent}${parts.afterHead}${parts.beforeBody}${bodyContent}${parts.afterBody}`;
857
+ };
858
+ var statusText = (status) => {
859
+ const map = {
860
+ 400: "Bad Request",
861
+ 401: "Unauthorized",
862
+ 403: "Forbidden",
863
+ 404: "Not Found",
864
+ 405: "Method Not Allowed",
865
+ 408: "Request Timeout",
866
+ 422: "Unprocessable Entity",
867
+ 429: "Too Many Requests",
868
+ 499: "Client Closed Request",
869
+ 500: "Internal Server Error",
870
+ 502: "Bad Gateway",
871
+ 503: "Service Unavailable",
872
+ 504: "Gateway Timeout"
873
+ };
874
+ return map[status] ?? "Error";
963
875
  };
964
876
 
965
- // src/utils/AssetManager.ts
966
- var createMaps = () => ({
967
- bootstrapModules: /* @__PURE__ */ new Map(),
968
- cssLinks: /* @__PURE__ */ new Map(),
969
- manifests: /* @__PURE__ */ new Map(),
970
- preloadLinks: /* @__PURE__ */ new Map(),
971
- renderModules: /* @__PURE__ */ new Map(),
972
- ssrManifests: /* @__PURE__ */ new Map(),
973
- templates: /* @__PURE__ */ new Map()
974
- });
975
- var processConfigs = (configs, baseClientRoot, templateDefaults) => {
976
- return configs.map((config) => {
977
- const clientRoot = path2.resolve(baseClientRoot, config.entryPoint);
978
- return {
979
- clientRoot,
980
- entryPoint: config.entryPoint,
981
- entryClient: config.entryClient || templateDefaults.defaultEntryClient,
982
- entryServer: config.entryServer || templateDefaults.defaultEntryServer,
983
- htmlTemplate: config.htmlTemplate || templateDefaults.defaultHtmlTemplate,
984
- appId: config.appId
985
- };
877
+ // src/utils/DataRoutes.ts
878
+ import { match } from "path-to-regexp";
879
+
880
+ // src/utils/DataServices.ts
881
+ import { performance as performance2 } from "perf_hooks";
882
+ async function callServiceMethod(registry, serviceName, methodName, params, ctx) {
883
+ if (ctx.signal?.aborted) throw AppError.timeout("Request canceled");
884
+ const service = registry[serviceName];
885
+ if (!service) throw AppError.notFound(`Unknown service: ${serviceName}`);
886
+ const method = service[methodName];
887
+ if (!method) throw AppError.notFound(`Unknown method: ${serviceName}.${methodName}`);
888
+ const logger = ctx.logger?.child?.({
889
+ component: "service-call",
890
+ service: serviceName,
891
+ method: methodName,
892
+ traceId: ctx.traceId
986
893
  });
894
+ const t0 = performance2.now();
895
+ try {
896
+ const result = await method(params ?? {}, ctx);
897
+ if (typeof result !== "object" || result === null) {
898
+ throw AppError.internal(`Non-object result from ${serviceName}.${methodName}`);
899
+ }
900
+ logger?.debug?.({ ms: +(performance2.now() - t0).toFixed(1) }, "Service method ok");
901
+ return result;
902
+ } catch (err) {
903
+ logger?.error?.(
904
+ {
905
+ params,
906
+ error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
907
+ ms: +(performance2.now() - t0).toFixed(1)
908
+ },
909
+ "Service method failed"
910
+ );
911
+ throw err instanceof AppError ? err : err instanceof Error ? AppError.internal(err.message, { cause: err }) : AppError.internal("Unknown error", { details: { err } });
912
+ }
913
+ }
914
+ var isServiceDescriptor = (obj) => {
915
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return false;
916
+ const o = obj;
917
+ if (typeof o.serviceName !== "string" || typeof o.serviceMethod !== "string") return false;
918
+ if ("args" in o) {
919
+ if (o.args === null || typeof o.args !== "object" || Array.isArray(o.args)) return false;
920
+ }
921
+ return true;
987
922
  };
988
- var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssLinks, manifests, preloadLinks, renderModules, ssrManifests, templates, opts = {}) => {
989
- const logger = opts.logger ?? createLogger({
990
- debug: opts.debug,
991
- includeContext: true
923
+
924
+ // src/utils/DataRoutes.ts
925
+ var safeDecode = (value) => {
926
+ try {
927
+ return decodeURIComponent(value);
928
+ } catch {
929
+ return value;
930
+ }
931
+ };
932
+ var cleanPath = (path7) => {
933
+ if (!path7) return "/";
934
+ const basePart = path7.split("?")[0];
935
+ const base = basePart ? basePart.split("#")[0] : "/";
936
+ return base || "/";
937
+ };
938
+ var calculateSpecificity = (path7) => {
939
+ let score = 0;
940
+ const segments = path7.split("/").filter(Boolean);
941
+ for (const segment of segments) {
942
+ if (segment.startsWith(":")) {
943
+ score += 1;
944
+ if (/[?+*]$/.test(segment)) score -= 0.5;
945
+ } else if (segment === "*") {
946
+ score += 0.1;
947
+ } else {
948
+ score += 10;
949
+ }
950
+ }
951
+ score += segments.length * 0.1;
952
+ return score;
953
+ };
954
+ var isPlainObject = (v) => !!v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
955
+ var createRouteMatchers = (routes) => {
956
+ const sortedRoutes = [...routes].sort((a, b) => calculateSpecificity(b.path) - calculateSpecificity(a.path));
957
+ return sortedRoutes.map((route) => {
958
+ const matcher = match(route.path, { decode: safeDecode });
959
+ const specificity = calculateSpecificity(route.path);
960
+ const keys = [];
961
+ return { route, matcher, keys, specificity };
992
962
  });
993
- for (const config of processedConfigs) {
994
- const { clientRoot, entryClient, entryServer, htmlTemplate } = config;
995
- try {
996
- const templateHtmlPath = path2.join(clientRoot, htmlTemplate);
997
- const templateHtml = await readFile(templateHtmlPath, "utf-8");
998
- templates.set(clientRoot, templateHtml);
999
- const relativeBasePath = path2.relative(baseClientRoot, clientRoot).replace(/\\/g, "/");
1000
- const adjustedRelativePath = relativeBasePath ? `/${relativeBasePath}` : "";
1001
- if (!isDevelopment) {
1002
- try {
1003
- const manifestPath = path2.join(clientRoot, ".vite/manifest.json");
1004
- const manifestContent = await readFile(manifestPath, "utf-8");
1005
- const manifest = JSON.parse(manifestContent);
1006
- manifests.set(clientRoot, manifest);
1007
- const ssrManifestPath = path2.join(clientRoot, ".vite/ssr-manifest.json");
1008
- const ssrManifestContent = await readFile(ssrManifestPath, "utf-8");
1009
- const ssrManifest = JSON.parse(ssrManifestContent);
1010
- ssrManifests.set(clientRoot, ssrManifest);
1011
- const entryClientFile = manifest[`${entryClient}.tsx`]?.file;
1012
- if (!entryClientFile) {
1013
- throw AppError.internal(`Entry client file not found in manifest for ${entryClient}.tsx`, {
1014
- details: {
1015
- clientRoot,
1016
- entryClient,
1017
- availableKeys: Object.keys(manifest)
1018
- }
1019
- });
1020
- }
1021
- const bootstrapModule = `/${adjustedRelativePath}/${entryClientFile}`.replace(/\/{2,}/g, "/");
1022
- bootstrapModules.set(clientRoot, bootstrapModule);
1023
- const preloadLink = renderPreloadLinks(ssrManifest, adjustedRelativePath);
1024
- preloadLinks.set(clientRoot, preloadLink);
1025
- const cssLink = getCssLinks(manifest, adjustedRelativePath);
1026
- cssLinks.set(clientRoot, cssLink);
1027
- const renderModulePath = path2.join(clientRoot, `${entryServer}.js`);
1028
- const moduleUrl = pathToFileURL(renderModulePath).href;
1029
- try {
1030
- const importedModule = await import(moduleUrl);
1031
- renderModules.set(clientRoot, importedModule);
1032
- } catch (err) {
1033
- throw AppError.internal(`Failed to load render module ${renderModulePath}`, {
1034
- cause: err,
1035
- details: { moduleUrl, clientRoot, entryServer }
1036
- });
1037
- }
1038
- } catch (err) {
1039
- if (err instanceof AppError) {
1040
- logger.error("Asset load failed", {
1041
- error: { name: err.name, message: err.message, stack: err.stack, code: err.code },
1042
- stage: "loadAssets:production"
1043
- });
1044
- } else {
1045
- logger.error("Asset load failed", {
1046
- error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
1047
- stage: "loadAssets:production"
1048
- });
1049
- }
1050
- }
1051
- } else {
1052
- const bootstrapModule = `/${adjustedRelativePath}/${entryClient}`.replace(/\/{2,}/g, "/");
1053
- bootstrapModules.set(clientRoot, bootstrapModule);
1054
- }
1055
- } catch (err) {
1056
- logger.error("Failed to process config", {
1057
- error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
1058
- stage: "loadAssets:config"
963
+ };
964
+ var matchRoute = (url, routeMatchers) => {
965
+ const path7 = cleanPath(url);
966
+ for (const { route, matcher, keys } of routeMatchers) {
967
+ const match2 = matcher(path7);
968
+ if (match2) {
969
+ return {
970
+ route,
971
+ params: match2.params,
972
+ keys
973
+ };
974
+ }
975
+ }
976
+ return null;
977
+ };
978
+ var fetchInitialData = async (attr, params, serviceRegistry, ctx, callServiceMethodImpl = callServiceMethod) => {
979
+ const dataHandler = attr?.data;
980
+ if (!dataHandler || typeof dataHandler !== "function") return {};
981
+ try {
982
+ const result = await dataHandler(params, {
983
+ ...ctx,
984
+ headers: ctx.headers ?? {}
985
+ });
986
+ if (isServiceDescriptor(result)) {
987
+ const { serviceName, serviceMethod, args } = result;
988
+ return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctx);
989
+ }
990
+ if (isPlainObject(result)) return result;
991
+ throw AppError.badRequest("attr.data must return a plain object or a ServiceDescriptor");
992
+ } catch (err) {
993
+ let e = AppError.from(err);
994
+ const msg = String(err?.message ?? "");
995
+ const looksLikeHtml = /<!DOCTYPE/i.test(msg) || /<html/i.test(msg) || /Unexpected token <.*JSON/i.test(msg);
996
+ if (looksLikeHtml) {
997
+ const prevDetails = e.details && typeof e.details === "object" ? e.details : {};
998
+ e = AppError.internal("attr.data expected JSON but received HTML. Likely cause: API route missing or returning HTML.", err, {
999
+ ...prevDetails,
1000
+ hint: "api-missing-or-content-type",
1001
+ suggestion: "Register api route so it returns JSON, or return a ServiceDescriptor from attr.data and use the ServiceRegistry.",
1002
+ logged: true
1059
1003
  });
1060
1004
  }
1005
+ const level = e.kind === "domain" || e.kind === "validation" || e.kind === "auth" ? "warn" : "error";
1006
+ const meta = {
1007
+ component: "fetch-initial-data",
1008
+ kind: e.kind,
1009
+ httpStatus: e.httpStatus,
1010
+ ...e.code ? { code: e.code } : {},
1011
+ ...e.details ? { details: e.details } : {},
1012
+ ...params ? { params } : {},
1013
+ traceId: ctx.traceId
1014
+ };
1015
+ ctx.logger?.[level](meta, e.message);
1016
+ throw e;
1061
1017
  }
1062
1018
  };
1063
1019
 
1064
- // src/utils/DevServer.ts
1065
- import path3 from "path";
1066
- var setupDevServer = async (app, baseClientRoot, alias, debug, devNet) => {
1067
- const logger = createLogger({
1068
- context: { service: "setupDevServer" },
1069
- debug,
1070
- minLevel: "debug"
1071
- });
1072
- const host = devNet?.host ?? process.env.HOST?.trim() ?? process.env.FASTIFY_ADDRESS?.trim() ?? "localhost";
1073
- const hmrPort = devNet?.hmrPort ?? (Number(process.env.HMR_PORT) || 5174);
1074
- const { createServer: createServer2 } = await import("vite");
1075
- const viteDevServer = await createServer2({
1076
- appType: "custom",
1077
- css: {
1078
- preprocessorOptions: {
1079
- scss: {
1080
- api: "modern-compiler"
1081
- }
1020
+ // src/security/Auth.ts
1021
+ var createAuthHook = (routeMatchers, logger) => {
1022
+ return async function authHook(req, reply) {
1023
+ const url = new URL(req.url, `http://${req.headers.host}`).pathname;
1024
+ const match2 = matchRoute(url, routeMatchers);
1025
+ if (!match2) return;
1026
+ const { route } = match2;
1027
+ const authConfig = route.attr?.middleware?.auth;
1028
+ req.routeMeta = {
1029
+ path: route.path,
1030
+ appId: route.appId,
1031
+ attr: {
1032
+ middleware: {
1033
+ auth: route.attr?.middleware?.auth
1034
+ },
1035
+ render: route.attr?.render
1082
1036
  }
1083
- },
1084
- mode: "development",
1085
- plugins: [
1086
- ...debug ? [
1037
+ };
1038
+ if (!authConfig) {
1039
+ logger.debug("auth", { method: req.method, url: req.url }, "(none)");
1040
+ return;
1041
+ }
1042
+ if (typeof req.server.authenticate !== "function") {
1043
+ logger.warn(
1087
1044
  {
1088
- name: "\u03C4js-development-server-debug-logging",
1089
- configureServer(server) {
1090
- logger.debug("vite", `${CONTENT.TAG} Development server debug started`);
1091
- server.middlewares.use((req, res, next) => {
1092
- logger.debug("vite", "\u2190 rx", {
1093
- method: req.method,
1094
- url: req.url,
1095
- host: req.headers.host,
1096
- ua: req.headers["user-agent"]
1097
- });
1098
- res.on("finish", () => {
1099
- logger.debug("vite", "\u2192 tx", {
1100
- method: req.method,
1101
- url: req.url,
1102
- statusCode: res.statusCode
1103
- });
1104
- });
1105
- next();
1106
- });
1107
- }
1108
- }
1109
- ] : []
1110
- ],
1111
- resolve: {
1112
- alias: {
1113
- "@client": path3.resolve(baseClientRoot),
1114
- "@server": path3.resolve(__dirname),
1115
- "@shared": path3.resolve(__dirname, "../shared"),
1116
- ...alias
1117
- }
1118
- },
1119
- root: baseClientRoot,
1120
- server: {
1121
- middlewareMode: true,
1122
- hmr: {
1123
- clientPort: hmrPort,
1124
- host: host !== "localhost" ? host : void 0,
1125
- port: hmrPort,
1126
- protocol: "ws"
1127
- }
1045
+ path: url,
1046
+ appId: route.appId
1047
+ },
1048
+ "Route requires auth but Fastify authenticate decorator is missing"
1049
+ );
1050
+ return reply.status(500).send("Server misconfiguration: auth decorator missing.");
1128
1051
  }
1129
- });
1130
- overrideCSSHMRConsoleError();
1131
- app.addHook("onRequest", async (request, reply) => {
1132
- await new Promise((resolve) => {
1133
- viteDevServer.middlewares(request.raw, reply.raw, () => {
1134
- if (!reply.sent) resolve();
1135
- });
1136
- });
1137
- });
1138
- return viteDevServer;
1052
+ try {
1053
+ logger.debug("auth", { method: req.method, url: req.url }, "Invoking authenticate(...)");
1054
+ await req.server.authenticate(req, reply);
1055
+ logger.debug("auth", { method: req.method, url: req.url }, "Authentication successful");
1056
+ } catch (err) {
1057
+ logger.debug("auth", { method: req.method, url: req.url }, "Authentication failed");
1058
+ return reply.send(err);
1059
+ }
1060
+ };
1139
1061
  };
1140
1062
 
1141
- // src/utils/HandleRender.ts
1142
- import path4 from "path";
1143
- import { PassThrough } from "stream";
1063
+ // src/security/CSP.ts
1064
+ var import_fastify_plugin = __toESM(require_plugin(), 1);
1065
+ import crypto from "crypto";
1144
1066
 
1145
- // src/utils/Telemetry.ts
1146
- import crypto2 from "crypto";
1147
- function createRequestContext(req, reply, baseLogger) {
1148
- const raw = typeof req.headers["x-trace-id"] === "string" ? req.headers["x-trace-id"] : "";
1149
- const traceId = raw && REGEX.SAFE_TRACE.test(raw) ? raw : typeof req.id === "string" ? req.id : crypto2.randomUUID();
1150
- reply.header("x-trace-id", traceId);
1151
- const anyLogger = baseLogger;
1152
- const child = anyLogger.child;
1153
- const logger = typeof child === "function" ? child.call(baseLogger, { traceId, url: req.url, method: req.method }) : baseLogger;
1154
- const headers = Object.fromEntries(
1155
- Object.entries(req.headers).map(([headerName, headerValue]) => {
1156
- const normalisedValue = Array.isArray(headerValue) ? headerValue.join(",") : headerValue ?? "";
1157
- return [headerName, normalisedValue];
1158
- })
1159
- );
1160
- return { traceId, logger, headers };
1161
- }
1067
+ // src/utils/System.ts
1068
+ import { dirname, join } from "path";
1069
+ import "path";
1070
+ import { fileURLToPath } from "url";
1071
+ var isDevelopment = process.env.NODE_ENV === "development";
1072
+ var __filename = fileURLToPath(import.meta.url);
1073
+ var __dirname = join(dirname(__filename), !isDevelopment ? "./" : "..");
1162
1074
 
1163
- // src/utils/HandleRender.ts
1164
- var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, opts = {}) => {
1165
- const { viteDevServer } = opts;
1166
- const logger = opts.logger ?? createLogger({
1167
- debug: opts.debug,
1168
- minLevel: isDevelopment ? "debug" : "info",
1169
- includeContext: true,
1170
- includeStack: (lvl) => lvl === "error" || isDevelopment
1171
- });
1172
- try {
1173
- if (/\.\w+$/.test(req.raw.url ?? "")) return reply.callNotFound();
1174
- const url = req.url ? new URL(req.url, `http://${req.headers.host}`).pathname : "/";
1175
- const matchedRoute = matchRoute(url, routeMatchers);
1176
- const rawNonce = req.cspNonce;
1177
- const cspNonce = rawNonce && rawNonce.length > 0 ? rawNonce : void 0;
1178
- if (!matchedRoute) {
1179
- reply.callNotFound();
1180
- return;
1181
- }
1182
- const { route, params } = matchedRoute;
1183
- const { attr, appId } = route;
1184
- const config = processedConfigs.find((c) => c.appId === appId);
1185
- if (!config) {
1186
- throw AppError.internal("No configuration found for the request", {
1187
- details: {
1188
- appId,
1189
- availableAppIds: processedConfigs.map((c) => c.appId),
1190
- url
1191
- }
1192
- });
1193
- }
1194
- const { clientRoot, entryServer } = config;
1195
- let template = ensureNonNull(maps.templates.get(clientRoot), `Template not found for clientRoot: ${clientRoot}`);
1196
- const bootstrapModule = maps.bootstrapModules.get(clientRoot);
1197
- const cssLink = maps.cssLinks.get(clientRoot);
1198
- const manifest = maps.manifests.get(clientRoot);
1199
- const preloadLink = maps.preloadLinks.get(clientRoot);
1200
- const ssrManifest = maps.ssrManifests.get(clientRoot);
1201
- let renderModule;
1202
- if (isDevelopment && viteDevServer) {
1203
- try {
1204
- template = template.replace(/<script type="module" src="\/@vite\/client"><\/script>/g, "");
1205
- template = template.replace(/<style type="text\/css">[\s\S]*?<\/style>/g, "");
1206
- const entryServerPath = path4.join(clientRoot, `${entryServer}.tsx`);
1207
- const executedModule = await viteDevServer.ssrLoadModule(entryServerPath);
1208
- renderModule = executedModule;
1209
- const styles = await collectStyle(viteDevServer, [entryServerPath]);
1210
- const styleNonce = cspNonce ? ` nonce="${cspNonce}"` : "";
1211
- template = template?.replace("</head>", `<style type="text/css"${styleNonce}>${styles}</style></head>`);
1212
- template = await viteDevServer.transformIndexHtml(url, template);
1213
- } catch (error) {
1214
- throw AppError.internal("Failed to load dev assets", { cause: error, details: { clientRoot, entryServer, url } });
1215
- }
1075
+ // src/security/CSP.ts
1076
+ var defaultGenerateCSP = (directives, nonce, req) => {
1077
+ const merged = { ...directives };
1078
+ merged["script-src"] = merged["script-src"] || ["'self'"];
1079
+ if (!merged["script-src"].some((v) => v.startsWith("'nonce-"))) {
1080
+ merged["script-src"].push(`'nonce-${nonce}'`);
1081
+ }
1082
+ if (isDevelopment) {
1083
+ const connect = merged["connect-src"] || ["'self'"];
1084
+ if (!connect.includes("ws:")) connect.push("ws:");
1085
+ if (!connect.includes("http:")) connect.push("http:");
1086
+ merged["connect-src"] = connect;
1087
+ const style = merged["style-src"] || ["'self'"];
1088
+ if (!style.includes("'unsafe-inline'")) style.push("'unsafe-inline'");
1089
+ merged["style-src"] = style;
1090
+ }
1091
+ return Object.entries(merged).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
1092
+ };
1093
+ var generateNonce = () => crypto.randomBytes(16).toString("base64");
1094
+ var mergeDirectives = (base, override) => {
1095
+ const merged = { ...base };
1096
+ for (const [directive, values] of Object.entries(override)) {
1097
+ if (merged[directive]) {
1098
+ merged[directive] = [.../* @__PURE__ */ new Set([...merged[directive], ...values])];
1216
1099
  } else {
1217
- renderModule = maps.renderModules.get(clientRoot);
1218
- if (!renderModule) throw AppError.internal(`Render module not found for clientRoot: ${clientRoot}. Module should have been preloaded.`);
1100
+ merged[directive] = [...values];
1219
1101
  }
1220
- const renderType = attr?.render ?? RENDERTYPE.ssr;
1221
- const templateParts = processTemplate(template);
1222
- const baseLogger = opts.logger ?? logger;
1223
- const { traceId, logger: reqLogger, headers } = createRequestContext(req, reply, baseLogger);
1224
- const ctx = { traceId, logger: reqLogger, headers };
1225
- const initialDataInput = () => fetchInitialData(attr, params, serviceRegistry, ctx);
1226
- if (renderType === RENDERTYPE.ssr) {
1227
- const { renderSSR } = renderModule;
1228
- if (!renderSSR) {
1229
- throw AppError.internal("renderSSR function not found in module", {
1230
- details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1231
- });
1232
- }
1233
- const ac = new AbortController();
1234
- const onAborted = () => ac.abort("client_aborted");
1235
- req.raw.on("aborted", onAborted);
1236
- reply.raw.on("close", () => {
1237
- if (!reply.raw.writableEnded) ac.abort("socket_closed");
1238
- });
1239
- reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1240
- if (ac.signal.aborted) {
1241
- logger.warn("SSR skipped; already aborted", { url: req.url });
1242
- return;
1243
- }
1244
- const initialDataResolved = await initialDataInput();
1245
- let headContent = "";
1246
- let appHtml = "";
1102
+ }
1103
+ return merged;
1104
+ };
1105
+ var findMatchingRoute = (routeMatchers, path7) => {
1106
+ if (!routeMatchers) return null;
1107
+ const match2 = matchRoute(path7, routeMatchers);
1108
+ return match2 ? { route: match2.route, params: match2.params } : null;
1109
+ };
1110
+ var cspPlugin = (0, import_fastify_plugin.default)(
1111
+ async (fastify, opts) => {
1112
+ const { generateCSP = defaultGenerateCSP, routes = [], routeMatchers, debug } = opts;
1113
+ const globalDirectives = opts.directives || DEV_CSP_DIRECTIVES;
1114
+ const matchers = routeMatchers || (routes.length > 0 ? createRouteMatchers(routes) : null);
1115
+ const logger = createLogger({
1116
+ debug,
1117
+ context: { component: "csp-plugin" }
1118
+ });
1119
+ fastify.addHook("onRequest", (req, reply, done) => {
1120
+ const nonce = generateNonce();
1121
+ req.cspNonce = nonce;
1122
+ const headerNameFor = (routeCSP2) => routeCSP2 && typeof routeCSP2 === "object" && routeCSP2.reportOnly || opts.reporting?.reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
1123
+ let routeCSP;
1247
1124
  try {
1248
- const res = await renderSSR(initialDataResolved, req.url, attr?.meta, ac.signal, { logger: reqLogger });
1249
- headContent = res.headContent;
1250
- appHtml = res.appHtml;
1251
- } catch (err) {
1252
- const msg = String(err?.message ?? err ?? "");
1253
- const benign = REGEX.BENIGN_NET_ERR.test(msg);
1254
- if (ac.signal.aborted || benign) {
1255
- logger.warn("SSR aborted mid-render (benign)", { url: req.url, reason: msg });
1125
+ const routeMatch = findMatchingRoute(matchers, req.url);
1126
+ routeCSP = routeMatch?.route.attr?.middleware?.csp;
1127
+ if (routeCSP === false) {
1128
+ done();
1256
1129
  return;
1257
1130
  }
1258
- logger.error("SSR render failed", { url: req.url, error: normaliseError(err) });
1259
- throw err;
1131
+ let finalDirectives = globalDirectives;
1132
+ if (routeCSP && typeof routeCSP === "object" && !routeCSP.disabled) {
1133
+ const routeDirectives = typeof routeCSP.directives === "function" ? routeCSP.directives({
1134
+ url: req.url,
1135
+ params: routeMatch?.params || {},
1136
+ headers: req.headers,
1137
+ req
1138
+ }) : routeCSP.directives ?? {};
1139
+ finalDirectives = routeCSP.mode === "replace" ? routeDirectives : mergeDirectives(globalDirectives, routeDirectives);
1140
+ }
1141
+ const cspHeader = routeCSP?.generateCSP ? routeCSP.generateCSP(finalDirectives, nonce, req) : generateCSP(finalDirectives, nonce, req);
1142
+ reply.header(headerNameFor(routeCSP), cspHeader);
1143
+ } catch (error) {
1144
+ logger.error(
1145
+ {
1146
+ url: req.url,
1147
+ error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error)
1148
+ },
1149
+ "CSP plugin error"
1150
+ );
1151
+ const fallbackHeader = generateCSP(globalDirectives, nonce, req);
1152
+ reply.header(headerNameFor(routeCSP), fallbackHeader);
1260
1153
  }
1261
- let aggregateHeadContent = headContent;
1262
- if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
1263
- if (manifest && cssLink) aggregateHeadContent += cssLink;
1264
- const shouldHydrate = attr?.hydrate !== false;
1265
- const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
1266
- const initialDataScript = `<script${nonceAttr}>window.__INITIAL_DATA__ = ${JSON.stringify(initialDataResolved).replace(/</g, "\\u003c")};</script>`;
1267
- const bootstrapScriptTag = shouldHydrate && bootstrapModule ? `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script>` : "";
1268
- const safeAppHtml = appHtml.trim();
1269
- const fullHtml = rebuildTemplate(templateParts, aggregateHeadContent, `${safeAppHtml}${initialDataScript}${bootstrapScriptTag}`);
1154
+ done();
1155
+ });
1156
+ },
1157
+ { name: "taujs-csp-plugin" }
1158
+ );
1159
+
1160
+ // src/security/CSPReporting.ts
1161
+ var import_fastify_plugin2 = __toESM(require_plugin(), 1);
1162
+ function sanitiseContext(ctx) {
1163
+ return {
1164
+ userAgent: ctx.userAgent,
1165
+ ip: ctx.ip,
1166
+ referer: ctx.referer,
1167
+ timestamp: ctx.timestamp
1168
+ // headers: ctx.headers,
1169
+ };
1170
+ }
1171
+ function logCspViolation(logger, report, context) {
1172
+ logger.warn(
1173
+ {
1174
+ violation: {
1175
+ documentUri: report["document-uri"],
1176
+ violatedDirective: report["violated-directive"],
1177
+ blockedUri: report["blocked-uri"],
1178
+ sourceFile: report["source-file"],
1179
+ line: report["line-number"],
1180
+ column: report["column-number"],
1181
+ scriptSample: report["script-sample"],
1182
+ originalPolicy: report["original-policy"],
1183
+ disposition: report.disposition
1184
+ },
1185
+ context: {
1186
+ userAgent: context.userAgent,
1187
+ ip: context.ip,
1188
+ referer: context.referer,
1189
+ timestamp: context.timestamp
1190
+ }
1191
+ },
1192
+ "CSP Violation"
1193
+ );
1194
+ }
1195
+ var processCSPReport = (body, context, logger) => {
1196
+ try {
1197
+ const reportData = body?.["csp-report"] || body;
1198
+ if (!reportData || typeof reportData !== "object") {
1199
+ logger.warn(
1200
+ {
1201
+ bodyType: typeof body,
1202
+ context: sanitiseContext(context)
1203
+ },
1204
+ "Ignoring malformed CSP report"
1205
+ );
1206
+ return;
1207
+ }
1208
+ const documentUri = reportData["document-uri"] ?? reportData["documentURL"];
1209
+ const violatedDirective = reportData["violated-directive"] ?? reportData["violatedDirective"];
1210
+ if (!documentUri || !violatedDirective) {
1211
+ logger.warn(
1212
+ {
1213
+ hasDocumentUri: !!documentUri,
1214
+ hasViolatedDirective: !!violatedDirective,
1215
+ context: sanitiseContext(context)
1216
+ },
1217
+ "Ignoring incomplete CSP report"
1218
+ );
1219
+ return;
1220
+ }
1221
+ const violation = {
1222
+ "document-uri": String(documentUri),
1223
+ "violated-directive": String(violatedDirective),
1224
+ "blocked-uri": reportData["blocked-uri"] ?? reportData["blockedURL"] ?? "",
1225
+ "source-file": reportData["source-file"] ?? reportData["sourceFile"],
1226
+ "line-number": reportData["line-number"] ?? reportData["lineNumber"],
1227
+ "column-number": reportData["column-number"] ?? reportData["columnNumber"],
1228
+ "script-sample": reportData["script-sample"] ?? reportData["sample"],
1229
+ "original-policy": reportData["original-policy"] ?? reportData["originalPolicy"] ?? "",
1230
+ disposition: reportData.disposition ?? "enforce"
1231
+ };
1232
+ logCspViolation(logger, violation, context);
1233
+ } catch (processingError) {
1234
+ logger.warn(
1235
+ {
1236
+ error: processingError instanceof Error ? processingError.message : String(processingError),
1237
+ bodyType: typeof body,
1238
+ context: sanitiseContext(context)
1239
+ },
1240
+ "CSP report processing failed"
1241
+ );
1242
+ }
1243
+ };
1244
+ var cspReportPlugin = (0, import_fastify_plugin2.default)(
1245
+ async (fastify, opts) => {
1246
+ const { onViolation } = opts;
1247
+ if (!opts.path || typeof opts.path !== "string") throw AppError.badRequest("CSP report path is required and must be a string");
1248
+ const logger = createLogger({
1249
+ debug: opts.debug,
1250
+ context: { service: "csp-reporting" },
1251
+ minLevel: "info"
1252
+ });
1253
+ fastify.post(opts.path, async (req, reply) => {
1254
+ const context = {
1255
+ userAgent: req.headers["user-agent"],
1256
+ ip: req.ip,
1257
+ referer: req.headers.referer,
1258
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1259
+ headers: req.headers,
1260
+ __fastifyRequest: req
1261
+ // onViolation callback
1262
+ };
1270
1263
  try {
1271
- return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
1264
+ processCSPReport(req.body, context, logger);
1265
+ const reportData = req.body?.["csp-report"] || req.body;
1266
+ if (onViolation && reportData && typeof reportData === "object") {
1267
+ const documentUri = reportData["document-uri"] ?? reportData["documentURL"];
1268
+ const violatedDirective = reportData["violated-directive"] ?? reportData["violatedDirective"];
1269
+ if (documentUri && violatedDirective) {
1270
+ const violation = {
1271
+ "document-uri": String(documentUri),
1272
+ "violated-directive": String(violatedDirective),
1273
+ "blocked-uri": reportData["blocked-uri"] ?? reportData["blockedURL"] ?? "",
1274
+ "source-file": reportData["source-file"] ?? reportData["sourceFile"],
1275
+ "line-number": reportData["line-number"] ?? reportData["lineNumber"],
1276
+ "column-number": reportData["column-number"] ?? reportData["columnNumber"],
1277
+ "script-sample": reportData["script-sample"] ?? reportData["sample"],
1278
+ "original-policy": reportData["original-policy"] ?? reportData["originalPolicy"] ?? "",
1279
+ disposition: reportData.disposition ?? "enforce"
1280
+ };
1281
+ onViolation(violation, req);
1282
+ }
1283
+ }
1272
1284
  } catch (err) {
1273
- const msg = String(err?.message ?? err ?? "");
1274
- const benign = REGEX.BENIGN_NET_ERR.test(msg);
1275
- if (!benign) logger.error("SSR send failed", { url: req.url, error: normaliseError(err) });
1276
- else logger.warn("SSR send aborted (benign)", { url: req.url, reason: msg });
1277
- return;
1278
- }
1279
- } else {
1280
- const { renderStream } = renderModule;
1281
- if (!renderStream) {
1282
- throw AppError.internal("renderStream function not found in module", {
1283
- details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1284
- });
1285
+ logger.warn(
1286
+ {
1287
+ error: err instanceof Error ? err.message : String(err)
1288
+ },
1289
+ "CSP reporting route failed"
1290
+ );
1285
1291
  }
1286
- const cspHeader = reply.getHeader("Content-Security-Policy");
1287
- reply.raw.writeHead(200, {
1288
- "Content-Security-Policy": cspHeader,
1289
- "Content-Type": "text/html; charset=utf-8"
1290
- });
1291
- const ac = new AbortController();
1292
- const onAborted = () => ac.abort();
1293
- req.raw.on("aborted", onAborted);
1294
- reply.raw.on("close", () => {
1295
- if (!reply.raw.writableEnded) ac.abort();
1296
- });
1297
- reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1298
- const shouldHydrate = attr?.hydrate !== false;
1299
- const abortedState = { aborted: false };
1300
- const isBenignSocketAbort = (e) => {
1301
- const msg = String(e?.message ?? e ?? "");
1302
- return REGEX.BENIGN_NET_ERR.test(msg);
1303
- };
1304
- const writable = new PassThrough();
1305
- writable.on("error", (err) => {
1306
- if (!isBenignSocketAbort(err)) logger.error("PassThrough error:", { error: err });
1307
- });
1308
- reply.raw.on("error", (err) => {
1309
- if (!isBenignSocketAbort(err)) logger.error("HTTP socket error:", { error: err });
1292
+ reply.code(204).send();
1293
+ });
1294
+ },
1295
+ { name: "taujs-csp-report-plugin" }
1296
+ );
1297
+
1298
+ // src/utils/AssetManager.ts
1299
+ import { readFile } from "fs/promises";
1300
+ import path2 from "path";
1301
+ import { pathToFileURL } from "url";
1302
+
1303
+ // src/utils/Templates.ts
1304
+ var CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
1305
+ async function collectStyle(server, entries) {
1306
+ const urls = await collectStyleUrls(server, entries);
1307
+ const codes = await Promise.all(
1308
+ urls.map(async (url) => {
1309
+ const res = await server.transformRequest(url + "?direct");
1310
+ return [`/* [collectStyle] ${url} */`, res?.code];
1311
+ })
1312
+ );
1313
+ return codes.flat().filter(Boolean).join("\n\n");
1314
+ }
1315
+ async function collectStyleUrls(server, entries) {
1316
+ const visited = /* @__PURE__ */ new Set();
1317
+ async function traverse(url) {
1318
+ const [, id] = await server.moduleGraph.resolveUrl(url);
1319
+ if (visited.has(id)) return;
1320
+ visited.add(id);
1321
+ const mod = server.moduleGraph.getModuleById(id);
1322
+ if (!mod) return;
1323
+ await Promise.all([...mod.importedModules].map((childMod) => traverse(childMod.url)));
1324
+ }
1325
+ await Promise.all(entries.map((e) => server.transformRequest(e)));
1326
+ await Promise.all(entries.map((url) => traverse(url)));
1327
+ return [...visited].filter((url) => url.match(CSS_LANGS_RE));
1328
+ }
1329
+ function renderPreloadLinks(ssrManifest, basePath = "") {
1330
+ const seen = /* @__PURE__ */ new Set();
1331
+ let links = "";
1332
+ for (const moduleId in ssrManifest) {
1333
+ const files = ssrManifest[moduleId];
1334
+ if (files) {
1335
+ files.forEach((file) => {
1336
+ if (!seen.has(file)) {
1337
+ seen.add(file);
1338
+ links += renderPreloadLink(basePath ? `${basePath}/${file}` : `${file}`);
1339
+ }
1310
1340
  });
1311
- writable.pipe(reply.raw, { end: false });
1312
- let finalData = void 0;
1313
- renderStream(
1314
- writable,
1315
- {
1316
- onHead: (headContent) => {
1317
- let aggregateHeadContent = headContent;
1318
- if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
1319
- if (manifest && cssLink) aggregateHeadContent += cssLink;
1320
- return reply.raw.write(`${templateParts.beforeHead}${aggregateHeadContent}${templateParts.afterHead}${templateParts.beforeBody}`);
1321
- },
1322
- onShellReady: () => {
1323
- },
1324
- onAllReady: (data) => {
1325
- if (!abortedState.aborted) finalData = data;
1326
- },
1327
- onError: (err) => {
1328
- if (abortedState.aborted || isBenignSocketAbort(err)) {
1329
- logger.warn("Client disconnected before stream finished");
1330
- try {
1331
- if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy();
1332
- } catch (e) {
1333
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1341
+ }
1342
+ }
1343
+ return links;
1344
+ }
1345
+ function renderPreloadLink(file) {
1346
+ const fileType = file.match(/\.(js|css|woff2?|gif|jpe?g|png|svg)$/)?.[1];
1347
+ switch (fileType) {
1348
+ case "js":
1349
+ return `<link rel="modulepreload" href="${file}">`;
1350
+ case "css":
1351
+ return `<link rel="stylesheet" href="${file}">`;
1352
+ case "woff":
1353
+ case "woff2":
1354
+ return `<link rel="preload" href="${file}" as="font" type="font/${fileType}" crossorigin>`;
1355
+ case "gif":
1356
+ case "jpeg":
1357
+ case "jpg":
1358
+ case "png":
1359
+ return `<link rel="preload" href="${file}" as="image" type="image/${fileType}">`;
1360
+ case "svg":
1361
+ return `<link rel="preload" href="${file}" as="image" type="image/svg+xml">`;
1362
+ default:
1363
+ return "";
1364
+ }
1365
+ }
1366
+ function getCssLinks(manifest, basePath = "") {
1367
+ const seen = /* @__PURE__ */ new Set();
1368
+ const styles = [];
1369
+ for (const key in manifest) {
1370
+ const entry = manifest[key];
1371
+ if (entry && entry.css) {
1372
+ for (const cssFile of entry.css) {
1373
+ if (!seen.has(cssFile)) {
1374
+ seen.add(cssFile);
1375
+ styles.push(`<link rel="preload stylesheet" as="style" type="text/css" href="${basePath}/${cssFile}">`);
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1380
+ return styles.join("\n");
1381
+ }
1382
+ var overrideCSSHMRConsoleError = () => {
1383
+ const originalConsoleError = console.error;
1384
+ console.error = function(message, ...optionalParams) {
1385
+ if (typeof message === "string" && message.includes("css hmr is not supported in runtime mode")) return;
1386
+ originalConsoleError.apply(console, [message, ...optionalParams]);
1387
+ };
1388
+ };
1389
+ var ensureNonNull = (value, errorMessage) => {
1390
+ if (value === void 0 || value === null) throw new Error(errorMessage);
1391
+ return value;
1392
+ };
1393
+ function processTemplate(template) {
1394
+ const [headSplit, bodySplit] = template.split(SSRTAG.ssrHead);
1395
+ if (typeof bodySplit === "undefined") throw new Error(`Template is missing ${SSRTAG.ssrHead} marker.`);
1396
+ const [beforeBody, afterBody] = bodySplit.split(SSRTAG.ssrHtml);
1397
+ if (typeof beforeBody === "undefined" || typeof afterBody === "undefined") throw new Error(`Template is missing ${SSRTAG.ssrHtml} marker.`);
1398
+ return {
1399
+ beforeHead: headSplit,
1400
+ afterHead: "",
1401
+ beforeBody: beforeBody.replace(/\s*$/, ""),
1402
+ afterBody: afterBody.replace(/^\s*/, "")
1403
+ };
1404
+ }
1405
+ var rebuildTemplate = (parts, headContent, bodyContent) => {
1406
+ return `${parts.beforeHead}${headContent}${parts.afterHead}${parts.beforeBody}${bodyContent}${parts.afterBody}`;
1407
+ };
1408
+
1409
+ // src/utils/AssetManager.ts
1410
+ var createMaps = () => ({
1411
+ bootstrapModules: /* @__PURE__ */ new Map(),
1412
+ cssLinks: /* @__PURE__ */ new Map(),
1413
+ manifests: /* @__PURE__ */ new Map(),
1414
+ preloadLinks: /* @__PURE__ */ new Map(),
1415
+ renderModules: /* @__PURE__ */ new Map(),
1416
+ ssrManifests: /* @__PURE__ */ new Map(),
1417
+ templates: /* @__PURE__ */ new Map()
1418
+ });
1419
+ var processConfigs = (configs, baseClientRoot, templateDefaults) => {
1420
+ return configs.map((config) => {
1421
+ const clientRoot = path2.resolve(baseClientRoot, config.entryPoint);
1422
+ return {
1423
+ clientRoot,
1424
+ entryPoint: config.entryPoint,
1425
+ entryClient: config.entryClient || templateDefaults.defaultEntryClient,
1426
+ entryServer: config.entryServer || templateDefaults.defaultEntryServer,
1427
+ htmlTemplate: config.htmlTemplate || templateDefaults.defaultHtmlTemplate,
1428
+ appId: config.appId
1429
+ };
1430
+ });
1431
+ };
1432
+ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssLinks, manifests, preloadLinks, renderModules, ssrManifests, templates, opts = {}) => {
1433
+ const logger = opts.logger ?? createLogger({
1434
+ debug: opts.debug,
1435
+ includeContext: true
1436
+ });
1437
+ for (const config of processedConfigs) {
1438
+ const { clientRoot, entryClient, entryServer, htmlTemplate } = config;
1439
+ try {
1440
+ const templateHtmlPath = path2.join(clientRoot, htmlTemplate);
1441
+ const templateHtml = await readFile(templateHtmlPath, "utf-8");
1442
+ templates.set(clientRoot, templateHtml);
1443
+ const relativeBasePath = path2.relative(baseClientRoot, clientRoot).replace(/\\/g, "/");
1444
+ const adjustedRelativePath = relativeBasePath ? `/${relativeBasePath}` : "";
1445
+ if (!isDevelopment) {
1446
+ try {
1447
+ const manifestPath = path2.join(clientRoot, ".vite/manifest.json");
1448
+ const manifestContent = await readFile(manifestPath, "utf-8");
1449
+ const manifest = JSON.parse(manifestContent);
1450
+ manifests.set(clientRoot, manifest);
1451
+ const ssrManifestPath = path2.join(clientRoot, ".vite/ssr-manifest.json");
1452
+ const ssrManifestContent = await readFile(ssrManifestPath, "utf-8");
1453
+ const ssrManifest = JSON.parse(ssrManifestContent);
1454
+ ssrManifests.set(clientRoot, ssrManifest);
1455
+ const entryClientFile = manifest[`${entryClient}.tsx`]?.file;
1456
+ if (!entryClientFile) {
1457
+ throw AppError.internal(`Entry client file not found in manifest for ${entryClient}.tsx`, {
1458
+ details: {
1459
+ clientRoot,
1460
+ entryClient,
1461
+ availableKeys: Object.keys(manifest)
1334
1462
  }
1335
- return;
1336
- }
1337
- abortedState.aborted = true;
1338
- logger.error("Critical rendering error during stream", {
1339
- error: normaliseError(err),
1340
- clientRoot,
1341
- url: req.url
1342
1463
  });
1343
- try {
1344
- ac?.abort?.();
1345
- } catch (e) {
1346
- logger.debug?.("stream teardown: abort() failed", { error: normaliseError(e) });
1347
- }
1348
- const reason = toReason(err);
1349
- try {
1350
- if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy(reason);
1351
- } catch (e) {
1352
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1353
- }
1354
1464
  }
1465
+ const bootstrapModule = `/${adjustedRelativePath}/${entryClientFile}`.replace(/\/{2,}/g, "/");
1466
+ bootstrapModules.set(clientRoot, bootstrapModule);
1467
+ const preloadLink = renderPreloadLinks(ssrManifest, adjustedRelativePath);
1468
+ preloadLinks.set(clientRoot, preloadLink);
1469
+ const cssLink = getCssLinks(manifest, adjustedRelativePath);
1470
+ cssLinks.set(clientRoot, cssLink);
1471
+ const renderModulePath = path2.join(clientRoot, `${entryServer}.js`);
1472
+ const moduleUrl = pathToFileURL(renderModulePath).href;
1473
+ try {
1474
+ const importedModule = await import(moduleUrl);
1475
+ renderModules.set(clientRoot, importedModule);
1476
+ } catch (err) {
1477
+ throw AppError.internal(`Failed to load render module ${renderModulePath}`, {
1478
+ cause: err,
1479
+ details: { moduleUrl, clientRoot, entryServer }
1480
+ });
1481
+ }
1482
+ } catch (err) {
1483
+ if (err instanceof AppError) {
1484
+ logger.error(
1485
+ {
1486
+ error: { name: err.name, message: err.message, stack: err.stack, code: err.code },
1487
+ stage: "loadAssets:production"
1488
+ },
1489
+ "Asset load failed"
1490
+ );
1491
+ } else {
1492
+ logger.error(
1493
+ {
1494
+ error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
1495
+ stage: "loadAssets:production"
1496
+ },
1497
+ "Asset load failed"
1498
+ );
1499
+ }
1500
+ }
1501
+ } else {
1502
+ const bootstrapModule = `/${adjustedRelativePath}/${entryClient}`.replace(/\/{2,}/g, "/");
1503
+ bootstrapModules.set(clientRoot, bootstrapModule);
1504
+ }
1505
+ } catch (err) {
1506
+ logger.error(
1507
+ {
1508
+ error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
1509
+ stage: "loadAssets:config"
1355
1510
  },
1356
- initialDataInput,
1357
- req.url,
1358
- shouldHydrate ? bootstrapModule : void 0,
1359
- attr?.meta,
1360
- cspNonce,
1361
- ac.signal,
1362
- { logger: reqLogger }
1511
+ "Failed to process config"
1363
1512
  );
1364
- writable.on("finish", () => {
1365
- if (abortedState.aborted || reply.raw.writableEnded) return;
1366
- const data = finalData ?? {};
1367
- const initialDataScript = `<script${cspNonce ? ` nonce="${cspNonce}"` : ""}>window.__INITIAL_DATA__ = ${JSON.stringify(data).replace(
1368
- /</g,
1369
- "\\u003c"
1370
- )}; window.dispatchEvent(new Event('taujs:data-ready'));</script>`;
1371
- reply.raw.write(initialDataScript);
1372
- reply.raw.write(templateParts.afterBody);
1373
- reply.raw.end();
1374
- });
1375
1513
  }
1376
- } catch (err) {
1377
- if (err instanceof AppError) throw err;
1378
- throw AppError.internal("handleRender failed", err, {
1379
- url: req.url,
1380
- route: req.routeOptions?.url
1381
- });
1382
1514
  }
1383
1515
  };
1384
1516
 
1385
- // src/utils/HandleNotFound.ts
1386
- var handleNotFound = async (req, reply, processedConfigs, maps, opts = {}) => {
1387
- const logger = opts.logger ?? createLogger({
1388
- debug: opts.debug,
1389
- context: { component: "handle-not-found", url: req.url, method: req.method, traceId: req.id }
1517
+ // src/utils/DevServer.ts
1518
+ import path3 from "path";
1519
+ var setupDevServer = async (app, baseClientRoot, alias, debug, devNet) => {
1520
+ const logger = createLogger({
1521
+ context: { service: "setupDevServer" },
1522
+ debug,
1523
+ minLevel: "debug"
1390
1524
  });
1391
- try {
1392
- if (/\.\w+$/.test(req.raw.url ?? "")) {
1393
- logger.debug?.("ssr", "Delegating asset-like request to Fastify notFound handler", { url: req.raw.url });
1394
- return reply.callNotFound();
1525
+ const host = devNet?.host ?? process.env.HOST?.trim() ?? process.env.FASTIFY_ADDRESS?.trim() ?? "localhost";
1526
+ const hmrPort = devNet?.hmrPort ?? (Number(process.env.HMR_PORT) || 5174);
1527
+ const { createServer: createServer2 } = await import("vite");
1528
+ const viteDevServer = await createServer2({
1529
+ appType: "custom",
1530
+ css: {
1531
+ preprocessorOptions: {
1532
+ scss: {
1533
+ api: "modern-compiler"
1534
+ }
1535
+ }
1536
+ },
1537
+ mode: "development",
1538
+ plugins: [
1539
+ ...debug ? [
1540
+ {
1541
+ name: "\u03C4js-development-server-debug-logging",
1542
+ configureServer(server) {
1543
+ logger.debug("vite", `${CONTENT.TAG} Development server debug started`);
1544
+ server.middlewares.use((req, res, next) => {
1545
+ logger.debug(
1546
+ "vite",
1547
+ {
1548
+ method: req.method,
1549
+ url: req.url,
1550
+ host: req.headers.host,
1551
+ ua: req.headers["user-agent"]
1552
+ },
1553
+ "\u2190 rx"
1554
+ );
1555
+ res.on("finish", () => {
1556
+ logger.debug(
1557
+ "vite",
1558
+ {
1559
+ method: req.method,
1560
+ url: req.url,
1561
+ statusCode: res.statusCode
1562
+ },
1563
+ "\u2192 tx"
1564
+ );
1565
+ });
1566
+ next();
1567
+ });
1568
+ }
1569
+ }
1570
+ ] : []
1571
+ ],
1572
+ resolve: {
1573
+ alias: {
1574
+ "@client": path3.resolve(baseClientRoot),
1575
+ "@server": path3.resolve(__dirname),
1576
+ "@shared": path3.resolve(__dirname, "../shared"),
1577
+ ...alias
1578
+ }
1579
+ },
1580
+ root: baseClientRoot,
1581
+ server: {
1582
+ middlewareMode: true,
1583
+ hmr: {
1584
+ clientPort: hmrPort,
1585
+ host: host !== "localhost" ? host : void 0,
1586
+ port: hmrPort,
1587
+ protocol: "ws"
1588
+ }
1395
1589
  }
1396
- const defaultConfig = processedConfigs[0];
1397
- if (!defaultConfig) {
1398
- logger.error?.("No default configuration found", { configCount: processedConfigs.length, url: req.raw.url });
1399
- throw AppError.internal("No default configuration found", {
1400
- details: { configCount: processedConfigs.length, url: req.raw.url }
1590
+ });
1591
+ overrideCSSHMRConsoleError();
1592
+ app.addHook("onRequest", async (request, reply) => {
1593
+ await new Promise((resolve) => {
1594
+ viteDevServer.middlewares(request.raw, reply.raw, () => {
1595
+ if (!reply.sent) resolve();
1401
1596
  });
1402
- }
1403
- const { clientRoot } = defaultConfig;
1404
- const cspNonce = req.cspNonce ?? void 0;
1405
- const template = ensureNonNull(maps.templates.get(clientRoot), `Template not found for clientRoot: ${clientRoot}`);
1406
- const cssLink = maps.cssLinks.get(clientRoot);
1407
- const bootstrapModule = maps.bootstrapModules.get(clientRoot);
1408
- logger.debug?.("ssr", "Preparing not-found fallback HTML", {
1409
- clientRoot,
1410
- hasCssLink: Boolean(cssLink),
1411
- hasBootstrapModule: Boolean(bootstrapModule),
1412
- isDevelopment,
1413
- hasCspNonce: Boolean(cspNonce)
1414
1597
  });
1415
- let processedTemplate = template.replace(SSRTAG.ssrHead, "").replace(SSRTAG.ssrHtml, "");
1416
- if (!isDevelopment && cssLink) {
1417
- processedTemplate = processedTemplate.replace("</head>", `${cssLink}</head>`);
1418
- }
1419
- if (bootstrapModule) {
1420
- const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
1421
- processedTemplate = processedTemplate.replace("</body>", `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script></body>`);
1422
- }
1423
- logger.debug?.("ssr", "Sending not-found fallback HTML", { status: 200 });
1424
- return reply.status(200).type("text/html").send(processedTemplate);
1425
- } catch (err) {
1426
- logger.error?.("handleNotFound failed", { error: err, url: req.url, clientRoot: processedConfigs[0]?.clientRoot });
1427
- throw AppError.internal("handleNotFound failed", err, {
1428
- stage: "handleNotFound",
1429
- url: req.url,
1430
- clientRoot: processedConfigs[0]?.clientRoot
1431
- });
1432
- }
1598
+ });
1599
+ return viteDevServer;
1433
1600
  };
1434
1601
 
1435
- // src/security/CSPReporting.ts
1436
- var import_fastify_plugin2 = __toESM(require_plugin(), 1);
1437
- function sanitiseContext(ctx) {
1438
- return {
1439
- userAgent: ctx.userAgent,
1440
- ip: ctx.ip,
1441
- referer: ctx.referer,
1442
- timestamp: ctx.timestamp
1443
- // headers: ctx.headers,
1444
- };
1602
+ // src/utils/HandleRender.ts
1603
+ import path4 from "path";
1604
+ import { PassThrough } from "stream";
1605
+
1606
+ // src/utils/Telemetry.ts
1607
+ import crypto2 from "crypto";
1608
+ function createRequestContext(req, reply, baseLogger) {
1609
+ const raw = typeof req.headers["x-trace-id"] === "string" ? req.headers["x-trace-id"] : "";
1610
+ const traceId = raw && REGEX.SAFE_TRACE.test(raw) ? raw : typeof req.id === "string" ? req.id : crypto2.randomUUID();
1611
+ reply.header("x-trace-id", traceId);
1612
+ const anyLogger = baseLogger;
1613
+ const child = anyLogger.child;
1614
+ const logger = typeof child === "function" ? child.call(baseLogger, { traceId, url: req.url, method: req.method }) : baseLogger;
1615
+ const headers = Object.fromEntries(
1616
+ Object.entries(req.headers).map(([headerName, headerValue]) => {
1617
+ const normalisedValue = Array.isArray(headerValue) ? headerValue.join(",") : headerValue ?? "";
1618
+ return [headerName, normalisedValue];
1619
+ })
1620
+ );
1621
+ return { traceId, logger, headers };
1445
1622
  }
1446
- function logCspViolation(logger, report, context) {
1447
- logger.warn("CSP Violation", {
1448
- violation: {
1449
- documentUri: report["document-uri"],
1450
- violatedDirective: report["violated-directive"],
1451
- blockedUri: report["blocked-uri"],
1452
- sourceFile: report["source-file"],
1453
- line: report["line-number"],
1454
- column: report["column-number"],
1455
- scriptSample: report["script-sample"],
1456
- originalPolicy: report["original-policy"],
1457
- disposition: report.disposition
1458
- },
1459
- context: {
1460
- userAgent: context.userAgent,
1461
- ip: context.ip,
1462
- referer: context.referer,
1463
- timestamp: context.timestamp
1464
- }
1623
+
1624
+ // src/utils/HandleRender.ts
1625
+ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, opts = {}) => {
1626
+ const { viteDevServer } = opts;
1627
+ const logger = opts.logger ?? createLogger({
1628
+ debug: opts.debug,
1629
+ minLevel: isDevelopment ? "debug" : "info",
1630
+ includeContext: true,
1631
+ includeStack: (lvl) => lvl === "error" || isDevelopment
1465
1632
  });
1466
- }
1467
- var processCSPReport = (body, context, logger) => {
1468
1633
  try {
1469
- const reportData = body?.["csp-report"] || body;
1470
- if (!reportData || typeof reportData !== "object") {
1471
- logger.warn("Ignoring malformed CSP report", {
1472
- bodyType: typeof body,
1473
- context: sanitiseContext(context)
1474
- });
1634
+ if (/\.\w+$/.test(req.raw.url ?? "")) return reply.callNotFound();
1635
+ const url = req.url ? new URL(req.url, `http://${req.headers.host}`).pathname : "/";
1636
+ const matchedRoute = matchRoute(url, routeMatchers);
1637
+ const rawNonce = req.cspNonce;
1638
+ const cspNonce = rawNonce && rawNonce.length > 0 ? rawNonce : void 0;
1639
+ if (!matchedRoute) {
1640
+ reply.callNotFound();
1475
1641
  return;
1476
1642
  }
1477
- const documentUri = reportData["document-uri"] ?? reportData["documentURL"];
1478
- const violatedDirective = reportData["violated-directive"] ?? reportData["violatedDirective"];
1479
- if (!documentUri || !violatedDirective) {
1480
- logger.warn("Ignoring incomplete CSP report", {
1481
- hasDocumentUri: !!documentUri,
1482
- hasViolatedDirective: !!violatedDirective,
1483
- context: sanitiseContext(context)
1643
+ const { route, params } = matchedRoute;
1644
+ const { attr, appId } = route;
1645
+ const config = processedConfigs.find((c) => c.appId === appId);
1646
+ if (!config) {
1647
+ throw AppError.internal("No configuration found for the request", {
1648
+ details: {
1649
+ appId,
1650
+ availableAppIds: processedConfigs.map((c) => c.appId),
1651
+ url
1652
+ }
1484
1653
  });
1485
- return;
1486
1654
  }
1487
- const violation = {
1488
- "document-uri": String(documentUri),
1489
- "violated-directive": String(violatedDirective),
1490
- "blocked-uri": reportData["blocked-uri"] ?? reportData["blockedURL"] ?? "",
1491
- "source-file": reportData["source-file"] ?? reportData["sourceFile"],
1492
- "line-number": reportData["line-number"] ?? reportData["lineNumber"],
1493
- "column-number": reportData["column-number"] ?? reportData["columnNumber"],
1494
- "script-sample": reportData["script-sample"] ?? reportData["sample"],
1495
- "original-policy": reportData["original-policy"] ?? reportData["originalPolicy"] ?? "",
1496
- disposition: reportData.disposition ?? "enforce"
1497
- };
1498
- logCspViolation(logger, violation, context);
1499
- } catch (processingError) {
1500
- logger.warn("CSP report processing failed", {
1501
- error: processingError instanceof Error ? processingError.message : String(processingError),
1502
- bodyType: typeof body,
1503
- context: sanitiseContext(context)
1504
- });
1505
- }
1506
- };
1507
- var cspReportPlugin = (0, import_fastify_plugin2.default)(
1508
- async (fastify, opts) => {
1509
- const { onViolation } = opts;
1510
- if (!opts.path || typeof opts.path !== "string") throw AppError.badRequest("CSP report path is required and must be a string");
1511
- const logger = createLogger({
1512
- debug: opts.debug,
1513
- context: { service: "csp-reporting" },
1514
- minLevel: "info"
1515
- });
1516
- fastify.post(opts.path, async (req, reply) => {
1517
- const context = {
1518
- userAgent: req.headers["user-agent"],
1519
- ip: req.ip,
1520
- referer: req.headers.referer,
1521
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1522
- headers: req.headers,
1523
- __fastifyRequest: req
1524
- // onViolation callback
1525
- };
1655
+ const { clientRoot, entryServer } = config;
1656
+ let template = ensureNonNull(maps.templates.get(clientRoot), `Template not found for clientRoot: ${clientRoot}`);
1657
+ const bootstrapModule = maps.bootstrapModules.get(clientRoot);
1658
+ const cssLink = maps.cssLinks.get(clientRoot);
1659
+ const manifest = maps.manifests.get(clientRoot);
1660
+ const preloadLink = maps.preloadLinks.get(clientRoot);
1661
+ const ssrManifest = maps.ssrManifests.get(clientRoot);
1662
+ let renderModule;
1663
+ if (isDevelopment && viteDevServer) {
1526
1664
  try {
1527
- processCSPReport(req.body, context, logger);
1528
- const reportData = req.body?.["csp-report"] || req.body;
1529
- if (onViolation && reportData && typeof reportData === "object") {
1530
- const documentUri = reportData["document-uri"] ?? reportData["documentURL"];
1531
- const violatedDirective = reportData["violated-directive"] ?? reportData["violatedDirective"];
1532
- if (documentUri && violatedDirective) {
1533
- const violation = {
1534
- "document-uri": String(documentUri),
1535
- "violated-directive": String(violatedDirective),
1536
- "blocked-uri": reportData["blocked-uri"] ?? reportData["blockedURL"] ?? "",
1537
- "source-file": reportData["source-file"] ?? reportData["sourceFile"],
1538
- "line-number": reportData["line-number"] ?? reportData["lineNumber"],
1539
- "column-number": reportData["column-number"] ?? reportData["columnNumber"],
1540
- "script-sample": reportData["script-sample"] ?? reportData["sample"],
1541
- "original-policy": reportData["original-policy"] ?? reportData["originalPolicy"] ?? "",
1542
- disposition: reportData.disposition ?? "enforce"
1543
- };
1544
- onViolation(violation, req);
1545
- }
1665
+ template = template.replace(/<script type="module" src="\/@vite\/client"><\/script>/g, "");
1666
+ template = template.replace(/<style type="text\/css">[\s\S]*?<\/style>/g, "");
1667
+ const entryServerPath = path4.join(clientRoot, `${entryServer}.tsx`);
1668
+ const executedModule = await viteDevServer.ssrLoadModule(entryServerPath);
1669
+ renderModule = executedModule;
1670
+ const styles = await collectStyle(viteDevServer, [entryServerPath]);
1671
+ const styleNonce = cspNonce ? ` nonce="${cspNonce}"` : "";
1672
+ template = template?.replace("</head>", `<style type="text/css"${styleNonce}>${styles}</style></head>`);
1673
+ template = await viteDevServer.transformIndexHtml(url, template);
1674
+ } catch (error) {
1675
+ throw AppError.internal("Failed to load dev assets", { cause: error, details: { clientRoot, entryServer, url } });
1676
+ }
1677
+ } else {
1678
+ renderModule = maps.renderModules.get(clientRoot);
1679
+ if (!renderModule) throw AppError.internal(`Render module not found for clientRoot: ${clientRoot}. Module should have been preloaded.`);
1680
+ }
1681
+ const renderType = attr?.render ?? RENDERTYPE.ssr;
1682
+ const templateParts = processTemplate(template);
1683
+ const baseLogger = opts.logger ?? logger;
1684
+ const { traceId, logger: reqLogger, headers } = createRequestContext(req, reply, baseLogger);
1685
+ const ctx = { traceId, logger: reqLogger, headers };
1686
+ const initialDataInput = () => fetchInitialData(attr, params, serviceRegistry, ctx);
1687
+ if (renderType === RENDERTYPE.ssr) {
1688
+ const { renderSSR } = renderModule;
1689
+ if (!renderSSR) {
1690
+ throw AppError.internal("renderSSR function not found in module", {
1691
+ details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1692
+ });
1693
+ }
1694
+ const ac = new AbortController();
1695
+ const onAborted = () => ac.abort("client_aborted");
1696
+ req.raw.on("aborted", onAborted);
1697
+ reply.raw.on("close", () => {
1698
+ if (!reply.raw.writableEnded) ac.abort("socket_closed");
1699
+ });
1700
+ reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1701
+ if (ac.signal.aborted) {
1702
+ logger.warn("SSR skipped; already aborted", { url: req.url });
1703
+ return;
1704
+ }
1705
+ const initialDataResolved = await initialDataInput();
1706
+ let headContent = "";
1707
+ let appHtml = "";
1708
+ try {
1709
+ const res = await renderSSR(initialDataResolved, req.url, attr?.meta, ac.signal, { logger: reqLogger });
1710
+ headContent = res.headContent;
1711
+ appHtml = res.appHtml;
1712
+ } catch (err) {
1713
+ const msg = String(err?.message ?? err ?? "");
1714
+ const benign = REGEX.BENIGN_NET_ERR.test(msg);
1715
+ if (ac.signal.aborted || benign) {
1716
+ logger.warn("SSR aborted mid-render (benign)", { url: req.url, reason: msg });
1717
+ return;
1546
1718
  }
1719
+ logger.error("SSR render failed", { url: req.url, error: normaliseError(err) });
1720
+ throw err;
1721
+ }
1722
+ let aggregateHeadContent = headContent;
1723
+ if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
1724
+ if (manifest && cssLink) aggregateHeadContent += cssLink;
1725
+ const shouldHydrate = attr?.hydrate !== false;
1726
+ const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
1727
+ const initialDataScript = `<script${nonceAttr}>window.__INITIAL_DATA__ = ${JSON.stringify(initialDataResolved).replace(/</g, "\\u003c")};</script>`;
1728
+ const bootstrapScriptTag = shouldHydrate && bootstrapModule ? `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script>` : "";
1729
+ const safeAppHtml = appHtml.trim();
1730
+ const fullHtml = rebuildTemplate(templateParts, aggregateHeadContent, `${safeAppHtml}${initialDataScript}${bootstrapScriptTag}`);
1731
+ try {
1732
+ return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
1547
1733
  } catch (err) {
1548
- logger.warn("CSP reporting route failed", {
1549
- error: err instanceof Error ? err.message : String(err)
1550
- });
1734
+ const msg = String(err?.message ?? err ?? "");
1735
+ const benign = REGEX.BENIGN_NET_ERR.test(msg);
1736
+ if (!benign) logger.error("SSR send failed", { url: req.url, error: normaliseError(err) });
1737
+ else logger.warn("SSR send aborted (benign)", { url: req.url, reason: msg });
1738
+ return;
1551
1739
  }
1552
- reply.code(204).send();
1553
- });
1554
- },
1555
- { name: "taujs-csp-report-plugin" }
1556
- );
1557
-
1558
- // src/SSRServer.ts
1559
- var SSRServer = (0, import_fastify_plugin3.default)(
1560
- async (app, opts) => {
1561
- const { alias, configs, routes, serviceRegistry, clientRoot: baseClientRoot, security } = opts;
1562
- const logger = createLogger({
1563
- debug: opts.debug,
1564
- context: { component: "ssr-server" },
1565
- minLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
1566
- includeContext: true,
1567
- singleLine: true
1568
- });
1569
- const maps = createMaps();
1570
- const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
1571
- const routeMatchers = createRouteMatchers(routes);
1572
- let viteDevServer;
1573
- await loadAssets(
1574
- processedConfigs,
1575
- baseClientRoot,
1576
- maps.bootstrapModules,
1577
- maps.cssLinks,
1578
- maps.manifests,
1579
- maps.preloadLinks,
1580
- maps.renderModules,
1581
- maps.ssrManifests,
1582
- maps.templates,
1583
- {
1584
- debug: opts.debug,
1585
- logger
1740
+ } else {
1741
+ const { renderStream } = renderModule;
1742
+ if (!renderStream) {
1743
+ throw AppError.internal("renderStream function not found in module", {
1744
+ details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1745
+ });
1586
1746
  }
1587
- );
1588
- if (opts.registerStaticAssets && typeof opts.registerStaticAssets === "object") {
1589
- const { plugin, options } = opts.registerStaticAssets;
1590
- await app.register(plugin, {
1591
- root: baseClientRoot,
1592
- prefix: "/",
1593
- index: false,
1594
- wildcard: false,
1595
- ...options ?? {}
1747
+ const cspHeader = reply.getHeader("Content-Security-Policy");
1748
+ reply.raw.writeHead(200, {
1749
+ "Content-Security-Policy": cspHeader,
1750
+ "Content-Type": "text/html; charset=utf-8"
1596
1751
  });
1597
- }
1598
- if (security?.csp?.reporting) {
1599
- app.register(cspReportPlugin, {
1600
- path: security.csp.reporting.endpoint,
1601
- debug: opts.debug,
1602
- logger,
1603
- onViolation: security.csp.reporting.onViolation
1752
+ const ac = new AbortController();
1753
+ const onAborted = () => ac.abort();
1754
+ req.raw.on("aborted", onAborted);
1755
+ reply.raw.on("close", () => {
1756
+ if (!reply.raw.writableEnded) ac.abort();
1604
1757
  });
1605
- }
1606
- app.register(cspPlugin, {
1607
- directives: opts.security?.csp?.directives,
1608
- generateCSP: opts.security?.csp?.generateCSP,
1609
- routeMatchers,
1610
- debug: opts.debug
1611
- });
1612
- if (isDevelopment) viteDevServer = await setupDevServer(app, baseClientRoot, alias, opts.debug, opts.devNet);
1613
- app.addHook("onRequest", createAuthHook(routeMatchers, logger));
1614
- app.get("/*", async (req, reply) => {
1615
- await handleRender(req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, {
1616
- debug: opts.debug,
1617
- logger,
1618
- viteDevServer
1758
+ reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1759
+ const shouldHydrate = attr?.hydrate !== false;
1760
+ const abortedState = { aborted: false };
1761
+ const isBenignSocketAbort = (e) => {
1762
+ const msg = String(e?.message ?? e ?? "");
1763
+ return REGEX.BENIGN_NET_ERR.test(msg);
1764
+ };
1765
+ const writable = new PassThrough();
1766
+ writable.on("error", (err) => {
1767
+ if (!isBenignSocketAbort(err)) logger.error("PassThrough error:", { error: err });
1619
1768
  });
1620
- });
1621
- app.setNotFoundHandler(async (req, reply) => {
1622
- await handleNotFound(
1623
- req,
1624
- reply,
1625
- processedConfigs,
1769
+ reply.raw.on("error", (err) => {
1770
+ if (!isBenignSocketAbort(err)) logger.error("HTTP socket error:", { error: err });
1771
+ });
1772
+ writable.pipe(reply.raw, { end: false });
1773
+ let finalData = void 0;
1774
+ renderStream(
1775
+ writable,
1626
1776
  {
1627
- cssLinks: maps.cssLinks,
1628
- bootstrapModules: maps.bootstrapModules,
1629
- templates: maps.templates
1777
+ onHead: (headContent) => {
1778
+ let aggregateHeadContent = headContent;
1779
+ if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
1780
+ if (manifest && cssLink) aggregateHeadContent += cssLink;
1781
+ return reply.raw.write(`${templateParts.beforeHead}${aggregateHeadContent}${templateParts.afterHead}${templateParts.beforeBody}`);
1782
+ },
1783
+ onShellReady: () => {
1784
+ },
1785
+ onAllReady: (data) => {
1786
+ if (!abortedState.aborted) finalData = data;
1787
+ },
1788
+ onError: (err) => {
1789
+ if (abortedState.aborted || isBenignSocketAbort(err)) {
1790
+ logger.warn("Client disconnected before stream finished");
1791
+ try {
1792
+ if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy();
1793
+ } catch (e) {
1794
+ logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1795
+ }
1796
+ return;
1797
+ }
1798
+ abortedState.aborted = true;
1799
+ logger.error("Critical rendering error during stream", {
1800
+ error: normaliseError(err),
1801
+ clientRoot,
1802
+ url: req.url
1803
+ });
1804
+ try {
1805
+ ac?.abort?.();
1806
+ } catch (e) {
1807
+ logger.debug?.("stream teardown: abort() failed", { error: normaliseError(e) });
1808
+ }
1809
+ const reason = toReason(err);
1810
+ try {
1811
+ if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy(reason);
1812
+ } catch (e) {
1813
+ logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1814
+ }
1815
+ }
1630
1816
  },
1631
- {
1632
- debug: opts.debug,
1633
- logger
1634
- }
1817
+ initialDataInput,
1818
+ req.url,
1819
+ shouldHydrate ? bootstrapModule : void 0,
1820
+ attr?.meta,
1821
+ cspNonce,
1822
+ ac.signal,
1823
+ { logger: reqLogger }
1635
1824
  );
1636
- });
1637
- app.setErrorHandler((err, req, reply) => {
1638
- const e = AppError.from(err);
1639
- logger.error(e.message, {
1640
- kind: e.kind,
1641
- httpStatus: e.httpStatus,
1642
- ...e.code && { code: e.code },
1643
- details: e.details,
1644
- method: req.method,
1645
- url: req.url,
1646
- route: req.routeOptions?.url,
1647
- stack: e.stack
1648
- });
1649
- if (!reply.raw.headersSent) {
1650
- const { status, body } = toHttp(e);
1651
- reply.status(status).send(body);
1652
- } else {
1825
+ writable.on("finish", () => {
1826
+ if (abortedState.aborted || reply.raw.writableEnded) return;
1827
+ const data = finalData ?? {};
1828
+ const initialDataScript = `<script${cspNonce ? ` nonce="${cspNonce}"` : ""}>window.__INITIAL_DATA__ = ${JSON.stringify(data).replace(
1829
+ /</g,
1830
+ "\\u003c"
1831
+ )}; window.dispatchEvent(new Event('taujs:data-ready'));</script>`;
1832
+ reply.raw.write(initialDataScript);
1833
+ reply.raw.write(templateParts.afterBody);
1653
1834
  reply.raw.end();
1654
- }
1655
- });
1656
- },
1657
- { name: "\u03C4js-ssr-server" }
1658
- );
1659
-
1660
- // src/CreateServer.ts
1661
- var import_picocolors4 = __toESM(require_picocolors(), 1);
1662
- import path5 from "path";
1663
- import { performance as performance2 } from "perf_hooks";
1664
- import fastifyStatic from "@fastify/static";
1665
- import Fastify from "fastify";
1666
-
1667
- // src/Setup.ts
1668
- import { performance } from "perf_hooks";
1669
- var extractBuildConfigs = (config) => {
1670
- return config.apps.map(({ appId, entryPoint, plugins }) => ({
1671
- appId,
1672
- entryPoint,
1673
- plugins
1674
- }));
1675
- };
1676
- var extractRoutes = (taujsConfig) => {
1677
- const t0 = performance.now();
1678
- const allRoutes = [];
1679
- const apps = [];
1680
- const warnings = [];
1681
- const pathTracker = /* @__PURE__ */ new Map();
1682
- for (const app of taujsConfig.apps) {
1683
- const appRoutes = (app.routes ?? []).map((route) => {
1684
- const fullRoute = { ...route, appId: app.appId };
1685
- if (!pathTracker.has(route.path)) pathTracker.set(route.path, []);
1686
- pathTracker.get(route.path).push(app.appId);
1687
- return fullRoute;
1688
- });
1689
- apps.push({ appId: app.appId, routeCount: appRoutes.length });
1690
- allRoutes.push(...appRoutes);
1691
- }
1692
- for (const [path6, appIds] of pathTracker.entries()) {
1693
- if (appIds.length > 1) warnings.push(`Route path "${path6}" is declared in multiple apps: ${appIds.join(", ")}`);
1694
- }
1695
- const sortedRoutes = allRoutes.sort((a, b) => computeScore(b.path) - computeScore(a.path));
1696
- const durationMs = performance.now() - t0;
1697
- return {
1698
- routes: sortedRoutes,
1699
- apps,
1700
- totalRoutes: allRoutes.length,
1701
- durationMs,
1702
- warnings
1703
- };
1704
- };
1705
- var extractSecurity = (taujsConfig) => {
1706
- const t0 = performance.now();
1707
- const user = taujsConfig.security ?? {};
1708
- const userCsp = user.csp;
1709
- const hasExplicitCSP = !!userCsp;
1710
- const normalisedCsp = userCsp ? {
1711
- defaultMode: userCsp.defaultMode ?? "merge",
1712
- directives: userCsp.directives,
1713
- generateCSP: userCsp.generateCSP,
1714
- reporting: userCsp.reporting ? {
1715
- endpoint: userCsp.reporting.endpoint,
1716
- onViolation: userCsp.reporting.onViolation,
1717
- reportOnly: userCsp.reporting.reportOnly ?? false
1718
- } : void 0
1719
- } : void 0;
1720
- const security = { csp: normalisedCsp };
1721
- const summary = {
1722
- mode: hasExplicitCSP ? "explicit" : "dev-defaults",
1723
- defaultMode: normalisedCsp?.defaultMode ?? "merge",
1724
- hasReporting: !!normalisedCsp?.reporting?.endpoint,
1725
- reportOnly: !!normalisedCsp?.reporting?.reportOnly
1726
- };
1727
- const durationMs = performance.now() - t0;
1728
- return {
1729
- security,
1730
- durationMs,
1731
- hasExplicitCSP,
1732
- summary
1733
- };
1734
- };
1735
- function printConfigSummary(logger, apps, configsCount, totalRoutes, durationMs, warnings) {
1736
- logger.info(`${CONTENT.TAG} [config] Loaded ${configsCount} app(s), ${totalRoutes} route(s) in ${durationMs.toFixed(1)}ms`);
1737
- apps.forEach((a) => logger.debug("routes", `\u2022 ${a.appId}: ${a.routeCount} route(s)`));
1738
- warnings.forEach((w) => logger.warn(`${CONTENT.TAG} [warn] ${w}`));
1739
- }
1740
- function printSecuritySummary(logger, routes, security, hasExplicitCSP, securityDurationMs) {
1741
- const total = routes.length;
1742
- const disabled = routes.filter((r) => r.attr?.middleware?.csp === false).length;
1743
- const custom = routes.filter((r) => {
1744
- const v = r.attr?.middleware?.csp;
1745
- return v !== void 0 && v !== false;
1746
- }).length;
1747
- const enabled = total - disabled;
1748
- const hasReporting = !!security.csp?.reporting?.endpoint;
1749
- const mode = security.csp?.defaultMode ?? "merge";
1750
- let status = "configured";
1751
- let detail = "";
1752
- if (hasExplicitCSP) {
1753
- detail = `explicit, mode=${mode}`;
1754
- if (hasReporting) detail += ", reporting";
1755
- if (custom > 0) detail += `, ${custom} route override(s)`;
1756
- } else {
1757
- if (process.env.NODE_ENV === "production") {
1758
- logger.warn("(consider explicit config for production)");
1759
- }
1760
- }
1761
- logger.info(`${CONTENT.TAG} [security] CSP ${status} (${enabled}/${total} routes) in ${securityDurationMs.toFixed(1)}ms`);
1762
- }
1763
- function printContractReport(logger, report) {
1764
- for (const r of report.items) {
1765
- const line = `${CONTENT.TAG} [security][${r.key}] ${r.message}`;
1766
- if (r.status === "error") {
1767
- logger.error(line);
1768
- } else if (r.status === "warning") {
1769
- logger.warn(line);
1770
- } else if (r.status === "skipped") {
1771
- logger.debug(r.key, line);
1772
- } else {
1773
- logger.info(line);
1835
+ });
1774
1836
  }
1837
+ } catch (err) {
1838
+ if (err instanceof AppError) throw err;
1839
+ throw AppError.internal("handleRender failed", err, {
1840
+ url: req.url,
1841
+ route: req.routeOptions?.url
1842
+ });
1775
1843
  }
1776
- }
1777
- var computeScore = (path6) => {
1778
- return path6.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
1779
1844
  };
1780
1845
 
1781
- // src/network/Network.ts
1782
- var import_picocolors3 = __toESM(require_picocolors(), 1);
1783
- import { networkInterfaces } from "os";
1784
- var isPrivateIPv4 = (addr) => {
1785
- if (!/^\d+\.\d+\.\d+\.\d+$/.test(addr)) return false;
1786
- const [a, b, _c, _d] = addr.split(".").map(Number);
1787
- if (a === 10) return true;
1788
- if (a === 192 && b === 168) return true;
1789
- if (a === 172 && b >= 16 && b <= 31) return true;
1790
- return false;
1791
- };
1792
- var bannerPlugin = async (fastify, options) => {
1793
- const logger = createLogger({ debug: options.debug });
1794
- const dbgNetwork = logger.isDebugEnabled("network");
1795
- fastify.decorate("showBanner", function showBanner() {
1796
- const addr = this.server.address();
1797
- if (!addr || typeof addr === "string") return;
1798
- const { address, port } = addr;
1799
- const boundHost = address === "::1" ? "localhost" : address === "::" ? "::" : address === "0.0.0.0" ? "0.0.0.0" : address;
1800
- console.log(`\u2503 Local ${import_picocolors3.default.bold(`http://localhost:${port}/`)}`);
1801
- if (boundHost === "localhost" || boundHost === "127.0.0.1") {
1802
- console.log("\u2503 Network use --host to expose\n");
1803
- return;
1804
- }
1805
- const nets = networkInterfaces();
1806
- let networkAddress = null;
1807
- for (const ifaces of Object.values(nets)) {
1808
- if (!ifaces) continue;
1809
- for (const iface of ifaces) {
1810
- if (iface.internal || iface.family !== "IPv4") continue;
1811
- if (isPrivateIPv4(iface.address)) {
1812
- networkAddress = iface.address;
1813
- break;
1814
- }
1815
- if (!networkAddress) networkAddress = iface.address;
1816
- }
1817
- if (networkAddress && isPrivateIPv4(networkAddress)) break;
1846
+ // src/utils/HandleNotFound.ts
1847
+ var handleNotFound = async (req, reply, processedConfigs, maps, opts = {}) => {
1848
+ const logger = opts.logger ?? createLogger({
1849
+ debug: opts.debug,
1850
+ context: { component: "handle-not-found", url: req.url, method: req.method, traceId: req.id }
1851
+ });
1852
+ try {
1853
+ if (/\.\w+$/.test(req.raw.url ?? "")) {
1854
+ logger.debug?.("ssr", { url: req.raw.url }, "Delegating asset-like request to Fastify notFound handler");
1855
+ return reply.callNotFound();
1818
1856
  }
1819
- if (networkAddress) {
1820
- console.log(`\u2503 Network http://${networkAddress}:${port}/
1821
- `);
1822
- if (dbgNetwork) logger.warn(import_picocolors3.default.yellow(`${CONTENT.TAG} [network] Dev server exposed on network - for local testing only.`));
1857
+ const defaultConfig = processedConfigs[0];
1858
+ if (!defaultConfig) {
1859
+ logger.error?.({ configCount: processedConfigs.length, url: req.raw.url }, "No default configuration found");
1860
+ throw AppError.internal("No default configuration found", {
1861
+ details: { configCount: processedConfigs.length, url: req.raw.url }
1862
+ });
1823
1863
  }
1824
- logger.info(import_picocolors3.default.green(`${CONTENT.TAG} [network] Bound to host: ${boundHost}`));
1825
- });
1826
- fastify.addHook("onReady", async function() {
1827
- if (this.server.listening) {
1828
- this.showBanner();
1829
- return;
1864
+ const { clientRoot } = defaultConfig;
1865
+ const cspNonce = req.cspNonce ?? void 0;
1866
+ const template = ensureNonNull(maps.templates.get(clientRoot), `Template not found for clientRoot: ${clientRoot}`);
1867
+ const cssLink = maps.cssLinks.get(clientRoot);
1868
+ const bootstrapModule = maps.bootstrapModules.get(clientRoot);
1869
+ logger.debug?.(
1870
+ "ssr",
1871
+ {
1872
+ clientRoot,
1873
+ hasCssLink: !!cssLink,
1874
+ hasBootstrapModule: !!bootstrapModule,
1875
+ isDevelopment,
1876
+ hasCspNonce: !!cspNonce
1877
+ },
1878
+ "Preparing not-found fallback HTML"
1879
+ );
1880
+ let processedTemplate = template.replace(SSRTAG.ssrHead, "").replace(SSRTAG.ssrHtml, "");
1881
+ if (!isDevelopment && cssLink) {
1882
+ processedTemplate = processedTemplate.replace("</head>", `${cssLink}</head>`);
1830
1883
  }
1831
- this.server.once("listening", () => this.showBanner());
1832
- });
1833
- };
1834
-
1835
- // src/network/CLI.ts
1836
- function readFlag(argv, keys, bareValue) {
1837
- const end = argv.indexOf("--");
1838
- const limit = end === -1 ? argv.length : end;
1839
- for (let i = 0; i < limit; i++) {
1840
- const arg = argv[i];
1841
- for (const key of keys) {
1842
- if (arg === key) {
1843
- const next = argv[i + 1];
1844
- if (!next || next.startsWith("-")) return bareValue;
1845
- return next.trim();
1846
- }
1847
- const pref = `${key}=`;
1848
- if (arg && arg.startsWith(pref)) {
1849
- const v = arg.slice(pref.length).trim();
1850
- return v || bareValue;
1851
- }
1884
+ if (bootstrapModule) {
1885
+ const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
1886
+ processedTemplate = processedTemplate.replace("</body>", `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script></body>`);
1852
1887
  }
1888
+ logger.debug?.("ssr", { status: 200 }, "Sending not-found fallback HTML");
1889
+ return reply.status(200).type("text/html").send(processedTemplate);
1890
+ } catch (err) {
1891
+ logger.error?.({ error: err, url: req.url, clientRoot: processedConfigs[0]?.clientRoot }, "handleNotFound failed");
1892
+ throw AppError.internal("handleNotFound failed", err, {
1893
+ stage: "handleNotFound",
1894
+ url: req.url,
1895
+ clientRoot: processedConfigs[0]?.clientRoot
1896
+ });
1853
1897
  }
1854
- return void 0;
1898
+ };
1899
+
1900
+ // src/utils/StaticAssets.ts
1901
+ function normalizeStaticAssets(reg) {
1902
+ if (!reg) return [];
1903
+ return Array.isArray(reg) ? reg : [reg];
1855
1904
  }
1856
- function resolveNet(input) {
1857
- const env = process.env;
1858
- const argv = process.argv;
1859
- let host = "localhost";
1860
- let port = 5173;
1861
- let hmrPort = 5174;
1862
- if (input?.host) host = input.host;
1863
- if (Number.isFinite(input?.port)) port = Number(input.port);
1864
- if (Number.isFinite(input?.hmrPort)) hmrPort = Number(input.hmrPort);
1865
- if (env.HOST?.trim()) host = env.HOST.trim();
1866
- else if (env.FASTIFY_ADDRESS?.trim()) host = env.FASTIFY_ADDRESS.trim();
1867
- if (env.PORT) port = Number(env.PORT) || port;
1868
- if (env.FASTIFY_PORT) port = Number(env.FASTIFY_PORT) || port;
1869
- if (env.HMR_PORT) hmrPort = Number(env.HMR_PORT) || hmrPort;
1870
- const cliHost = readFlag(argv, ["--host", "--hostname", "-H"], "0.0.0.0");
1871
- const cliPort = readFlag(argv, ["--port", "-p"]);
1872
- const cliHMR = readFlag(argv, ["--hmr-port"]);
1873
- if (cliHost) host = cliHost;
1874
- if (cliPort) port = Number(cliPort) || port;
1875
- if (cliHMR) hmrPort = Number(cliHMR) || hmrPort;
1876
- if (host === "true" || host === "") host = "0.0.0.0";
1877
- return { host, port, hmrPort };
1905
+ function prefixWeight(prefix) {
1906
+ if (typeof prefix !== "string" || prefix === "/" || prefix.length === 0) return 0;
1907
+ return prefix.split("/").filter(Boolean).length;
1878
1908
  }
1879
-
1880
- // src/security/VerifyMiddleware.ts
1881
- var isAuthRequired = (route) => Boolean(route.attr?.middleware?.auth);
1882
- var hasAuthenticate = (app) => typeof app.authenticate === "function";
1883
- function formatCspLoadedMsg(hasGlobal, custom) {
1884
- if (hasGlobal) {
1885
- return custom > 0 ? `Loaded global config with ${custom} route override(s)` : "Loaded global config";
1909
+ async function registerStaticAssets(app, baseClientRoot, reg, defaults) {
1910
+ const entries = normalizeStaticAssets(reg).map(({ plugin, options }) => ({
1911
+ plugin,
1912
+ options: {
1913
+ root: baseClientRoot,
1914
+ prefix: "/",
1915
+ index: false,
1916
+ wildcard: false,
1917
+ ...defaults ?? {},
1918
+ ...options ?? {}
1919
+ }
1920
+ }));
1921
+ entries.sort((a, b) => prefixWeight(b.options?.prefix) - prefixWeight(a.options?.prefix));
1922
+ for (const { plugin, options } of entries) {
1923
+ await app.register(plugin, options);
1886
1924
  }
1887
- return custom > 0 ? `Loaded development defaults with ${custom} route override(s)` : "Loaded development defaults";
1888
1925
  }
1889
- var verifyContracts = (app, routes, contracts, security) => {
1890
- const items = [];
1891
- for (const contract of contracts) {
1892
- const isRequired = contract.required(routes, security);
1893
- if (!isRequired) {
1894
- items.push({
1895
- key: contract.key,
1896
- status: "skipped",
1897
- message: `No routes require "${contract.key}"`
1898
- });
1899
- continue;
1900
- }
1901
- if (!contract.verify(app)) {
1902
- const msg = `[\u03C4js] ${contract.errorMessage}`;
1903
- items.push({ key: contract.key, status: "error", message: msg });
1904
- throw new Error(msg);
1905
- }
1906
- if (contract.key === "csp") {
1907
- const total = routes.length;
1908
- const disabled = routes.filter((r) => r.attr?.middleware?.csp === false).length;
1909
- const custom = routes.filter((r) => {
1910
- const v = r.attr?.middleware?.csp;
1911
- return v !== void 0 && v !== false;
1912
- }).length;
1913
- const enabled = total - disabled;
1914
- const hasGlobal = !!security?.csp;
1915
- let status = "verified";
1916
- let tail = "";
1917
- if (!hasGlobal && process.env.NODE_ENV === "production") {
1918
- status = "warning";
1919
- tail = " (consider adding global CSP for production)";
1926
+
1927
+ // src/SSRServer.ts
1928
+ var SSRServer = (0, import_fastify_plugin3.default)(
1929
+ async (app, opts) => {
1930
+ const { alias, configs, routes, serviceRegistry, clientRoot: baseClientRoot, security } = opts;
1931
+ const logger = createLogger({
1932
+ debug: opts.debug,
1933
+ context: { component: "ssr-server" },
1934
+ minLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
1935
+ includeContext: true,
1936
+ singleLine: true
1937
+ });
1938
+ const maps = createMaps();
1939
+ const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
1940
+ const routeMatchers = createRouteMatchers(routes);
1941
+ let viteDevServer;
1942
+ await loadAssets(
1943
+ processedConfigs,
1944
+ baseClientRoot,
1945
+ maps.bootstrapModules,
1946
+ maps.cssLinks,
1947
+ maps.manifests,
1948
+ maps.preloadLinks,
1949
+ maps.renderModules,
1950
+ maps.ssrManifests,
1951
+ maps.templates,
1952
+ {
1953
+ debug: opts.debug,
1954
+ logger
1920
1955
  }
1921
- const baseMsg = formatCspLoadedMsg(hasGlobal, custom);
1922
- items.push({
1923
- key: "csp",
1924
- status,
1925
- message: baseMsg + tail
1926
- });
1927
- items.push({
1928
- key: "csp",
1929
- status,
1930
- message: `\u2713 Verified (${enabled} enabled, ${disabled} disabled, ${total} total). ` + tail
1931
- });
1932
- } else {
1933
- const count = routes.filter((r) => contract.required([r], security)).length;
1934
- items.push({
1935
- key: contract.key,
1936
- status: "verified",
1937
- message: `\u2713 ${count} route(s)`
1956
+ );
1957
+ if (opts.staticAssets) await registerStaticAssets(app, baseClientRoot, opts.staticAssets);
1958
+ if (security?.csp?.reporting) {
1959
+ app.register(cspReportPlugin, {
1960
+ path: security.csp.reporting.endpoint,
1961
+ debug: opts.debug,
1962
+ logger,
1963
+ onViolation: security.csp.reporting.onViolation
1938
1964
  });
1939
1965
  }
1940
- }
1941
- return { items };
1942
- };
1966
+ app.register(cspPlugin, {
1967
+ directives: opts.security?.csp?.directives,
1968
+ generateCSP: opts.security?.csp?.generateCSP,
1969
+ routeMatchers,
1970
+ debug: opts.debug
1971
+ });
1972
+ if (isDevelopment) viteDevServer = await setupDevServer(app, baseClientRoot, alias, opts.debug, opts.devNet);
1973
+ app.addHook("onRequest", createAuthHook(routeMatchers, logger));
1974
+ app.get("/*", async (req, reply) => {
1975
+ await handleRender(req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, {
1976
+ debug: opts.debug,
1977
+ logger,
1978
+ viteDevServer
1979
+ });
1980
+ });
1981
+ app.setNotFoundHandler(async (req, reply) => {
1982
+ await handleNotFound(
1983
+ req,
1984
+ reply,
1985
+ processedConfigs,
1986
+ {
1987
+ cssLinks: maps.cssLinks,
1988
+ bootstrapModules: maps.bootstrapModules,
1989
+ templates: maps.templates
1990
+ },
1991
+ {
1992
+ debug: opts.debug,
1993
+ logger
1994
+ }
1995
+ );
1996
+ });
1997
+ app.setErrorHandler((err, req, reply) => {
1998
+ const e = AppError.from(err);
1999
+ const alreadyLogged = !!e?.details && e.details && e.details.logged;
2000
+ if (!alreadyLogged) {
2001
+ logger.error(
2002
+ {
2003
+ kind: e.kind,
2004
+ httpStatus: e.httpStatus,
2005
+ ...e.code ? { code: e.code } : {},
2006
+ ...e.details ? { details: e.details } : {},
2007
+ method: req.method,
2008
+ url: req.url,
2009
+ route: req.routeOptions?.url,
2010
+ stack: e.stack
2011
+ },
2012
+ e.message
2013
+ );
2014
+ }
2015
+ if (!reply.raw.headersSent) {
2016
+ const { status, body } = toHttp(e);
2017
+ reply.status(status).send(body);
2018
+ } else {
2019
+ reply.raw.end();
2020
+ }
2021
+ });
2022
+ },
2023
+ { name: "\u03C4js-ssr-server" }
2024
+ );
1943
2025
 
1944
2026
  // src/CreateServer.ts
1945
2027
  var createServer = async (opts) => {
1946
- const t0 = performance2.now();
2028
+ const t0 = performance3.now();
1947
2029
  const clientRoot = opts.clientRoot ?? path5.resolve(process.cwd(), "client");
1948
2030
  const app = opts.fastify ?? Fastify({ logger: false });
1949
- const net = resolveNet(opts.config.server);
1950
- await app.register(bannerPlugin, {
1951
- debug: opts.debug,
1952
- hmr: { host: net.host, port: net.hmrPort }
1953
- });
2031
+ const fastifyLogger = app.log && app.log.level !== "silent" ? app.log : void 0;
1954
2032
  const logger = createLogger({
1955
2033
  debug: opts.debug,
1956
- custom: opts.logger,
2034
+ custom: opts.logger ?? fastifyLogger,
1957
2035
  minLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
1958
2036
  includeContext: true
1959
2037
  });
2038
+ const net = resolveNet(opts.config.server);
2039
+ await app.register(bannerPlugin, {
2040
+ debug: opts.debug,
2041
+ hmr: { host: net.host, port: net.hmrPort }
2042
+ });
1960
2043
  const configs = extractBuildConfigs(opts.config);
1961
2044
  const { routes, apps, totalRoutes, durationMs, warnings } = extractRoutes(opts.config);
1962
2045
  const { security, durationMs: securityDuration, hasExplicitCSP } = extractSecurity(opts.config);
@@ -1988,27 +2071,129 @@ var createServer = async (opts) => {
1988
2071
  configs,
1989
2072
  routes,
1990
2073
  serviceRegistry: opts.serviceRegistry,
1991
- registerStaticAssets: opts.registerStaticAssets !== void 0 ? opts.registerStaticAssets : { plugin: fastifyStatic },
2074
+ staticAssets: opts.staticAssets !== void 0 ? opts.staticAssets : { plugin: fastifyStatic },
1992
2075
  debug: opts.debug,
1993
2076
  alias: opts.alias,
1994
2077
  security,
1995
2078
  devNet: { host: net.host, hmrPort: net.hmrPort }
1996
2079
  });
1997
2080
  } catch (err) {
1998
- logger.error("Failed to register SSRServer", {
1999
- step: "register:SSRServer",
2000
- error: normaliseError(err)
2001
- });
2081
+ logger.error(
2082
+ {
2083
+ step: "register:SSRServer",
2084
+ error: normaliseError(err)
2085
+ },
2086
+ "Failed to register SSRServer"
2087
+ );
2002
2088
  }
2003
- const t1 = performance2.now();
2089
+ const t1 = performance3.now();
2004
2090
  console.log(`
2005
2091
  ${import_picocolors4.default.bgGreen(import_picocolors4.default.black(` ${CONTENT.TAG} `))} configured in ${(t1 - t0).toFixed(0)}ms
2006
2092
  `);
2007
2093
  if (opts.fastify) return { net };
2008
2094
  return { app, net };
2009
2095
  };
2096
+
2097
+ // src/Build.ts
2098
+ import path6 from "path";
2099
+ import { build } from "vite";
2100
+ import { nodePolyfills } from "vite-plugin-node-polyfills";
2101
+ async function taujsBuild({
2102
+ config,
2103
+ projectRoot,
2104
+ clientBaseDir,
2105
+ isSSRBuild = process.env.BUILD_MODE === "ssr"
2106
+ }) {
2107
+ const deleteDist = async () => {
2108
+ const { rm } = await import("fs/promises");
2109
+ const distPath = path6.resolve(projectRoot, "dist");
2110
+ try {
2111
+ await rm(distPath, { recursive: true, force: true });
2112
+ console.log("Deleted the dist directory\n");
2113
+ } catch (err) {
2114
+ console.error("Error deleting dist directory:", err);
2115
+ }
2116
+ };
2117
+ const extractedConfigs = extractBuildConfigs(config);
2118
+ const processedConfigs = processConfigs(extractedConfigs, clientBaseDir, TEMPLATE);
2119
+ if (!isSSRBuild) await deleteDist();
2120
+ for (const config2 of processedConfigs) {
2121
+ const { appId, entryPoint, clientRoot, entryClient, entryServer, htmlTemplate, plugins = [] } = config2;
2122
+ const outDir = path6.resolve(projectRoot, `dist/client/${entryPoint}`);
2123
+ const root = entryPoint ? path6.resolve(clientBaseDir, entryPoint) : clientBaseDir;
2124
+ const server = path6.resolve(clientRoot, `${entryServer}.tsx`);
2125
+ const client = path6.resolve(clientRoot, `${entryClient}.tsx`);
2126
+ const main = path6.resolve(clientRoot, htmlTemplate);
2127
+ const viteConfig = {
2128
+ base: entryPoint ? `/${entryPoint}/` : "/",
2129
+ build: {
2130
+ outDir,
2131
+ manifest: !isSSRBuild,
2132
+ rollupOptions: {
2133
+ input: isSSRBuild ? { server } : { client, main }
2134
+ },
2135
+ ssr: isSSRBuild ? server : void 0,
2136
+ ssrManifest: isSSRBuild,
2137
+ ...isSSRBuild && {
2138
+ format: "esm",
2139
+ target: `node${process.versions.node.split(".").map(Number)[0]}`
2140
+ }
2141
+ },
2142
+ css: {
2143
+ preprocessorOptions: {
2144
+ scss: { api: "modern-compiler" }
2145
+ }
2146
+ },
2147
+ plugins: [...plugins, nodePolyfills({ include: ["fs", "stream"] })],
2148
+ publicDir: "public",
2149
+ resolve: {
2150
+ alias: {
2151
+ "@client": root,
2152
+ "@server": path6.resolve(projectRoot, "src/server"),
2153
+ "@shared": path6.resolve(projectRoot, "src/shared")
2154
+ }
2155
+ },
2156
+ root,
2157
+ server: {
2158
+ proxy: {
2159
+ "/api": {
2160
+ target: "http://localhost:3000",
2161
+ changeOrigin: true,
2162
+ rewrite: (path7) => path7.replace(/^\/api/, "")
2163
+ }
2164
+ }
2165
+ }
2166
+ };
2167
+ try {
2168
+ console.log(`Building for entryPoint: "${entryPoint}" (${appId})`);
2169
+ await build(viteConfig);
2170
+ console.log(`Build complete for entryPoint: "${entryPoint}"
2171
+ `);
2172
+ } catch (error) {
2173
+ console.error(`Error building for entryPoint: "${entryPoint}"
2174
+ `, error);
2175
+ process.exit(1);
2176
+ }
2177
+ }
2178
+ }
2179
+
2180
+ // src/logging/Adapters.ts
2181
+ var cleanMeta = (m) => m && Object.keys(m).length === 0 ? void 0 : m;
2182
+ function messageMetaAdapter(sink) {
2183
+ return {
2184
+ debug: (meta, message) => sink.debug?.(message, cleanMeta(meta)),
2185
+ info: (meta, message) => sink.info?.(message, cleanMeta(meta)),
2186
+ warn: (meta, message) => sink.warn?.(message, cleanMeta(meta)),
2187
+ error: (meta, message) => sink.error?.(message, cleanMeta(meta)),
2188
+ child: (ctx) => messageMetaAdapter(sink.child?.(ctx) ?? sink)
2189
+ };
2190
+ }
2191
+ function winstonAdapter(winston) {
2192
+ return messageMetaAdapter(winston);
2193
+ }
2010
2194
  export {
2011
- SSRServer,
2012
- TEMPLATE,
2013
- createServer
2195
+ createLogger,
2196
+ createServer,
2197
+ taujsBuild,
2198
+ winstonAdapter
2014
2199
  };