satset-react 0.2.6 → 0.2.8
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/assets/robots.d.ts +12 -0
- package/dist/assets/robots.d.ts.map +1 -1
- package/dist/assets/robots.js +42 -17
- package/dist/assets/robots.js.map +1 -1
- package/dist/assets/sitemap.d.ts +7 -0
- package/dist/assets/sitemap.d.ts.map +1 -1
- package/dist/assets/sitemap.js +20 -13
- package/dist/assets/sitemap.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +15 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/theme.d.ts +21 -0
- package/dist/components/theme.d.ts.map +1 -0
- package/dist/components/theme.js +85 -0
- package/dist/components/theme.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/ssr.d.ts +1 -0
- package/dist/core/ssr.d.ts.map +1 -1
- package/dist/core/ssr.js +1 -1
- package/dist/core/ssr.js.map +1 -1
- package/dist/core/translation.d.ts +2 -0
- package/dist/core/translation.d.ts.map +1 -1
- package/dist/core/translation.js +11 -2
- package/dist/core/translation.js.map +1 -1
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/server/dev.d.ts.map +1 -1
- package/dist/server/dev.js +964 -764
- package/dist/server/dev.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/storage.d.ts +13 -0
- package/dist/server/storage.d.ts.map +1 -0
- package/dist/server/storage.js +26 -0
- package/dist/server/storage.js.map +1 -0
- package/package.json +2 -1
package/dist/server/dev.js
CHANGED
|
@@ -13,6 +13,7 @@ const env_1 = require("./env");
|
|
|
13
13
|
const error_overlay_1 = require("./error-overlay");
|
|
14
14
|
const bundler_1 = require("./bundler");
|
|
15
15
|
const response_1 = require("./response");
|
|
16
|
+
const storage_1 = require("./storage");
|
|
16
17
|
const translation_1 = require("../core/translation");
|
|
17
18
|
const util_1 = __importDefault(require("util"));
|
|
18
19
|
const tui_1 = require("./tui");
|
|
@@ -235,10 +236,128 @@ async function startDevServer(config = {}) {
|
|
|
235
236
|
// Create HTTP server
|
|
236
237
|
const server = http_1.default.createServer(async (req, res) => {
|
|
237
238
|
const url = req.url || '/';
|
|
238
|
-
|
|
239
|
-
const effectivePath = stripLocaleFromPath(rawPath);
|
|
239
|
+
// Parse cookies early for redirection
|
|
240
240
|
const cookieStore = parseRequestCookies(req);
|
|
241
241
|
(0, response_1.setCurrentRequestCookies)(cookieStore);
|
|
242
|
+
const rawPath = url.split('?')[0].split('#')[0] || '/';
|
|
243
|
+
// Helper to get base URL
|
|
244
|
+
const getBaseUrl = () => {
|
|
245
|
+
if (process.env.SATSET_PUBLIC_SITE_URL)
|
|
246
|
+
return process.env.SATSET_PUBLIC_SITE_URL;
|
|
247
|
+
const host = req.headers.host || 'localhost';
|
|
248
|
+
const proto = req.headers['x-forwarded-proto'] || 'http';
|
|
249
|
+
return `${proto}://${host}`;
|
|
250
|
+
};
|
|
251
|
+
// Sitemap.xml Handler
|
|
252
|
+
if (rawPath === '/sitemap.xml') {
|
|
253
|
+
try {
|
|
254
|
+
const sitemapFiles = ['sitemap.ts', 'sitemap.js', 'sitemap.tsx'];
|
|
255
|
+
let sitemapFile = null;
|
|
256
|
+
for (const f of sitemapFiles) {
|
|
257
|
+
const p = path_1.default.join(root, 'src', f);
|
|
258
|
+
if (fs_1.default.existsSync(p)) {
|
|
259
|
+
sitemapFile = p;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (sitemapFile) {
|
|
264
|
+
const outFile = path_1.default.join(tempDir, 'sitemap.server.js');
|
|
265
|
+
await bundler_1.bundler.bundleServer({ entryPoint: sitemapFile, outfile: outFile, root });
|
|
266
|
+
try {
|
|
267
|
+
delete require.cache[require.resolve(outFile)];
|
|
268
|
+
}
|
|
269
|
+
catch (e) { }
|
|
270
|
+
const mod = require(outFile);
|
|
271
|
+
const fn = mod.default;
|
|
272
|
+
if (typeof fn === 'function') {
|
|
273
|
+
const data = await fn();
|
|
274
|
+
const { generateSitemapXml } = await import('../assets/sitemap.js');
|
|
275
|
+
const xml = generateSitemapXml(data);
|
|
276
|
+
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
|
277
|
+
res.end(xml);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Fallback: Generate from routes on-the-fly
|
|
282
|
+
const { generateSitemap } = await import('../assets/sitemap.js');
|
|
283
|
+
const xml = generateSitemap({ baseUrl: getBaseUrl(), routes });
|
|
284
|
+
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
|
285
|
+
res.end(xml);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
console.error('Error generating sitemap:', e);
|
|
290
|
+
res.writeHead(500);
|
|
291
|
+
res.end('Error generating sitemap');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Robots.txt Handler
|
|
296
|
+
if (rawPath === '/robots.txt') {
|
|
297
|
+
try {
|
|
298
|
+
const robotsFiles = ['robots.ts', 'robots.js'];
|
|
299
|
+
let robotsFile = null;
|
|
300
|
+
for (const f of robotsFiles) {
|
|
301
|
+
const p = path_1.default.join(root, 'src', f);
|
|
302
|
+
if (fs_1.default.existsSync(p)) {
|
|
303
|
+
robotsFile = p;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (robotsFile) {
|
|
308
|
+
const outFile = path_1.default.join(tempDir, 'robots.server.js');
|
|
309
|
+
await bundler_1.bundler.bundleServer({ entryPoint: robotsFile, outfile: outFile, root });
|
|
310
|
+
try {
|
|
311
|
+
delete require.cache[require.resolve(outFile)];
|
|
312
|
+
}
|
|
313
|
+
catch (e) { }
|
|
314
|
+
const mod = require(outFile);
|
|
315
|
+
const fn = mod.default;
|
|
316
|
+
if (typeof fn === 'function') {
|
|
317
|
+
const data = await fn();
|
|
318
|
+
const { generateRobotsTxtFromData } = await import('../assets/robots.js');
|
|
319
|
+
const txt = generateRobotsTxtFromData(data);
|
|
320
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
321
|
+
res.end(txt);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Fallback
|
|
326
|
+
const { generateRobotsTxt } = await import('../assets/robots.js');
|
|
327
|
+
const txt = generateRobotsTxt();
|
|
328
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
329
|
+
res.end(txt);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
console.error('Error generating robots.txt:', e);
|
|
334
|
+
res.writeHead(500);
|
|
335
|
+
res.end('Error generating robots.txt');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Virtual Routing / Locale Detection
|
|
340
|
+
let locale = 'en-US';
|
|
341
|
+
let effectivePath = rawPath;
|
|
342
|
+
const segments = rawPath.split('/').filter(Boolean);
|
|
343
|
+
const firstSegment = segments[0];
|
|
344
|
+
// 1. Detect from URL (Virtual Path)
|
|
345
|
+
if (firstSegment && /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/.test(firstSegment)) {
|
|
346
|
+
locale = firstSegment;
|
|
347
|
+
effectivePath = stripLocaleFromPath(rawPath);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// 2. Detect from Cookie / Header
|
|
351
|
+
if (cookieStore && cookieStore['SATSET_LANG']) {
|
|
352
|
+
locale = cookieStore['SATSET_LANG'];
|
|
353
|
+
}
|
|
354
|
+
else if (req.headers['accept-language']) {
|
|
355
|
+
const accept = req.headers['accept-language'].split(',')[0].trim();
|
|
356
|
+
const match = accept.match(/^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?/);
|
|
357
|
+
if (match)
|
|
358
|
+
locale = match[0];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
242
361
|
// Serve bundled JS files
|
|
243
362
|
if (url.startsWith('/_satset/')) {
|
|
244
363
|
// Remove query parameters from the URL before resolving file path
|
|
@@ -414,7 +533,7 @@ async function startDevServer(config = {}) {
|
|
|
414
533
|
}
|
|
415
534
|
const apiMatch = (0, file_system_1.matchRoute)(effectivePath, apiRoutes);
|
|
416
535
|
if (apiMatch) {
|
|
417
|
-
await handleApiRoute(apiMatch.route, req, res, root, tempDir, apiMatch.params);
|
|
536
|
+
await handleApiRoute(apiMatch.route, req, res, root, tempDir, apiMatch.params, locale);
|
|
418
537
|
return;
|
|
419
538
|
}
|
|
420
539
|
if (effectivePath.startsWith('/api/')) {
|
|
@@ -424,13 +543,13 @@ async function startDevServer(config = {}) {
|
|
|
424
543
|
}
|
|
425
544
|
const matched = (0, file_system_1.matchRoute)(effectivePath, routes);
|
|
426
545
|
if (matched) {
|
|
427
|
-
await handlePageRoute(matched.route, req, res, root, tempDir, matched.params, routes, publicDir);
|
|
546
|
+
await handlePageRoute(matched.route, req, res, root, tempDir, matched.params, routes, publicDir, locale);
|
|
428
547
|
return;
|
|
429
548
|
}
|
|
430
549
|
const notFoundRoute = routes.find(r => r.path === '/404') ||
|
|
431
550
|
routes.find(r => r.path === '/not-found');
|
|
432
551
|
if (notFoundRoute) {
|
|
433
|
-
await handlePageRoute(notFoundRoute, req, res, root, tempDir, {}, routes, publicDir);
|
|
552
|
+
await handlePageRoute(notFoundRoute, req, res, root, tempDir, {}, routes, publicDir, locale);
|
|
434
553
|
return;
|
|
435
554
|
}
|
|
436
555
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
@@ -534,126 +653,124 @@ async function buildClientBundle(root, outdir, routes) {
|
|
|
534
653
|
if (layoutRoute && layoutRoute.component) {
|
|
535
654
|
const layoutRelative = path_1.default.relative(root, layoutRoute.component).replace(/\\/g, '/');
|
|
536
655
|
layoutImport = `import Layout from '../../${layoutRelative}';`;
|
|
537
|
-
appCreationBlock = `
|
|
538
|
-
const pageElement = React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props));
|
|
539
|
-
const withLayout = React.createElement(Layout, null, pageElement);
|
|
540
|
-
const App = React.createElement(
|
|
541
|
-
I18nProvider,
|
|
542
|
-
{
|
|
543
|
-
initialLocale,
|
|
544
|
-
dictionaries: window.__SATSET_DICTIONARIES__
|
|
545
|
-
},
|
|
546
|
-
withLayout
|
|
656
|
+
appCreationBlock = `
|
|
657
|
+
const pageElement = React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props));
|
|
658
|
+
const withLayout = React.createElement(Layout, null, pageElement);
|
|
659
|
+
const App = React.createElement(
|
|
660
|
+
I18nProvider,
|
|
661
|
+
{
|
|
662
|
+
initialLocale,
|
|
663
|
+
dictionaries: window.__SATSET_DICTIONARIES__
|
|
664
|
+
},
|
|
665
|
+
withLayout
|
|
547
666
|
);`;
|
|
548
667
|
}
|
|
549
668
|
else {
|
|
550
|
-
appCreationBlock = `
|
|
551
|
-
const App = React.createElement(
|
|
552
|
-
I18nProvider,
|
|
553
|
-
{
|
|
554
|
-
initialLocale,
|
|
555
|
-
dictionaries: window.__SATSET_DICTIONARIES__
|
|
556
|
-
},
|
|
557
|
-
React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props))
|
|
669
|
+
appCreationBlock = `
|
|
670
|
+
const App = React.createElement(
|
|
671
|
+
I18nProvider,
|
|
672
|
+
{
|
|
673
|
+
initialLocale,
|
|
674
|
+
dictionaries: window.__SATSET_DICTIONARIES__
|
|
675
|
+
},
|
|
676
|
+
React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props))
|
|
558
677
|
);`;
|
|
559
678
|
}
|
|
560
|
-
const entryContent = `
|
|
561
|
-
import React from 'react';
|
|
562
|
-
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
563
|
-
import { I18nProvider } from 'satset-react';
|
|
564
|
-
${layoutImport}
|
|
565
|
-
|
|
566
|
-
// Import all page components
|
|
567
|
-
${importLines.join('\n')}
|
|
568
|
-
// Route definitions
|
|
569
|
-
const routeDefs = [
|
|
570
|
-
${routeDefLines.join('\n')}
|
|
571
|
-
];
|
|
572
|
-
|
|
573
|
-
function stripLocale(pathname) {
|
|
574
|
-
if (!pathname) return '/';
|
|
575
|
-
const raw = pathname.split('?')[0].split('#')[0];
|
|
576
|
-
const segments = raw.split('/').filter(Boolean);
|
|
577
|
-
if (!segments.length) return '/';
|
|
578
|
-
const first = segments[0];
|
|
579
|
-
const localePattern = /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/;
|
|
580
|
-
if (localePattern.test(first)) {
|
|
581
|
-
const rest = segments.slice(1);
|
|
582
|
-
return rest.length ? '/' + rest.join('/') : '/';
|
|
583
|
-
}
|
|
584
|
-
return raw.startsWith('/') ? raw : '/' + raw;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function matchPath(pathname) {
|
|
588
|
-
const normalized = stripLocale(pathname);
|
|
589
|
-
const pathSegments = normalized.split('/').filter(Boolean);
|
|
590
|
-
for (const r of routeDefs) {
|
|
591
|
-
const routeSegments = r.path.split('/').filter(Boolean);
|
|
592
|
-
|
|
593
|
-
// catch-all
|
|
594
|
-
if (r.path.includes('*')) {
|
|
595
|
-
const catchIndex = routeSegments.findIndex(s => s.startsWith('*'));
|
|
596
|
-
const paramName = routeSegments[catchIndex].slice(1);
|
|
597
|
-
const params = {};
|
|
598
|
-
params[paramName] = pathSegments.slice(catchIndex).join('/');
|
|
599
|
-
return { component: r.component, params };
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (routeSegments.length !== pathSegments.length) continue;
|
|
603
|
-
|
|
604
|
-
let matched = true;
|
|
605
|
-
const params = {};
|
|
606
|
-
|
|
607
|
-
for (let i = 0; i < routeSegments.length; i++) {
|
|
608
|
-
const rs = routeSegments[i];
|
|
609
|
-
const ps = pathSegments[i];
|
|
610
|
-
|
|
611
|
-
if (rs.startsWith(':')) {
|
|
612
|
-
params[rs.slice(1)] = ps;
|
|
613
|
-
} else if (rs !== ps) {
|
|
614
|
-
matched = false;
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (matched) {
|
|
620
|
-
return { component: r.component, params };
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// fallback to root route
|
|
625
|
-
return { component: routeDefs.find(r => r.path === '/')?.component, params: {} };
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Hydrate based on current path
|
|
629
|
-
const currentPath = stripLocale(window.location.pathname);
|
|
630
|
-
const match = matchPath(currentPath);
|
|
631
|
-
// expose routes and params to Router
|
|
632
|
-
window.__SATSET_ROUTES__ = routeDefs.map(r => r.path);
|
|
633
|
-
window.__SATSET_PARAMS__ = match.params || {};
|
|
634
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(dictionaries)};
|
|
635
|
-
|
|
636
|
-
const PageComponent = match.component;
|
|
637
|
-
|
|
638
|
-
if (PageComponent) {
|
|
639
|
-
const root = document.getElementById('root');
|
|
640
|
-
if (root) {
|
|
641
|
-
const props = match.params ? { params: match.params } : undefined;
|
|
642
|
-
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
}
|
|
679
|
+
const entryContent = `
|
|
680
|
+
import React from 'react';
|
|
681
|
+
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
682
|
+
import { I18nProvider } from 'satset-react';
|
|
683
|
+
${layoutImport}
|
|
684
|
+
|
|
685
|
+
// Import all page components
|
|
686
|
+
${importLines.join('\n')}
|
|
687
|
+
// Route definitions
|
|
688
|
+
const routeDefs = [
|
|
689
|
+
${routeDefLines.join('\n')}
|
|
690
|
+
];
|
|
691
|
+
|
|
692
|
+
function stripLocale(pathname) {
|
|
693
|
+
if (!pathname) return '/';
|
|
694
|
+
const raw = pathname.split('?')[0].split('#')[0];
|
|
695
|
+
const segments = raw.split('/').filter(Boolean);
|
|
696
|
+
if (!segments.length) return '/';
|
|
697
|
+
const first = segments[0];
|
|
698
|
+
const localePattern = /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/;
|
|
699
|
+
if (localePattern.test(first)) {
|
|
700
|
+
const rest = segments.slice(1);
|
|
701
|
+
return rest.length ? '/' + rest.join('/') : '/';
|
|
702
|
+
}
|
|
703
|
+
return raw.startsWith('/') ? raw : '/' + raw;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function matchPath(pathname) {
|
|
707
|
+
const normalized = stripLocale(pathname);
|
|
708
|
+
const pathSegments = normalized.split('/').filter(Boolean);
|
|
709
|
+
for (const r of routeDefs) {
|
|
710
|
+
const routeSegments = r.path.split('/').filter(Boolean);
|
|
711
|
+
|
|
712
|
+
// catch-all
|
|
713
|
+
if (r.path.includes('*')) {
|
|
714
|
+
const catchIndex = routeSegments.findIndex(s => s.startsWith('*'));
|
|
715
|
+
const paramName = routeSegments[catchIndex].slice(1);
|
|
716
|
+
const params = {};
|
|
717
|
+
params[paramName] = pathSegments.slice(catchIndex).join('/');
|
|
718
|
+
return { component: r.component, params };
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (routeSegments.length !== pathSegments.length) continue;
|
|
722
|
+
|
|
723
|
+
let matched = true;
|
|
724
|
+
const params = {};
|
|
725
|
+
|
|
726
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
727
|
+
const rs = routeSegments[i];
|
|
728
|
+
const ps = pathSegments[i];
|
|
729
|
+
|
|
730
|
+
if (rs.startsWith(':')) {
|
|
731
|
+
params[rs.slice(1)] = ps;
|
|
732
|
+
} else if (rs !== ps) {
|
|
733
|
+
matched = false;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (matched) {
|
|
739
|
+
return { component: r.component, params };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// fallback to root route
|
|
744
|
+
return { component: routeDefs.find(r => r.path === '/')?.component, params: {} };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Hydrate based on current path
|
|
748
|
+
const currentPath = stripLocale(window.location.pathname);
|
|
749
|
+
const match = matchPath(currentPath);
|
|
750
|
+
// expose routes and params to Router
|
|
751
|
+
window.__SATSET_ROUTES__ = routeDefs.map(r => r.path);
|
|
752
|
+
window.__SATSET_PARAMS__ = match.params || {};
|
|
753
|
+
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(dictionaries)};
|
|
754
|
+
|
|
755
|
+
const PageComponent = match.component;
|
|
756
|
+
|
|
757
|
+
if (PageComponent) {
|
|
758
|
+
const root = document.getElementById('root');
|
|
759
|
+
if (root) {
|
|
760
|
+
const props = match.params ? { params: match.params } : undefined;
|
|
761
|
+
|
|
762
|
+
const initialLocale = window.__SATSET_LOCALE__ || 'en-US';
|
|
763
|
+
|
|
764
|
+
${appCreationBlock}
|
|
765
|
+
|
|
766
|
+
if (root.hasChildNodes()) {
|
|
767
|
+
hydrateRoot(root, App);
|
|
768
|
+
} else {
|
|
769
|
+
const rootInstance = createRoot(root);
|
|
770
|
+
rootInstance.render(App);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
657
774
|
`;
|
|
658
775
|
const entryPath = path_1.default.join(outdir, '_entry.tsx');
|
|
659
776
|
fs_1.default.writeFileSync(entryPath, entryContent);
|
|
@@ -697,745 +814,828 @@ ${appCreationBlock}
|
|
|
697
814
|
console.error(`❌ ERROR [${formatTime()}] Client bundle failed:`, error && error.message ? error.message : String(error));
|
|
698
815
|
}
|
|
699
816
|
}
|
|
700
|
-
async function handleApiRoute(route, req, res, root, tempDir, params = {}) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
.
|
|
709
|
-
.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
817
|
+
async function handleApiRoute(route, req, res, root, tempDir, params = {}, locale = 'en-US') {
|
|
818
|
+
const dictionaries = getDictionaries(root);
|
|
819
|
+
return storage_1.requestContext.run({ locale, dictionaries, params, pathname: req.url || '/' }, async () => {
|
|
820
|
+
try {
|
|
821
|
+
// Compile API module to CJS before requiring to support TS/ESM sources.
|
|
822
|
+
// Cache the compiled file between requests and only re-bundle when the
|
|
823
|
+
// source file is newer. Use a per-route file name derived from the
|
|
824
|
+
// component's relative path to avoid collisions between different API
|
|
825
|
+
// handlers that share the same base file name (e.g. multiple `route.ts`).
|
|
826
|
+
const relComponentPath = path_1.default
|
|
827
|
+
.relative(root, route.component)
|
|
828
|
+
.replace(/\\/g, '/')
|
|
829
|
+
.replace(/\.[^.]+$/, '');
|
|
830
|
+
const safeComponentKey = relComponentPath.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
831
|
+
const compiledApiPath = path_1.default.join(tempDir, safeComponentKey + '.api.server.js');
|
|
832
|
+
console.log('[API Debug] route:', route.path, 'component:', route.component);
|
|
833
|
+
console.log('[API Debug] safeKey:', safeComponentKey, 'compiledPath:', compiledApiPath);
|
|
834
|
+
let ApiModule = null;
|
|
835
|
+
async function safeBundleApi(entry, outfile) {
|
|
836
|
+
try {
|
|
837
|
+
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
838
|
+
}
|
|
839
|
+
catch (err) {
|
|
840
|
+
const msg = String(err && err.message ? err.message : err);
|
|
841
|
+
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
842
|
+
try {
|
|
843
|
+
if (fs_1.default.existsSync(tempDir)) {
|
|
844
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
845
|
+
}
|
|
846
|
+
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
847
|
+
}
|
|
848
|
+
catch (cleanupErr) {
|
|
849
|
+
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
850
|
+
}
|
|
851
|
+
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
throw err;
|
|
855
|
+
}
|
|
719
856
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (
|
|
857
|
+
try {
|
|
858
|
+
let needsBundle = true;
|
|
859
|
+
if (fs_1.default.existsSync(compiledApiPath)) {
|
|
723
860
|
try {
|
|
724
|
-
|
|
725
|
-
|
|
861
|
+
const srcStat = fs_1.default.statSync(route.component);
|
|
862
|
+
const outStat = fs_1.default.statSync(compiledApiPath);
|
|
863
|
+
if (outStat.mtimeMs >= srcStat.mtimeMs) {
|
|
864
|
+
needsBundle = false;
|
|
726
865
|
}
|
|
727
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
728
866
|
}
|
|
729
|
-
catch (
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
await
|
|
733
|
-
return;
|
|
867
|
+
catch (e) { }
|
|
868
|
+
}
|
|
869
|
+
if (needsBundle) {
|
|
870
|
+
await safeBundleApi(route.component, compiledApiPath);
|
|
734
871
|
}
|
|
735
|
-
throw err;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
try {
|
|
739
|
-
let needsBundle = true;
|
|
740
|
-
if (fs_1.default.existsSync(compiledApiPath)) {
|
|
741
872
|
try {
|
|
742
|
-
|
|
743
|
-
const outStat = fs_1.default.statSync(compiledApiPath);
|
|
744
|
-
if (outStat.mtimeMs >= srcStat.mtimeMs) {
|
|
745
|
-
needsBundle = false;
|
|
746
|
-
}
|
|
873
|
+
delete require.cache[require.resolve(compiledApiPath)];
|
|
747
874
|
}
|
|
748
875
|
catch (e) { }
|
|
876
|
+
ApiModule = require(compiledApiPath);
|
|
749
877
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
878
|
+
catch (err) {
|
|
879
|
+
console.error('API bundling failed for', route.component, err);
|
|
880
|
+
const errMsg = String(err && err.message ? err.message : err);
|
|
881
|
+
const message = /ENOSPC|not enough space|There is not enough space/i.test(errMsg)
|
|
882
|
+
? 'API bundling failed: not enough disk space. Free disk space or delete .satset/temp'
|
|
883
|
+
: 'API bundling failed';
|
|
884
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
885
|
+
res.end(JSON.stringify({ error: { code: 500, message }, file: route.component }));
|
|
886
|
+
return;
|
|
755
887
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return { default: mod };
|
|
775
|
-
if (mod && typeof mod === 'object') {
|
|
776
|
-
if (typeof mod.default === 'function')
|
|
777
|
-
return { default: mod.default };
|
|
778
|
-
// method-named exports (GET, POST, etc.)
|
|
779
|
-
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
|
780
|
-
const out = {};
|
|
781
|
-
for (const m of methods) {
|
|
782
|
-
if (typeof mod[m] === 'function')
|
|
783
|
-
out[m] = mod[m];
|
|
888
|
+
function resolveHandler(mod) {
|
|
889
|
+
if (!mod)
|
|
890
|
+
return null;
|
|
891
|
+
// default export function
|
|
892
|
+
if (typeof mod === 'function')
|
|
893
|
+
return { default: mod };
|
|
894
|
+
if (mod && typeof mod === 'object') {
|
|
895
|
+
if (typeof mod.default === 'function')
|
|
896
|
+
return { default: mod.default };
|
|
897
|
+
// method-named exports (GET, POST, etc.)
|
|
898
|
+
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
|
899
|
+
const out = {};
|
|
900
|
+
for (const m of methods) {
|
|
901
|
+
if (typeof mod[m] === 'function')
|
|
902
|
+
out[m] = mod[m];
|
|
903
|
+
}
|
|
904
|
+
if (Object.keys(out).length)
|
|
905
|
+
return out;
|
|
784
906
|
}
|
|
785
|
-
|
|
786
|
-
return out;
|
|
907
|
+
return null;
|
|
787
908
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
(
|
|
909
|
+
const handlerObj = resolveHandler(ApiModule);
|
|
910
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
911
|
+
async function callHandlerWithNodeStyle(fn) {
|
|
912
|
+
// If function declares at least two args, assume Node-style (req, res)
|
|
913
|
+
if (fn.length >= 2) {
|
|
914
|
+
// Inject locale into request object for Node-style handlers
|
|
915
|
+
req.locale = locale;
|
|
916
|
+
req.lang = locale;
|
|
917
|
+
const maybe = await fn(req, res, params);
|
|
918
|
+
// If the handler returns a SatsetResponse, send it
|
|
919
|
+
if (response_1.SatsetResponse.isSatsetResponse(maybe)) {
|
|
920
|
+
(0, response_1.sendSatsetResponse)(res, maybe);
|
|
921
|
+
}
|
|
922
|
+
// assume the handler handled the Node res
|
|
923
|
+
return true;
|
|
799
924
|
}
|
|
800
|
-
|
|
801
|
-
return true;
|
|
802
|
-
}
|
|
803
|
-
return false;
|
|
804
|
-
}
|
|
805
|
-
async function callHandlerWithWebStyle(fn) {
|
|
806
|
-
// Build a lightweight Request-like object for route handlers
|
|
807
|
-
const webReq = await (0, response_1.buildSatsetRequest)(req);
|
|
808
|
-
const result = await fn(webReq, { params });
|
|
809
|
-
if (response_1.SatsetResponse.isSatsetResponse(result)) {
|
|
810
|
-
(0, response_1.sendSatsetResponse)(res, result);
|
|
811
|
-
return true;
|
|
925
|
+
return false;
|
|
812
926
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
927
|
+
async function callHandlerWithWebStyle(fn) {
|
|
928
|
+
// Build a lightweight Request-like object for route handlers
|
|
929
|
+
const webReq = await (0, response_1.buildSatsetRequest)(req);
|
|
930
|
+
// Inject locale into context
|
|
931
|
+
const context = { params, locale, lang: locale };
|
|
932
|
+
const result = await fn(webReq, context);
|
|
933
|
+
if (response_1.SatsetResponse.isSatsetResponse(result)) {
|
|
934
|
+
(0, response_1.sendSatsetResponse)(res, result);
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
// If result is a native Response-like object with json/text, try to handle
|
|
938
|
+
if (result && typeof result === 'object' && typeof result.status === 'number' && (result.headers || result.json)) {
|
|
939
|
+
// Try basic mapping
|
|
940
|
+
const status = result.status || 200;
|
|
941
|
+
// Normalize headers: support Headers instance, array of tuples, or plain object
|
|
942
|
+
let headers = {};
|
|
943
|
+
const rawHeaders = result.headers;
|
|
944
|
+
if (rawHeaders) {
|
|
945
|
+
if (typeof rawHeaders.get === 'function' && typeof rawHeaders.entries === 'function') {
|
|
946
|
+
headers = Object.fromEntries(rawHeaders.entries());
|
|
947
|
+
}
|
|
948
|
+
else if (Array.isArray(rawHeaders)) {
|
|
949
|
+
headers = Object.fromEntries(rawHeaders);
|
|
950
|
+
}
|
|
951
|
+
else if (typeof rawHeaders === 'object') {
|
|
952
|
+
headers = rawHeaders;
|
|
953
|
+
}
|
|
823
954
|
}
|
|
824
|
-
|
|
825
|
-
|
|
955
|
+
let body = undefined;
|
|
956
|
+
try {
|
|
957
|
+
if (typeof result.json === 'function') {
|
|
958
|
+
body = await result.json();
|
|
959
|
+
res.writeHead(status, { 'Content-Type': 'application/json', ...headers });
|
|
960
|
+
res.end(JSON.stringify(body));
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
if (typeof result.text === 'function') {
|
|
964
|
+
body = await result.text();
|
|
965
|
+
res.writeHead(status, headers);
|
|
966
|
+
res.end(body);
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
826
969
|
}
|
|
827
|
-
|
|
828
|
-
|
|
970
|
+
catch (e) {
|
|
971
|
+
// fallthrough
|
|
829
972
|
}
|
|
830
973
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
res.end(JSON.stringify(body));
|
|
837
|
-
return true;
|
|
838
|
-
}
|
|
839
|
-
if (typeof result.text === 'function') {
|
|
840
|
-
body = await result.text();
|
|
841
|
-
res.writeHead(status, headers);
|
|
842
|
-
res.end(body);
|
|
843
|
-
return true;
|
|
844
|
-
}
|
|
974
|
+
// If result is an object, return as JSON
|
|
975
|
+
if (result && typeof result === 'object') {
|
|
976
|
+
const sat = response_1.SatsetResponse.json(result);
|
|
977
|
+
(0, response_1.sendSatsetResponse)(res, sat);
|
|
978
|
+
return true;
|
|
845
979
|
}
|
|
846
|
-
|
|
847
|
-
|
|
980
|
+
// If result is string or number, send as text
|
|
981
|
+
if (result != null) {
|
|
982
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
983
|
+
res.end(String(result));
|
|
984
|
+
return true;
|
|
848
985
|
}
|
|
986
|
+
// undefined -> assume handler will manage res; if not, leave to caller
|
|
987
|
+
return false;
|
|
849
988
|
}
|
|
850
|
-
//
|
|
851
|
-
if (
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
// Prefer method-named export for App-style handlers
|
|
866
|
-
if (handlerObj && typeof handlerObj[method] === 'function') {
|
|
867
|
-
const fn = handlerObj[method];
|
|
868
|
-
// Try node-style first
|
|
869
|
-
const nodeTook = await callHandlerWithNodeStyle(fn);
|
|
870
|
-
if (nodeTook)
|
|
989
|
+
// Prefer method-named export for App-style handlers
|
|
990
|
+
if (handlerObj && typeof handlerObj[method] === 'function') {
|
|
991
|
+
const fn = handlerObj[method];
|
|
992
|
+
// Try node-style first
|
|
993
|
+
const nodeTook = await callHandlerWithNodeStyle(fn);
|
|
994
|
+
if (nodeTook)
|
|
995
|
+
return;
|
|
996
|
+
// Fallback to Web-style
|
|
997
|
+
const webTook = await callHandlerWithWebStyle(fn);
|
|
998
|
+
if (webTook)
|
|
999
|
+
return;
|
|
1000
|
+
// no response produced
|
|
1001
|
+
res.writeHead(204);
|
|
1002
|
+
res.end();
|
|
871
1003
|
return;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
if (
|
|
1004
|
+
}
|
|
1005
|
+
// Default export fallback
|
|
1006
|
+
if (handlerObj && typeof handlerObj.default === 'function') {
|
|
1007
|
+
const fn = handlerObj.default;
|
|
1008
|
+
const nodeTook = await callHandlerWithNodeStyle(fn);
|
|
1009
|
+
if (nodeTook)
|
|
1010
|
+
return;
|
|
1011
|
+
const webTook = await callHandlerWithWebStyle(fn);
|
|
1012
|
+
if (webTook)
|
|
1013
|
+
return;
|
|
1014
|
+
res.writeHead(204);
|
|
1015
|
+
res.end();
|
|
875
1016
|
return;
|
|
876
|
-
|
|
877
|
-
res.writeHead(
|
|
878
|
-
res.end();
|
|
879
|
-
return;
|
|
1017
|
+
}
|
|
1018
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
1019
|
+
res.end(JSON.stringify({ error: { code: 405, message: 'Method not allowed' } }));
|
|
880
1020
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (webTook)
|
|
889
|
-
return;
|
|
890
|
-
res.writeHead(204);
|
|
891
|
-
res.end();
|
|
892
|
-
return;
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
const errorPayload = {
|
|
1023
|
+
code: 500,
|
|
1024
|
+
message: error && error.message ? error.message : 'Internal Server Error',
|
|
1025
|
+
};
|
|
1026
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1027
|
+
res.end(JSON.stringify({ error: errorPayload, file: route.component, stack: error && error.stack }));
|
|
893
1028
|
}
|
|
894
|
-
|
|
895
|
-
res.end(JSON.stringify({ error: { code: 405, message: 'Method not allowed' } }));
|
|
896
|
-
}
|
|
897
|
-
catch (error) {
|
|
898
|
-
const errorPayload = {
|
|
899
|
-
code: 500,
|
|
900
|
-
message: error && error.message ? error.message : 'Internal Server Error',
|
|
901
|
-
};
|
|
902
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
903
|
-
res.end(JSON.stringify({ error: errorPayload, file: route.component, stack: error && error.stack }));
|
|
904
|
-
}
|
|
1029
|
+
});
|
|
905
1030
|
}
|
|
906
|
-
async function handlePageRoute(route, req, res, root, tempDir, initialParams = {}, allRoutes = [], publicDirName = 'public') {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const firstSegment = urlSegments[0];
|
|
918
|
-
const locale = (firstSegment && /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/.test(firstSegment)) ? firstSegment : 'en-US';
|
|
919
|
-
const dictionaries = getDictionaries(root);
|
|
1031
|
+
async function handlePageRoute(route, req, res, root, tempDir, initialParams = {}, allRoutes = [], publicDirName = 'public', locale = 'en-US') {
|
|
1032
|
+
const dictionaries = getDictionaries(root);
|
|
1033
|
+
return storage_1.requestContext.run({ locale, dictionaries, params: initialParams, pathname: req.url || '/' }, async () => {
|
|
1034
|
+
try {
|
|
1035
|
+
const env = (0, env_1.loadEnv)(root, 'development');
|
|
1036
|
+
const envScript = (0, env_1.getPublicEnvScript)(env.publicVars);
|
|
1037
|
+
if (isClientOnlyPage(route.component)) {
|
|
1038
|
+
const routePaths = allRoutes.length ? allRoutes.map(r => r.path) : [];
|
|
1039
|
+
let metaHtml = '';
|
|
1040
|
+
let htmlLang = locale;
|
|
1041
|
+
let faviconLink = '';
|
|
920
1042
|
try {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const compiledMetaPath = path_1.default.join(tempDir, baseName + '.meta.server.js');
|
|
1043
|
+
// dictionaries already loaded in context
|
|
1044
|
+
// Auto-inject hreflang tags for SEO
|
|
924
1045
|
try {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
945
|
-
let text = dict[key] || key;
|
|
946
|
-
if (params) {
|
|
947
|
-
Object.entries(params).forEach(([k, v]) => {
|
|
948
|
-
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
949
|
-
});
|
|
1046
|
+
const supportedLocales = Object.keys(dictionaries);
|
|
1047
|
+
if (supportedLocales.length > 0) {
|
|
1048
|
+
const host = req.headers.host || 'localhost';
|
|
1049
|
+
// Determine protocol (assume http for dev, but honor x-forwarded-proto if present)
|
|
1050
|
+
const proto = req.headers['x-forwarded-proto'] || 'http';
|
|
1051
|
+
const origin = `${proto}://${host}`;
|
|
1052
|
+
const urlObj = new URL(req.url || '/', origin);
|
|
1053
|
+
const currentPath = urlObj.pathname;
|
|
1054
|
+
// Determine clean path (strip locale prefix if present in URL)
|
|
1055
|
+
let cleanPath = currentPath;
|
|
1056
|
+
if (currentPath === `/${locale}` || currentPath.startsWith(`/${locale}/`)) {
|
|
1057
|
+
cleanPath = currentPath.substring(locale.length + 1);
|
|
1058
|
+
if (!cleanPath.startsWith('/'))
|
|
1059
|
+
cleanPath = '/' + cleanPath;
|
|
1060
|
+
}
|
|
1061
|
+
supportedLocales.forEach(lang => {
|
|
1062
|
+
let href = origin;
|
|
1063
|
+
if (lang !== 'en-US') { // Assuming en-US is default/root
|
|
1064
|
+
href += `/${lang}`;
|
|
950
1065
|
}
|
|
951
|
-
|
|
952
|
-
|
|
1066
|
+
href += cleanPath === '/' ? '' : cleanPath;
|
|
1067
|
+
metaHtml += `<link rel="alternate" hreflang="${lang}" href="${href}" />\n`;
|
|
1068
|
+
});
|
|
1069
|
+
// Add x-default pointing to default language (en-US)
|
|
1070
|
+
const defaultHref = origin + (cleanPath === '/' ? '' : cleanPath);
|
|
1071
|
+
metaHtml += `<link rel="alternate" hreflang="x-default" href="${defaultHref}" />\n`;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
catch (e) {
|
|
1075
|
+
// ignore hreflang generation errors
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
const { renderMetaTags } = await import('../assets/metadata.js');
|
|
1079
|
+
const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
|
|
1080
|
+
const compiledMetaPath = path_1.default.join(tempDir, baseName + '.meta.server.js');
|
|
1081
|
+
try {
|
|
1082
|
+
await bundler_1.bundler.bundleServer({
|
|
1083
|
+
entryPoint: route.component,
|
|
1084
|
+
outfile: compiledMetaPath,
|
|
1085
|
+
minify: false,
|
|
1086
|
+
root,
|
|
1087
|
+
sourcemap: false,
|
|
1088
|
+
});
|
|
953
1089
|
try {
|
|
954
|
-
|
|
1090
|
+
delete require.cache[require.resolve(compiledMetaPath)];
|
|
955
1091
|
}
|
|
956
1092
|
catch (e) {
|
|
957
|
-
metaObj = null;
|
|
958
1093
|
}
|
|
1094
|
+
const PageModule = require(compiledMetaPath);
|
|
1095
|
+
let metaObj = null;
|
|
1096
|
+
if (PageModule && PageModule.metadata) {
|
|
1097
|
+
metaObj = PageModule.metadata;
|
|
1098
|
+
}
|
|
1099
|
+
else if (PageModule && typeof PageModule.getMetadata === 'function') {
|
|
1100
|
+
const t = (key, params) => {
|
|
1101
|
+
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1102
|
+
let text = dict[key] || key;
|
|
1103
|
+
if (params) {
|
|
1104
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
1105
|
+
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return text;
|
|
1109
|
+
};
|
|
1110
|
+
try {
|
|
1111
|
+
metaObj = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1112
|
+
}
|
|
1113
|
+
catch (e) {
|
|
1114
|
+
metaObj = null;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (metaObj && typeof metaObj.lang === 'string' && metaObj.lang.trim()) {
|
|
1118
|
+
htmlLang = metaObj.lang.trim();
|
|
1119
|
+
}
|
|
1120
|
+
metaHtml = renderMetaTags(metaObj);
|
|
959
1121
|
}
|
|
960
|
-
|
|
961
|
-
htmlLang = metaObj.lang.trim();
|
|
1122
|
+
catch (e) {
|
|
962
1123
|
}
|
|
963
|
-
metaHtml = renderMetaTags(metaObj);
|
|
964
1124
|
}
|
|
965
1125
|
catch (e) {
|
|
966
1126
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
computedFavicon
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1127
|
+
try {
|
|
1128
|
+
let computedFavicon = null;
|
|
1129
|
+
const publicPath = path_1.default.join(root, publicDirName);
|
|
1130
|
+
if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.ico')))
|
|
1131
|
+
computedFavicon = '/favicon.ico';
|
|
1132
|
+
else if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.png')))
|
|
1133
|
+
computedFavicon = '/favicon.png';
|
|
1134
|
+
if (computedFavicon) {
|
|
1135
|
+
faviconLink = `<link rel="icon" href="${computedFavicon}" />`;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
catch (e) {
|
|
979
1139
|
}
|
|
980
1140
|
}
|
|
981
1141
|
catch (e) {
|
|
982
1142
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
window.
|
|
988
|
-
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
989
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1143
|
+
const initialParamsScript = `<script>
|
|
1144
|
+
window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)};
|
|
1145
|
+
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
1146
|
+
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1147
|
+
window.__SATSET_LOCALE__ = "${locale}";
|
|
990
1148
|
</script>`;
|
|
991
|
-
|
|
992
|
-
<!DOCTYPE html>
|
|
993
|
-
<html lang="${htmlLang}">
|
|
994
|
-
<head>
|
|
995
|
-
<meta charset="UTF-8" />
|
|
996
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
997
|
-
${metaHtml}
|
|
998
|
-
${faviconLink}
|
|
999
|
-
<link rel="stylesheet" href="/_satset/globals.css" />
|
|
1000
|
-
</head>
|
|
1001
|
-
<body>
|
|
1002
|
-
<div id="root"></div>
|
|
1003
|
-
<script>${envScript}</script>
|
|
1004
|
-
${initialParamsScript}
|
|
1005
|
-
<script type="module" src="/_satset/_entry.js"></script>
|
|
1006
|
-
<script src="/__hmr"></script>
|
|
1007
|
-
</body>
|
|
1008
|
-
</html>
|
|
1149
|
+
const html = `
|
|
1150
|
+
<!DOCTYPE html>
|
|
1151
|
+
<html lang="${htmlLang}" suppressHydrationWarning>
|
|
1152
|
+
<head>
|
|
1153
|
+
<meta charset="UTF-8" />
|
|
1154
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1155
|
+
${metaHtml}
|
|
1156
|
+
${faviconLink}
|
|
1157
|
+
<link rel="stylesheet" href="/_satset/globals.css" />
|
|
1158
|
+
</head>
|
|
1159
|
+
<body>
|
|
1160
|
+
<div id="root"></div>
|
|
1161
|
+
<script>${envScript}</script>
|
|
1162
|
+
${initialParamsScript}
|
|
1163
|
+
<script type="module" src="/_satset/_entry.js"></script>
|
|
1164
|
+
<script src="/__hmr"></script>
|
|
1165
|
+
</body>
|
|
1166
|
+
</html>
|
|
1009
1167
|
`;
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
// Server-side render the component
|
|
1015
|
-
// Compile page module to a temporary server file so Node can require it (handles tsx/esm)
|
|
1016
|
-
const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
|
|
1017
|
-
const compiledServerPath = path_1.default.join(tempDir, baseName + '.server.js');
|
|
1018
|
-
let PageModule = null;
|
|
1019
|
-
// Helper: bundle server build with low-disk fallback
|
|
1020
|
-
async function safeBundleServer(entry, outfile) {
|
|
1021
|
-
try {
|
|
1022
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
1168
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1169
|
+
res.end(html);
|
|
1170
|
+
return;
|
|
1023
1171
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1172
|
+
// Server-side render the component
|
|
1173
|
+
// Compile page module to a temporary server file so Node can require it (handles tsx/esm)
|
|
1174
|
+
const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
|
|
1175
|
+
const compiledServerPath = path_1.default.join(tempDir, baseName + '.server.js');
|
|
1176
|
+
let PageModule = null;
|
|
1177
|
+
// Helper: bundle server build with low-disk fallback
|
|
1178
|
+
async function safeBundleServer(entry, outfile) {
|
|
1179
|
+
try {
|
|
1180
|
+
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
1181
|
+
}
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
const msg = String(err && err.message ? err.message : err);
|
|
1184
|
+
// detect low-disk / ENOSPC errors (esbuild reports "There is not enough space on the disk" in Windows)
|
|
1185
|
+
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
1186
|
+
console.warn('Low disk space detected while bundling. Attempting to clear temp and retry without sourcemaps.');
|
|
1187
|
+
try {
|
|
1188
|
+
// clear temp dir to free space
|
|
1189
|
+
if (fs_1.default.existsSync(tempDir)) {
|
|
1190
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
1191
|
+
}
|
|
1192
|
+
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
1193
|
+
}
|
|
1194
|
+
catch (cleanupErr) {
|
|
1195
|
+
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
1196
|
+
}
|
|
1197
|
+
try {
|
|
1198
|
+
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
catch (retryErr) {
|
|
1202
|
+
// still failed
|
|
1203
|
+
throw retryErr;
|
|
1033
1204
|
}
|
|
1034
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
1035
|
-
}
|
|
1036
|
-
catch (cleanupErr) {
|
|
1037
|
-
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
1038
|
-
}
|
|
1039
|
-
try {
|
|
1040
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
catch (retryErr) {
|
|
1044
|
-
// still failed
|
|
1045
|
-
throw retryErr;
|
|
1046
1205
|
}
|
|
1206
|
+
// Not a disk-space issue, rethrow
|
|
1207
|
+
throw err;
|
|
1047
1208
|
}
|
|
1048
|
-
// Not a disk-space issue, rethrow
|
|
1049
|
-
throw err;
|
|
1050
1209
|
}
|
|
1051
|
-
}
|
|
1052
|
-
try {
|
|
1053
|
-
// Bundle the page for Node (CJS)
|
|
1054
|
-
await safeBundleServer(route.component, compiledServerPath);
|
|
1055
1210
|
try {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
PageModule = require(compiledServerPath);
|
|
1062
|
-
}
|
|
1063
|
-
catch (err) {
|
|
1064
|
-
// If bundling fails, show an overlay with the bundling error and do NOT require the original TSX (which would crash)
|
|
1065
|
-
console.error('SSR bundling failed for', route.component, err);
|
|
1066
|
-
let message = 'SSR bundling failed';
|
|
1067
|
-
const errMsg = String(err && err.message ? err.message : err);
|
|
1068
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(errMsg)) {
|
|
1069
|
-
message = 'SSR bundling failed: not enough disk space. Try freeing disk space or deleting the .satset/temp folder.';
|
|
1070
|
-
}
|
|
1071
|
-
const errorInfo = {
|
|
1072
|
-
message,
|
|
1073
|
-
stack: err && err.stack ? err.stack : String(err),
|
|
1074
|
-
file: route.component,
|
|
1075
|
-
};
|
|
1076
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1077
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1078
|
-
res.end(overlayHTML);
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
function resolveExportedComponent(mod) {
|
|
1082
|
-
if (!mod)
|
|
1083
|
-
return null;
|
|
1084
|
-
if (typeof mod === 'function')
|
|
1085
|
-
return mod;
|
|
1086
|
-
if (mod && typeof mod === 'object') {
|
|
1087
|
-
if (typeof mod.default === 'function')
|
|
1088
|
-
return mod.default;
|
|
1089
|
-
if (mod.default && typeof mod.default === 'object' && typeof mod.default.default === 'function')
|
|
1090
|
-
return mod.default.default;
|
|
1091
|
-
for (const key of Object.keys(mod)) {
|
|
1092
|
-
if (typeof mod[key] === 'function')
|
|
1093
|
-
return mod[key];
|
|
1211
|
+
// Bundle the page for Node (CJS)
|
|
1212
|
+
await safeBundleServer(route.component, compiledServerPath);
|
|
1213
|
+
try {
|
|
1214
|
+
delete require.cache[require.resolve(compiledServerPath)];
|
|
1094
1215
|
}
|
|
1216
|
+
catch (e) {
|
|
1217
|
+
// ignore
|
|
1218
|
+
}
|
|
1219
|
+
PageModule = require(compiledServerPath);
|
|
1095
1220
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
if (isAsync) {
|
|
1111
|
-
const maybeNode = Comp(props);
|
|
1112
|
-
const resolved = await Promise.resolve(maybeNode);
|
|
1113
|
-
return renderToString(resolved);
|
|
1114
|
-
}
|
|
1115
|
-
return renderToString(React.createElement(Comp, props));
|
|
1116
|
-
}
|
|
1117
|
-
let statusCode = 200;
|
|
1118
|
-
// Determine locale for SSR
|
|
1119
|
-
const urlSegments = req.url?.split('/').filter(Boolean) || [];
|
|
1120
|
-
const firstSegment = urlSegments[0];
|
|
1121
|
-
const locale = (firstSegment && /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/.test(firstSegment)) ? firstSegment : 'en-US';
|
|
1122
|
-
const dictionaries = getDictionaries(root);
|
|
1123
|
-
try {
|
|
1124
|
-
if (!PageComponent || typeof PageComponent !== 'function') {
|
|
1125
|
-
console.error('SSR Error: resolved page component is not a function. PageModule:', util_1.default.inspect(PageModule, { depth: 2 }));
|
|
1126
|
-
const diagHtml = `<!doctype html><html><body><h1>500 - Component export error</h1><pre>${util_1.default.format('PageModule keys: %o\nResolved: %o', Object.keys(PageModule || {}), PageComponent)}</pre></body></html>`;
|
|
1221
|
+
catch (err) {
|
|
1222
|
+
// If bundling fails, show an overlay with the bundling error and do NOT require the original TSX (which would crash)
|
|
1223
|
+
console.error('SSR bundling failed for', route.component, err);
|
|
1224
|
+
let message = 'SSR bundling failed';
|
|
1225
|
+
const errMsg = String(err && err.message ? err.message : err);
|
|
1226
|
+
if (/ENOSPC|not enough space|There is not enough space/i.test(errMsg)) {
|
|
1227
|
+
message = 'SSR bundling failed: not enough disk space. Try freeing disk space or deleting the .satset/temp folder.';
|
|
1228
|
+
}
|
|
1229
|
+
const errorInfo = {
|
|
1230
|
+
message,
|
|
1231
|
+
stack: err && err.stack ? err.stack : String(err),
|
|
1232
|
+
file: route.component,
|
|
1233
|
+
};
|
|
1234
|
+
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1127
1235
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1128
|
-
res.end(
|
|
1236
|
+
res.end(overlayHTML);
|
|
1129
1237
|
return;
|
|
1130
1238
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (!mod)
|
|
1145
|
-
return null;
|
|
1146
|
-
if (typeof mod === 'function')
|
|
1147
|
-
return mod;
|
|
1148
|
-
if (mod && typeof mod === 'object') {
|
|
1149
|
-
if (typeof mod.default === 'function')
|
|
1150
|
-
return mod.default;
|
|
1151
|
-
for (const key of Object.keys(mod)) {
|
|
1152
|
-
if (typeof mod[key] === 'function')
|
|
1153
|
-
return mod[key];
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return null;
|
|
1157
|
-
}
|
|
1158
|
-
LayoutComponent = resolveExportedComp(LayoutModule);
|
|
1159
|
-
if (!LayoutComponent) {
|
|
1160
|
-
console.warn('Layout module found but no component exported:', layoutRoute.component);
|
|
1161
|
-
}
|
|
1162
|
-
else {
|
|
1163
|
-
console.log('Using layout module for SSR:', layoutRoute.component);
|
|
1239
|
+
function resolveExportedComponent(mod) {
|
|
1240
|
+
if (!mod)
|
|
1241
|
+
return null;
|
|
1242
|
+
if (typeof mod === 'function')
|
|
1243
|
+
return mod;
|
|
1244
|
+
if (mod && typeof mod === 'object') {
|
|
1245
|
+
if (typeof mod.default === 'function')
|
|
1246
|
+
return mod.default;
|
|
1247
|
+
if (mod.default && typeof mod.default === 'object' && typeof mod.default.default === 'function')
|
|
1248
|
+
return mod.default.default;
|
|
1249
|
+
for (const key of Object.keys(mod)) {
|
|
1250
|
+
if (typeof mod[key] === 'function')
|
|
1251
|
+
return mod[key];
|
|
1164
1252
|
}
|
|
1165
1253
|
}
|
|
1254
|
+
return null;
|
|
1166
1255
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
else {
|
|
1187
|
-
const Wrapper = () => React.createElement(translation_1.I18nProvider, { initialLocale: locale, dictionaries }, pageNode);
|
|
1188
|
-
pageHTML = await renderComponentToHtml(Wrapper);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
catch (err) {
|
|
1192
|
-
console.error('SSR Error:', err);
|
|
1193
|
-
if (err && err.__SATSET_REDIRECT) {
|
|
1194
|
-
const info = err.__SATSET_REDIRECT || {};
|
|
1195
|
-
const url = typeof info.url === 'string' && info.url.length ? info.url : '/';
|
|
1196
|
-
const status = typeof info.status === 'number' ? info.status : 307;
|
|
1197
|
-
const sat = response_1.SatsetResponse.redirect(url, status);
|
|
1198
|
-
(0, response_1.sendSatsetResponse)(res, sat);
|
|
1199
|
-
return;
|
|
1256
|
+
const PageComponent = resolveExportedComponent(PageModule);
|
|
1257
|
+
console.log('SSR: used component from', compiledServerPath, 'PageModule keys:', Object.keys(PageModule || {}), 'Resolved component type:', typeof PageComponent);
|
|
1258
|
+
console.log('SSR: PageModule keys:', Object.keys(PageModule || {}), 'PageComponent type:', typeof PageComponent);
|
|
1259
|
+
// Simple SSR with optional global layout composition
|
|
1260
|
+
const React = require('react');
|
|
1261
|
+
const { renderToString } = require('react-dom/server');
|
|
1262
|
+
let pageHTML = '<div>Loading...</div>';
|
|
1263
|
+
async function renderComponentToHtml(Comp, props = {}) {
|
|
1264
|
+
if (!Comp || typeof Comp !== 'function') {
|
|
1265
|
+
throw new Error('Invalid component to render');
|
|
1266
|
+
}
|
|
1267
|
+
const isAsync = Comp.constructor && Comp.constructor.name === 'AsyncFunction';
|
|
1268
|
+
if (isAsync) {
|
|
1269
|
+
const maybeNode = Comp(props);
|
|
1270
|
+
const resolved = await Promise.resolve(maybeNode);
|
|
1271
|
+
return renderToString(resolved);
|
|
1272
|
+
}
|
|
1273
|
+
return renderToString(React.createElement(Comp, props));
|
|
1200
1274
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1275
|
+
let statusCode = 200;
|
|
1276
|
+
// Determine locale for SSR
|
|
1277
|
+
// locale is now passed as argument
|
|
1278
|
+
// const dictionaries = getDictionaries(root); // Already defined in outer scope and context
|
|
1279
|
+
try {
|
|
1280
|
+
if (!PageComponent || typeof PageComponent !== 'function') {
|
|
1281
|
+
console.error('SSR Error: resolved page component is not a function. PageModule:', util_1.default.inspect(PageModule, { depth: 2 }));
|
|
1282
|
+
const diagHtml = `<!doctype html><html><body><h1>500 - Component export error</h1><pre>${util_1.default.format('PageModule keys: %o\nResolved: %o', Object.keys(PageModule || {}), PageComponent)}</pre></body></html>`;
|
|
1283
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1284
|
+
res.end(diagHtml);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
// Recursive Layout Loading
|
|
1288
|
+
const layouts = [];
|
|
1204
1289
|
try {
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1290
|
+
const findLayoutFiles = (dir) => {
|
|
1291
|
+
const results = [];
|
|
1292
|
+
let current = dir;
|
|
1293
|
+
const rootSrc = path_1.default.join(root, 'src');
|
|
1294
|
+
// Go up until we reach the root
|
|
1295
|
+
while (current.startsWith(root)) {
|
|
1296
|
+
const extensions = ['.tsx', '.jsx', '.ts', '.js'];
|
|
1297
|
+
for (const ext of extensions) {
|
|
1298
|
+
const layoutPath = path_1.default.join(current, `layout${ext}`);
|
|
1299
|
+
if (fs_1.default.existsSync(layoutPath)) {
|
|
1300
|
+
// Check if already added (to avoid duplicates if loop logic is flawed)
|
|
1301
|
+
if (!results.includes(layoutPath))
|
|
1302
|
+
results.unshift(layoutPath);
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (current === root)
|
|
1307
|
+
break;
|
|
1308
|
+
current = path_1.default.dirname(current);
|
|
1309
|
+
}
|
|
1310
|
+
return results;
|
|
1311
|
+
};
|
|
1312
|
+
const pageDir = path_1.default.dirname(route.component);
|
|
1313
|
+
const layoutFiles = findLayoutFiles(pageDir);
|
|
1314
|
+
for (const layoutFile of layoutFiles) {
|
|
1315
|
+
const layoutBase = path_1.default.basename(layoutFile).replace(/\.[^.]+$/, '');
|
|
1316
|
+
const crypto = require('crypto');
|
|
1317
|
+
const hash = crypto.createHash('md5').update(layoutFile).digest('hex').substring(0, 8);
|
|
1318
|
+
const layoutCompiled = path_1.default.join(tempDir, `${layoutBase}.${hash}.layout.server.js`);
|
|
1319
|
+
await bundler_1.bundler.bundleServer({ entryPoint: layoutFile, outfile: layoutCompiled, minify: false, root });
|
|
1211
1320
|
try {
|
|
1212
|
-
delete require.cache[require.resolve(
|
|
1321
|
+
delete require.cache[require.resolve(layoutCompiled)];
|
|
1213
1322
|
}
|
|
1214
1323
|
catch (e) { }
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
if (
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1324
|
+
const LayoutModule = require(layoutCompiled);
|
|
1325
|
+
let LayoutComp = null;
|
|
1326
|
+
if (typeof LayoutModule === 'function')
|
|
1327
|
+
LayoutComp = LayoutModule;
|
|
1328
|
+
else if (LayoutModule && typeof LayoutModule === 'object') {
|
|
1329
|
+
if (typeof LayoutModule.default === 'function')
|
|
1330
|
+
LayoutComp = LayoutModule.default;
|
|
1331
|
+
else {
|
|
1332
|
+
for (const key of Object.keys(LayoutModule)) {
|
|
1333
|
+
if (typeof LayoutModule[key] === 'function') {
|
|
1334
|
+
LayoutComp = LayoutModule[key];
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1222
1339
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1340
|
+
if (LayoutComp)
|
|
1341
|
+
layouts.push(LayoutComp);
|
|
1226
1342
|
}
|
|
1227
1343
|
}
|
|
1228
1344
|
catch (e) {
|
|
1229
|
-
|
|
1345
|
+
console.warn('Could not load layout modules:', e);
|
|
1230
1346
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1347
|
+
const isPageAsync = PageComponent &&
|
|
1348
|
+
PageComponent.constructor &&
|
|
1349
|
+
PageComponent.constructor.name === 'AsyncFunction';
|
|
1350
|
+
let pageNode;
|
|
1351
|
+
if (isPageAsync) {
|
|
1352
|
+
const maybeNode = PageComponent({ params: initialParams, searchParams: {} });
|
|
1353
|
+
pageNode = await Promise.resolve(maybeNode);
|
|
1238
1354
|
}
|
|
1239
1355
|
else {
|
|
1240
|
-
|
|
1356
|
+
pageNode = React.createElement(PageComponent, { params: initialParams, searchParams: {} });
|
|
1241
1357
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
const candidatePaths = [];
|
|
1247
|
-
candidatePaths.push(`/${statusCode}`);
|
|
1248
|
-
if (statusCode === 404)
|
|
1249
|
-
candidatePaths.push('/404', '/not-found');
|
|
1250
|
-
if (statusCode === 500)
|
|
1251
|
-
candidatePaths.push('/500');
|
|
1252
|
-
candidatePaths.push('/error');
|
|
1253
|
-
let errorRoute = null;
|
|
1254
|
-
for (const p of candidatePaths) {
|
|
1255
|
-
const found = allRoutes.find(r => r.path === p);
|
|
1256
|
-
if (found) {
|
|
1257
|
-
errorRoute = found;
|
|
1258
|
-
break;
|
|
1259
|
-
}
|
|
1358
|
+
let appElement = pageNode;
|
|
1359
|
+
// Wrap from inner to outer
|
|
1360
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1361
|
+
appElement = React.createElement(layouts[i], { params: initialParams, locale }, appElement);
|
|
1260
1362
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1363
|
+
const Wrapper = () => React.createElement(translation_1.I18nProvider, { initialLocale: locale, dictionaries }, appElement);
|
|
1364
|
+
pageHTML = await renderComponentToHtml(Wrapper);
|
|
1365
|
+
}
|
|
1366
|
+
catch (err) {
|
|
1367
|
+
console.error('SSR Error:', err);
|
|
1368
|
+
if (err && err.__SATSET_REDIRECT) {
|
|
1369
|
+
const info = err.__SATSET_REDIRECT || {};
|
|
1370
|
+
const url = typeof info.url === 'string' && info.url.length ? info.url : '/';
|
|
1371
|
+
const status = typeof info.status === 'number' ? info.status : 307;
|
|
1372
|
+
const sat = response_1.SatsetResponse.redirect(url, status);
|
|
1373
|
+
(0, response_1.sendSatsetResponse)(res, sat);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (err && err.__SATSET_NOT_FOUND) {
|
|
1377
|
+
statusCode = 404;
|
|
1378
|
+
const errorPayload = { code: 404, message: 'Page not found' };
|
|
1379
|
+
try {
|
|
1380
|
+
const nf = allRoutes.find(r => r.path === '/404') ||
|
|
1381
|
+
allRoutes.find(r => r.path === '/not-found');
|
|
1382
|
+
if (nf && nf.component) {
|
|
1383
|
+
const nfBase = path_1.default.basename(nf.component).replace(/\.[^.]+$/, '');
|
|
1384
|
+
const nfCompiled = path_1.default.join(tempDir, nfBase + '.notfound.server.js');
|
|
1385
|
+
await bundler_1.bundler.bundleServer({ entryPoint: nf.component, outfile: nfCompiled, minify: false, root });
|
|
1386
|
+
try {
|
|
1387
|
+
delete require.cache[require.resolve(nfCompiled)];
|
|
1388
|
+
}
|
|
1389
|
+
catch (e) { }
|
|
1390
|
+
const NfModule = require(nfCompiled);
|
|
1391
|
+
const NfComp = (typeof NfModule === 'function') ? NfModule : (NfModule && typeof NfModule.default === 'function') ? NfModule.default : null;
|
|
1392
|
+
if (NfComp) {
|
|
1393
|
+
pageHTML = await renderComponentToHtml(NfComp, { params: initialParams, error: errorPayload });
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1397
|
+
}
|
|
1274
1398
|
}
|
|
1275
1399
|
else {
|
|
1276
|
-
|
|
1400
|
+
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1277
1401
|
}
|
|
1278
1402
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1403
|
+
catch (e) {
|
|
1404
|
+
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1281
1405
|
}
|
|
1282
1406
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1407
|
+
else {
|
|
1408
|
+
const maybeStatus = (err && err.statusCode) ??
|
|
1409
|
+
(err && err.status) ??
|
|
1410
|
+
(err && typeof err.code === 'number' ? err.code : undefined);
|
|
1411
|
+
if (typeof maybeStatus === 'number' && maybeStatus >= 400 && maybeStatus <= 599) {
|
|
1412
|
+
statusCode = maybeStatus;
|
|
1413
|
+
}
|
|
1414
|
+
else {
|
|
1415
|
+
statusCode = 500;
|
|
1416
|
+
}
|
|
1417
|
+
const errorPayload = {
|
|
1418
|
+
code: statusCode,
|
|
1419
|
+
message: (err && err.message) ? err.message : (statusCode === 404 ? 'Page not found' : 'Server error'),
|
|
1420
|
+
};
|
|
1421
|
+
const candidatePaths = [];
|
|
1422
|
+
candidatePaths.push(`/${statusCode}`);
|
|
1423
|
+
if (statusCode === 404)
|
|
1424
|
+
candidatePaths.push('/404', '/not-found');
|
|
1425
|
+
if (statusCode === 500)
|
|
1426
|
+
candidatePaths.push('/500');
|
|
1427
|
+
candidatePaths.push('/error');
|
|
1428
|
+
let errorRoute = null;
|
|
1429
|
+
for (const p of candidatePaths) {
|
|
1430
|
+
const found = allRoutes.find(r => r.path === p);
|
|
1431
|
+
if (found) {
|
|
1432
|
+
errorRoute = found;
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1286
1436
|
try {
|
|
1287
|
-
if (
|
|
1288
|
-
|
|
1289
|
-
|
|
1437
|
+
if (errorRoute && errorRoute.component) {
|
|
1438
|
+
const erBase = path_1.default.basename(errorRoute.component).replace(/\.[^.]+$/, '');
|
|
1439
|
+
const erCompiled = path_1.default.join(tempDir, erBase + '.error.server.js');
|
|
1440
|
+
await bundler_1.bundler.bundleServer({ entryPoint: errorRoute.component, outfile: erCompiled, minify: false, root });
|
|
1441
|
+
try {
|
|
1442
|
+
delete require.cache[require.resolve(erCompiled)];
|
|
1290
1443
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
return text;
|
|
1301
|
-
};
|
|
1302
|
-
const meta = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1303
|
-
if (meta && meta.title)
|
|
1304
|
-
pageTitle = meta.title;
|
|
1444
|
+
catch (e) { }
|
|
1445
|
+
const ErModule = require(erCompiled);
|
|
1446
|
+
const ErComp = (typeof ErModule === 'function') ? ErModule : (ErModule && typeof ErModule.default === 'function') ? ErModule.default : null;
|
|
1447
|
+
if (ErComp) {
|
|
1448
|
+
pageHTML = await renderComponentToHtml(ErComp, { error: errorPayload, reset: () => { } });
|
|
1449
|
+
}
|
|
1450
|
+
else {
|
|
1451
|
+
throw err;
|
|
1305
1452
|
}
|
|
1306
1453
|
}
|
|
1454
|
+
else {
|
|
1455
|
+
throw err;
|
|
1456
|
+
}
|
|
1307
1457
|
}
|
|
1308
|
-
catch (
|
|
1309
|
-
//
|
|
1458
|
+
catch (e) {
|
|
1459
|
+
// Attempt to extract title from metadata for the error overlay
|
|
1460
|
+
let pageTitle;
|
|
1461
|
+
try {
|
|
1462
|
+
if (PageModule) {
|
|
1463
|
+
if (PageModule.metadata && PageModule.metadata.title) {
|
|
1464
|
+
pageTitle = PageModule.metadata.title;
|
|
1465
|
+
}
|
|
1466
|
+
else if (typeof PageModule.getMetadata === 'function') {
|
|
1467
|
+
const t = (key, params) => {
|
|
1468
|
+
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1469
|
+
let text = dict[key] || key;
|
|
1470
|
+
if (params) {
|
|
1471
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
1472
|
+
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return text;
|
|
1476
|
+
};
|
|
1477
|
+
const meta = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1478
|
+
if (meta && meta.title)
|
|
1479
|
+
pageTitle = meta.title;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
catch (metaErr) {
|
|
1484
|
+
// ignore metadata extraction errors during error handling
|
|
1485
|
+
}
|
|
1486
|
+
const errorInfo = {
|
|
1487
|
+
message: err?.message || String(err),
|
|
1488
|
+
stack: err?.stack,
|
|
1489
|
+
file: route.component,
|
|
1490
|
+
title: pageTitle,
|
|
1491
|
+
};
|
|
1492
|
+
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1493
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1494
|
+
res.end(overlayHTML);
|
|
1495
|
+
return;
|
|
1310
1496
|
}
|
|
1311
|
-
const errorInfo = {
|
|
1312
|
-
message: err?.message || String(err),
|
|
1313
|
-
stack: err?.stack,
|
|
1314
|
-
file: route.component,
|
|
1315
|
-
title: pageTitle,
|
|
1316
|
-
};
|
|
1317
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1318
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1319
|
-
res.end(overlayHTML);
|
|
1320
|
-
return;
|
|
1321
1497
|
}
|
|
1322
1498
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
console.log('SSR: globals.css exists at', cssPath, fs_1.default.existsSync(cssPath));
|
|
1333
|
-
}
|
|
1334
|
-
catch (e) {
|
|
1335
|
-
// ignore
|
|
1336
|
-
}
|
|
1337
|
-
// attempt to obtain metadata from the page module (static or via getMetadata)
|
|
1338
|
-
let metaHtml = '';
|
|
1339
|
-
let htmlLang = 'en';
|
|
1340
|
-
try {
|
|
1341
|
-
const { renderMetaTags } = await import('../assets/metadata.js');
|
|
1342
|
-
// metadata may be exported as `metadata` or `getMetadata` function
|
|
1343
|
-
let metaObj = null;
|
|
1344
|
-
if (PageModule && PageModule.metadata) {
|
|
1345
|
-
metaObj = PageModule.metadata;
|
|
1499
|
+
// expose routes + initial params to client for Router
|
|
1500
|
+
const routePaths = allRoutes.length ? allRoutes.map(r => r.path) : [];
|
|
1501
|
+
// Check whether layout and CSS ended up in the rendered HTML for debugging
|
|
1502
|
+
try {
|
|
1503
|
+
const hasHeader = pageHTML.includes('<header');
|
|
1504
|
+
const hasFooter = pageHTML.includes('<footer');
|
|
1505
|
+
console.log(`SSR: rendered page includes header=${hasHeader} footer=${hasFooter}`);
|
|
1506
|
+
const cssPath = path_1.default.join(tempDir, 'globals.css');
|
|
1507
|
+
console.log('SSR: globals.css exists at', cssPath, fs_1.default.existsSync(cssPath));
|
|
1346
1508
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
metaObj = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1509
|
+
catch (e) {
|
|
1510
|
+
// ignore
|
|
1511
|
+
}
|
|
1512
|
+
// attempt to obtain metadata from the page module (static or via getMetadata)
|
|
1513
|
+
let metaHtml = '';
|
|
1514
|
+
let htmlLang = 'en';
|
|
1515
|
+
try {
|
|
1516
|
+
const { renderMetaTags } = await import('../assets/metadata.js');
|
|
1517
|
+
// metadata may be exported as `metadata` or `getMetadata` function
|
|
1518
|
+
let metaObj = null;
|
|
1519
|
+
if (PageModule && PageModule.metadata) {
|
|
1520
|
+
metaObj = PageModule.metadata;
|
|
1360
1521
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1522
|
+
else if (PageModule && typeof PageModule.getMetadata === 'function') {
|
|
1523
|
+
try {
|
|
1524
|
+
const t = (key, params) => {
|
|
1525
|
+
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1526
|
+
let text = dict[key] || key;
|
|
1527
|
+
if (params) {
|
|
1528
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
1529
|
+
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
return text;
|
|
1533
|
+
};
|
|
1534
|
+
metaObj = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1535
|
+
}
|
|
1536
|
+
catch (e) {
|
|
1537
|
+
metaObj = null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
// The `<Head>` component was removed; rely on exported `metadata` or `getMetadata` from the page/layout module.
|
|
1541
|
+
// Detect html lang from metadata if provided
|
|
1542
|
+
if (metaObj && typeof metaObj.lang === 'string' && metaObj.lang.trim()) {
|
|
1543
|
+
htmlLang = metaObj.lang.trim();
|
|
1363
1544
|
}
|
|
1545
|
+
metaHtml = renderMetaTags(metaObj);
|
|
1364
1546
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
if (metaObj && typeof metaObj.lang === 'string' && metaObj.lang.trim()) {
|
|
1368
|
-
htmlLang = metaObj.lang.trim();
|
|
1547
|
+
catch (e) {
|
|
1548
|
+
// ignore
|
|
1369
1549
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
const initialParamsScript = `<script>
|
|
1376
|
-
window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)};
|
|
1377
|
-
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
1378
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1550
|
+
const initialParamsScript = `<script>
|
|
1551
|
+
window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)};
|
|
1552
|
+
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
1553
|
+
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1554
|
+
window.__SATSET_LOCALE__ = "${locale}";
|
|
1379
1555
|
</script>`;
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
<html
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
<meta
|
|
1399
|
-
|
|
1400
|
-
${
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
<script
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
</
|
|
1556
|
+
// Determine favicon at request time (in case it was created/changed)
|
|
1557
|
+
let computedFavicon = null;
|
|
1558
|
+
try {
|
|
1559
|
+
const publicPath = path_1.default.join(root, publicDirName);
|
|
1560
|
+
if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.ico')))
|
|
1561
|
+
computedFavicon = '/favicon.ico';
|
|
1562
|
+
else if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.png')))
|
|
1563
|
+
computedFavicon = '/favicon.png';
|
|
1564
|
+
}
|
|
1565
|
+
catch (e) {
|
|
1566
|
+
// ignore
|
|
1567
|
+
}
|
|
1568
|
+
const faviconLink = computedFavicon ? `<link rel="icon" href="${computedFavicon}" />` : '';
|
|
1569
|
+
let html = '';
|
|
1570
|
+
const trimmedPageHTML = pageHTML.trim();
|
|
1571
|
+
if (trimmedPageHTML.toLowerCase().startsWith('<html')) {
|
|
1572
|
+
html = '<!DOCTYPE html>\n' + trimmedPageHTML;
|
|
1573
|
+
const headContent = `
|
|
1574
|
+
<meta charset="UTF-8" />
|
|
1575
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1576
|
+
${metaHtml}
|
|
1577
|
+
${faviconLink}
|
|
1578
|
+
<link rel="stylesheet" href="/_satset/globals.css" />`;
|
|
1579
|
+
if (html.includes('</head>')) {
|
|
1580
|
+
html = html.replace('</head>', `${headContent}</head>`);
|
|
1581
|
+
}
|
|
1582
|
+
const bodyScripts = `
|
|
1583
|
+
<script>${envScript}</script>
|
|
1584
|
+
${initialParamsScript}
|
|
1585
|
+
<script type="module" src="/_satset/_entry.js"></script>
|
|
1586
|
+
<script src="/__hmr"></script>`;
|
|
1587
|
+
if (html.includes('</body>')) {
|
|
1588
|
+
html = html.replace('</body>', `${bodyScripts}</body>`);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
else {
|
|
1592
|
+
html = `
|
|
1593
|
+
<!DOCTYPE html>
|
|
1594
|
+
<html lang="${htmlLang}" suppressHydrationWarning>
|
|
1595
|
+
<head>
|
|
1596
|
+
<meta charset="UTF-8" />
|
|
1597
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1598
|
+
${metaHtml}
|
|
1599
|
+
${faviconLink}
|
|
1600
|
+
<link rel="stylesheet" href="/_satset/globals.css" />
|
|
1601
|
+
</head>
|
|
1602
|
+
<body>
|
|
1603
|
+
<div id="root">${pageHTML}</div>
|
|
1604
|
+
<script>${envScript}</script>
|
|
1605
|
+
${initialParamsScript}
|
|
1606
|
+
<script type="module" src="/_satset/_entry.js"></script>
|
|
1607
|
+
<script src="/__hmr"></script>
|
|
1608
|
+
</body>
|
|
1609
|
+
</html>
|
|
1412
1610
|
`;
|
|
1413
|
-
res.writeHead(statusCode, { 'Content-Type': 'text/html' });
|
|
1414
|
-
res.end(html);
|
|
1415
|
-
}
|
|
1416
|
-
catch (error) {
|
|
1417
|
-
const errorInfo = {
|
|
1418
|
-
message: error.message,
|
|
1419
|
-
stack: error.stack,
|
|
1420
|
-
file: route.component,
|
|
1421
|
-
};
|
|
1422
|
-
if (route.component && fs_1.default.existsSync(route.component)) {
|
|
1423
|
-
const stackLines = error.stack?.split('\n') || [];
|
|
1424
|
-
const lineMatch = stackLines[0]?.match(/:(\d+):(\d+)/);
|
|
1425
|
-
if (lineMatch) {
|
|
1426
|
-
const line = parseInt(lineMatch[1]);
|
|
1427
|
-
errorInfo.line = line;
|
|
1428
|
-
errorInfo.column = parseInt(lineMatch[2]);
|
|
1429
|
-
errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(route.component, line);
|
|
1430
1611
|
}
|
|
1612
|
+
res.writeHead(statusCode, { 'Content-Type': 'text/html' });
|
|
1613
|
+
res.end(html);
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
const errorInfo = {
|
|
1617
|
+
message: error.message,
|
|
1618
|
+
stack: error.stack,
|
|
1619
|
+
file: route.component,
|
|
1620
|
+
};
|
|
1621
|
+
if (route.component && fs_1.default.existsSync(route.component)) {
|
|
1622
|
+
const stackLines = error.stack?.split('\n') || [];
|
|
1623
|
+
const lineMatch = stackLines[0]?.match(/:(\d+):(\d+)/);
|
|
1624
|
+
if (lineMatch) {
|
|
1625
|
+
const line = parseInt(lineMatch[1]);
|
|
1626
|
+
errorInfo.line = line;
|
|
1627
|
+
errorInfo.column = parseInt(lineMatch[2]);
|
|
1628
|
+
errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(route.component, line);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1632
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1633
|
+
res.end(overlayHTML);
|
|
1431
1634
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1436
|
-
finally {
|
|
1437
|
-
(0, response_1.setCurrentRequestCookies)(null);
|
|
1438
|
-
}
|
|
1635
|
+
finally {
|
|
1636
|
+
(0, response_1.setCurrentRequestCookies)(null);
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1439
1639
|
}
|
|
1440
1640
|
async function runMiddleware(root, tempDir, req, res, effectivePath) {
|
|
1441
1641
|
try {
|