satset-react 0.0.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/adapter/index.d.ts +4 -0
  2. package/dist/adapter/index.d.ts.map +1 -0
  3. package/dist/adapter/index.js +8 -0
  4. package/dist/adapter/index.js.map +1 -0
  5. package/dist/adapter/node.d.ts +3 -0
  6. package/dist/adapter/node.d.ts.map +1 -0
  7. package/dist/adapter/node.js +244 -0
  8. package/dist/adapter/node.js.map +1 -0
  9. package/dist/adapter/types.d.ts +24 -0
  10. package/dist/adapter/types.d.ts.map +1 -0
  11. package/dist/adapter/types.js +3 -0
  12. package/dist/adapter/types.js.map +1 -0
  13. package/dist/adapter/vercel.d.ts +3 -0
  14. package/dist/adapter/vercel.d.ts.map +1 -0
  15. package/dist/adapter/vercel.js +173 -0
  16. package/dist/adapter/vercel.js.map +1 -0
  17. package/dist/assets/favicon.d.ts +2 -0
  18. package/dist/assets/favicon.d.ts.map +1 -0
  19. package/dist/assets/favicon.js +67 -0
  20. package/dist/assets/favicon.js.map +1 -0
  21. package/dist/assets/index.d.ts +6 -0
  22. package/dist/assets/index.d.ts.map +1 -0
  23. package/dist/assets/index.js +14 -0
  24. package/dist/assets/index.js.map +1 -0
  25. package/dist/assets/metadata.d.ts +23 -0
  26. package/dist/assets/metadata.d.ts.map +1 -0
  27. package/dist/assets/metadata.js +55 -0
  28. package/dist/assets/metadata.js.map +1 -0
  29. package/dist/assets/robots.d.ts +11 -0
  30. package/dist/assets/robots.d.ts.map +1 -0
  31. package/dist/assets/robots.js +70 -0
  32. package/dist/assets/robots.js.map +1 -0
  33. package/dist/assets/sitemap.d.ts +12 -0
  34. package/dist/assets/sitemap.d.ts.map +1 -0
  35. package/dist/assets/sitemap.js +71 -0
  36. package/dist/assets/sitemap.js.map +1 -0
  37. package/dist/cli/commands.d.ts +4 -0
  38. package/dist/cli/commands.d.ts.map +1 -0
  39. package/dist/cli/commands.js +74 -0
  40. package/dist/cli/commands.js.map +1 -0
  41. package/dist/cli/index.d.ts +3 -0
  42. package/dist/cli/index.d.ts.map +1 -0
  43. package/dist/cli/index.js +32 -0
  44. package/dist/cli/index.js.map +1 -0
  45. package/dist/components/ErrorBoundary.d.ts +22 -0
  46. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  47. package/dist/components/ErrorBoundary.js +56 -0
  48. package/dist/components/ErrorBoundary.js.map +1 -0
  49. package/dist/components/Image.d.ts +15 -0
  50. package/dist/components/Image.d.ts.map +1 -0
  51. package/dist/components/Image.js +65 -0
  52. package/dist/components/Image.js.map +1 -0
  53. package/dist/components/Link.d.ts +14 -0
  54. package/dist/components/Link.d.ts.map +1 -0
  55. package/dist/components/Link.js +67 -0
  56. package/dist/components/Link.js.map +1 -0
  57. package/dist/components/Script.d.ts +10 -0
  58. package/dist/components/Script.d.ts.map +1 -0
  59. package/dist/components/Script.js +60 -0
  60. package/dist/components/Script.js.map +1 -0
  61. package/dist/components/index.d.ts +5 -0
  62. package/dist/components/index.d.ts.map +1 -0
  63. package/dist/components/index.js +15 -0
  64. package/dist/components/index.js.map +1 -0
  65. package/dist/core/hydrate.d.ts +8 -0
  66. package/dist/core/hydrate.d.ts.map +1 -0
  67. package/dist/core/hydrate.js +73 -0
  68. package/dist/core/hydrate.js.map +1 -0
  69. package/dist/core/index.d.ts +8 -0
  70. package/dist/core/index.d.ts.map +1 -0
  71. package/dist/core/index.js +19 -0
  72. package/dist/core/index.js.map +1 -0
  73. package/dist/core/ssr.d.ts +28 -0
  74. package/dist/core/ssr.d.ts.map +1 -0
  75. package/dist/core/ssr.js +91 -0
  76. package/dist/core/ssr.js.map +1 -0
  77. package/dist/core/types.d.ts +24 -0
  78. package/dist/core/types.d.ts.map +1 -0
  79. package/dist/core/types.js +3 -0
  80. package/dist/core/types.js.map +1 -0
  81. package/dist/index.d.ts +8 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +27 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/navigation/notFound.d.ts +2 -0
  86. package/dist/navigation/notFound.d.ts.map +1 -0
  87. package/dist/navigation/notFound.js +9 -0
  88. package/dist/navigation/notFound.js.map +1 -0
  89. package/dist/router/file-system.d.ts +19 -0
  90. package/dist/router/file-system.d.ts.map +1 -0
  91. package/dist/router/file-system.js +332 -0
  92. package/dist/router/file-system.js.map +1 -0
  93. package/dist/router/index.d.ts +4 -0
  94. package/dist/router/index.d.ts.map +1 -0
  95. package/dist/router/index.js +12 -0
  96. package/dist/router/index.js.map +1 -0
  97. package/dist/router/router.d.ts +17 -0
  98. package/dist/router/router.d.ts.map +1 -0
  99. package/dist/router/router.js +138 -0
  100. package/dist/router/router.js.map +1 -0
  101. package/dist/router/types.d.ts +11 -0
  102. package/dist/router/types.d.ts.map +1 -0
  103. package/dist/router/types.js +3 -0
  104. package/dist/router/types.js.map +1 -0
  105. package/dist/server/build.d.ts +5 -0
  106. package/dist/server/build.d.ts.map +1 -0
  107. package/dist/server/build.js +449 -0
  108. package/dist/server/build.js.map +1 -0
  109. package/dist/server/bundler.d.ts +27 -0
  110. package/dist/server/bundler.d.ts.map +1 -0
  111. package/dist/server/bundler.js +213 -0
  112. package/dist/server/bundler.js.map +1 -0
  113. package/dist/server/dev.d.ts +11 -0
  114. package/dist/server/dev.d.ts.map +1 -0
  115. package/dist/server/dev.js +1052 -0
  116. package/dist/server/dev.js.map +1 -0
  117. package/dist/server/env.d.ts +10 -0
  118. package/dist/server/env.d.ts.map +1 -0
  119. package/dist/server/env.js +102 -0
  120. package/dist/server/env.js.map +1 -0
  121. package/dist/server/error-overlay.d.ts +11 -0
  122. package/dist/server/error-overlay.d.ts.map +1 -0
  123. package/dist/server/error-overlay.js +395 -0
  124. package/dist/server/error-overlay.js.map +1 -0
  125. package/dist/server/hmr.d.ts +8 -0
  126. package/dist/server/hmr.d.ts.map +1 -0
  127. package/dist/server/hmr.js +166 -0
  128. package/dist/server/hmr.js.map +1 -0
  129. package/dist/server/index.d.ts +5 -0
  130. package/dist/server/index.d.ts.map +1 -0
  131. package/dist/server/index.js +10 -0
  132. package/dist/server/index.js.map +1 -0
  133. package/dist/server/response.d.ts +38 -0
  134. package/dist/server/response.d.ts.map +1 -0
  135. package/dist/server/response.js +142 -0
  136. package/dist/server/response.js.map +1 -0
  137. package/dist/server/types.d.ts +13 -0
  138. package/dist/server/types.d.ts.map +1 -0
  139. package/dist/server/types.js +3 -0
  140. package/dist/server/types.js.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,1052 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startDevServer = startDevServer;
7
+ const http_1 = __importDefault(require("http"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const file_system_1 = require("../router/file-system");
11
+ const hmr_1 = require("./hmr");
12
+ const env_1 = require("./env");
13
+ const error_overlay_1 = require("./error-overlay");
14
+ const bundler_1 = require("./bundler");
15
+ const response_1 = require("./response");
16
+ const util_1 = __importDefault(require("util"));
17
+ let buildCache = new Map();
18
+ async function startDevServer(config = {}) {
19
+ const { port = 3000, host = 'localhost', root = process.cwd(), publicDir = 'public', favicon, } = config;
20
+ // Favicon link - set during startup
21
+ let faviconHref = null;
22
+ // Load environment variables
23
+ const env = (0, env_1.loadEnv)(root, 'development');
24
+ console.log('🚀 Starting Satset dev server...');
25
+ // Debug: log current working root and contents to ensure correct project path
26
+ console.debug('[dev] starting with root:', root);
27
+ try {
28
+ const rootFiles = fs_1.default.readdirSync(root);
29
+ console.debug('[dev] root files:', rootFiles);
30
+ }
31
+ catch (e) {
32
+ const rootErrMsg = e?.message ?? String(e);
33
+ console.debug('[dev] could not read root dir', rootErrMsg);
34
+ }
35
+ // Scan routes
36
+ const { routes, apiRoutes } = (0, file_system_1.getRoutes)(root);
37
+ console.log(`📁 Found ${routes.length} pages and ${apiRoutes.length} API routes`);
38
+ // Generate sitemap and robots into public/ if not present
39
+ try {
40
+ const { generateAndSaveSitemap } = await import('../assets/sitemap.js');
41
+ const { generateAndSaveRobots } = await import('../assets/robots.js');
42
+ await generateAndSaveSitemap(root, routes);
43
+ await generateAndSaveRobots(root);
44
+ }
45
+ catch (e) {
46
+ // ignore if asset generators aren't available
47
+ }
48
+ // Ensure favicon if configured or generate default if missing
49
+ async function ensureFavicon(rootDir, publicDirName, faviconSetting) {
50
+ const publicPath = path_1.default.join(rootDir, publicDirName);
51
+ try {
52
+ if (!fs_1.default.existsSync(publicPath))
53
+ fs_1.default.mkdirSync(publicPath, { recursive: true });
54
+ }
55
+ catch (e) { }
56
+ // If a setting is provided, handle several types
57
+ if (faviconSetting) {
58
+ // github:username -> use generateFavicon helper
59
+ if (typeof faviconSetting === 'string' && faviconSetting.startsWith('github:')) {
60
+ const username = faviconSetting.split(':', 2)[1];
61
+ try {
62
+ const { generateFavicon } = await import('../assets/favicon.js');
63
+ await generateFavicon(rootDir, username);
64
+ faviconHref = '/favicon.png';
65
+ return;
66
+ }
67
+ catch (e) {
68
+ console.warn('Could not generate favicon from github username:', e);
69
+ }
70
+ }
71
+ // URL -> download into public
72
+ if (/^https?:\/\//i.test(faviconSetting)) {
73
+ try {
74
+ const url = faviconSetting;
75
+ const ext = path_1.default.extname(new URL(url).pathname) || '.png';
76
+ const destName = ext === '.ico' ? 'favicon.ico' : 'favicon.png';
77
+ const destPath = path_1.default.join(publicPath, destName);
78
+ await downloadToFile(url, destPath);
79
+ faviconHref = `/${destName}`;
80
+ return;
81
+ }
82
+ catch (e) {
83
+ console.warn('Failed to download favicon from URL:', e);
84
+ }
85
+ }
86
+ // Local path relative to project
87
+ try {
88
+ const srcPath = path_1.default.isAbsolute(faviconSetting) ? faviconSetting : path_1.default.join(rootDir, faviconSetting);
89
+ if (fs_1.default.existsSync(srcPath)) {
90
+ const ext = path_1.default.extname(srcPath) || '.png';
91
+ const destName = ext === '.ico' ? 'favicon.ico' : 'favicon.png';
92
+ const destPath = path_1.default.join(publicPath, destName);
93
+ fs_1.default.copyFileSync(srcPath, destPath);
94
+ faviconHref = `/${destName}`;
95
+ return;
96
+ }
97
+ }
98
+ catch (e) {
99
+ console.warn('Failed to use local favicon path:', e);
100
+ }
101
+ }
102
+ // If still no favicon, try to generate a default favicon.png if it doesn't exist
103
+ const pngPath = path_1.default.join(publicPath, 'favicon.png');
104
+ const icoPath = path_1.default.join(publicPath, 'favicon.ico');
105
+ if (fs_1.default.existsSync(pngPath) || fs_1.default.existsSync(icoPath)) {
106
+ faviconHref = fs_1.default.existsSync(icoPath) ? '/favicon.ico' : '/favicon.png';
107
+ return;
108
+ }
109
+ try {
110
+ const { generateFavicon } = await import('../assets/favicon.js');
111
+ await generateFavicon(rootDir);
112
+ faviconHref = '/favicon.png';
113
+ }
114
+ catch (e) {
115
+ // ignore - favicon generation optional
116
+ }
117
+ }
118
+ function downloadToFile(url, dest) {
119
+ return new Promise((resolve, reject) => {
120
+ const proto = url.startsWith('https') ? require('https') : require('http');
121
+ const file = fs_1.default.createWriteStream(dest);
122
+ proto.get(url, (resp) => {
123
+ if (resp.statusCode !== 200)
124
+ return reject(new Error('Failed to download'));
125
+ resp.pipe(file);
126
+ file.on('finish', () => { file.close(); resolve(); });
127
+ }).on('error', (err) => {
128
+ try {
129
+ fs_1.default.unlinkSync(dest);
130
+ }
131
+ catch (e) { }
132
+ reject(err);
133
+ });
134
+ });
135
+ }
136
+ // Run now
137
+ await ensureFavicon(root, publicDir, favicon);
138
+ // Setup build output directory
139
+ const tempDir = path_1.default.join(root, '.satset', 'temp');
140
+ if (!fs_1.default.existsSync(tempDir)) {
141
+ fs_1.default.mkdirSync(tempDir, { recursive: true });
142
+ }
143
+ // Initial build
144
+ console.log('📦 Building client bundle...');
145
+ await buildClientBundle(root, tempDir, routes);
146
+ // Create HTTP server
147
+ const server = http_1.default.createServer(async (req, res) => {
148
+ const url = req.url || '/';
149
+ // Serve bundled JS files
150
+ if (url.startsWith('/_satset/')) {
151
+ const filePath = path_1.default.join(tempDir, url.replace('/_satset/', ''));
152
+ if (fs_1.default.existsSync(filePath)) {
153
+ const ext = path_1.default.extname(filePath);
154
+ const contentType = getContentType(ext);
155
+ res.writeHead(200, { 'Content-Type': contentType });
156
+ fs_1.default.createReadStream(filePath).pipe(res);
157
+ return;
158
+ }
159
+ }
160
+ // Serve static files from public/
161
+ if (url.startsWith('/public/') || url.startsWith('/assets/')) {
162
+ const filePath = path_1.default.join(root, publicDir, url.replace(/^\/(public|assets)\//, ''));
163
+ if (fs_1.default.existsSync(filePath)) {
164
+ const ext = path_1.default.extname(filePath);
165
+ const contentType = getContentType(ext);
166
+ res.writeHead(200, { 'Content-Type': contentType });
167
+ fs_1.default.createReadStream(filePath).pipe(res);
168
+ return;
169
+ }
170
+ }
171
+ // Serve direct public files at root URLs (e.g., /image.png -> public/image.png)
172
+ // This mirrors Vite's behavior where static files in `public/` are available at '/{file}'
173
+ try {
174
+ const reqPath = (url || '/').split('?')[0].split('#')[0];
175
+ const parsed = path_1.default.parse(reqPath);
176
+ if (parsed.ext) {
177
+ const publicFilePath = path_1.default.join(root, publicDir, reqPath.replace(/^\/+/, ''));
178
+ if (fs_1.default.existsSync(publicFilePath)) {
179
+ const contentType = getContentType(parsed.ext.toLowerCase());
180
+ res.writeHead(200, { 'Content-Type': contentType });
181
+ fs_1.default.createReadStream(publicFilePath).pipe(res);
182
+ return;
183
+ }
184
+ }
185
+ }
186
+ catch (e) {
187
+ // ignore file serving errors
188
+ }
189
+ // Handle error reporting endpoint
190
+ if (url === '/__satset_error' && req.method === 'POST') {
191
+ let body = '';
192
+ req.on('data', chunk => { body += chunk; });
193
+ req.on('end', () => {
194
+ try {
195
+ const errorInfo = JSON.parse(body);
196
+ console.error('🔴 Runtime Error:', errorInfo.message);
197
+ if (errorInfo.file && errorInfo.line) {
198
+ errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(errorInfo.file, errorInfo.line);
199
+ }
200
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
201
+ res.writeHead(200, { 'Content-Type': 'text/html' });
202
+ res.end(overlayHTML);
203
+ }
204
+ catch (e) {
205
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
206
+ res.end('Bad Request');
207
+ }
208
+ });
209
+ return;
210
+ }
211
+ // Simple Server Actions endpoint (POST JSON: { name: 'actionName', data: { ... } })
212
+ if (url === '/_satset/action' && req.method === 'POST') {
213
+ let body = '';
214
+ req.on('data', chunk => { body += chunk; });
215
+ req.on('end', async () => {
216
+ try {
217
+ const payload = JSON.parse(body || '{}');
218
+ const actionName = payload?.name;
219
+ const data = payload?.data || {};
220
+ if (!actionName) {
221
+ res.writeHead(400, { 'Content-Type': 'application/json' });
222
+ res.end(JSON.stringify({ error: 'Missing action name' }));
223
+ return;
224
+ }
225
+ // try to find actions file
226
+ const candidates = ['src/app/actions.ts', 'src/app/actions.js', 'src/app/actions.tsx'];
227
+ let actionsPath = null;
228
+ for (const c of candidates) {
229
+ const p = path_1.default.join(root, c.replace(/^src\//, 'src/'));
230
+ if (fs_1.default.existsSync(p)) {
231
+ actionsPath = p;
232
+ break;
233
+ }
234
+ }
235
+ if (!actionsPath) {
236
+ res.writeHead(404, { 'Content-Type': 'application/json' });
237
+ res.end(JSON.stringify({ error: 'No actions file found' }));
238
+ return;
239
+ }
240
+ const baseName = path_1.default.basename(actionsPath).replace(/\.[^.]+$/, '');
241
+ const compiled = path_1.default.join(tempDir, baseName + '.actions.server.js');
242
+ try {
243
+ try {
244
+ await bundler_1.bundler.bundleServer({ entryPoint: actionsPath, outfile: compiled, minify: false, root, sourcemap: true });
245
+ }
246
+ catch (err) {
247
+ const msg = String(err && err.message ? err.message : err);
248
+ if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
249
+ console.warn('Low disk space detected while bundling actions. Attempting to clear temp and retry without sourcemaps.');
250
+ try {
251
+ if (fs_1.default.existsSync(tempDir)) {
252
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
253
+ }
254
+ fs_1.default.mkdirSync(tempDir, { recursive: true });
255
+ }
256
+ catch (cleanupErr) {
257
+ console.error('Failed to cleanup temp dir:', cleanupErr);
258
+ }
259
+ await bundler_1.bundler.bundleServer({ entryPoint: actionsPath, outfile: compiled, minify: false, root, sourcemap: false });
260
+ }
261
+ else {
262
+ throw err;
263
+ }
264
+ }
265
+ try {
266
+ delete require.cache[require.resolve(compiled)];
267
+ }
268
+ catch (e) { }
269
+ const ActionsModule = require(compiled);
270
+ const fn = ActionsModule && (ActionsModule[actionName] || (ActionsModule.default && ActionsModule.default[actionName]));
271
+ if (!fn || typeof fn !== 'function') {
272
+ res.writeHead(404, { 'Content-Type': 'application/json' });
273
+ res.end(JSON.stringify({ error: `Action not found: ${actionName}` }));
274
+ return;
275
+ }
276
+ // basic FormData-like helper
277
+ const formLike = { _map: data, get(k) { return this._map[k]; }, entries() { return Object.entries(this._map); } };
278
+ try {
279
+ global.__SATSET_ACTION_RES__ = res;
280
+ }
281
+ catch (e) { }
282
+ let result;
283
+ try {
284
+ result = await fn(formLike);
285
+ }
286
+ finally {
287
+ try {
288
+ delete global.__SATSET_ACTION_RES__;
289
+ }
290
+ catch (e) { }
291
+ }
292
+ // SatsetResponse handling
293
+ const respMod = require('./response');
294
+ if (result instanceof respMod.SatsetResponse) {
295
+ respMod.sendSatsetResponse(res, result);
296
+ return;
297
+ }
298
+ res.writeHead(200, { 'Content-Type': 'application/json' });
299
+ res.end(JSON.stringify({ result }));
300
+ return;
301
+ }
302
+ catch (e) {
303
+ const errInfo = { message: (e && e.message) || String(e), stack: e && e.stack, file: actionsPath };
304
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errInfo);
305
+ res.writeHead(500, { 'Content-Type': 'text/html' });
306
+ res.end(overlayHTML);
307
+ return;
308
+ }
309
+ }
310
+ catch (e) {
311
+ res.writeHead(400, { 'Content-Type': 'application/json' });
312
+ res.end(JSON.stringify({ error: 'Invalid request' }));
313
+ }
314
+ });
315
+ return;
316
+ }
317
+ // Handle API routes (support dynamic patterns)
318
+ const apiMatch = (0, file_system_1.matchRoute)(url, apiRoutes);
319
+ if (apiMatch) {
320
+ await handleApiRoute(apiMatch.route, req, res, root, tempDir, apiMatch.params);
321
+ return;
322
+ }
323
+ // Handle page routes (SSR) with dynamic matching
324
+ const matched = (0, file_system_1.matchRoute)(url, routes);
325
+ if (matched) {
326
+ await handlePageRoute(matched.route, req, res, root, tempDir, matched.params, routes, publicDir);
327
+ return;
328
+ }
329
+ // 404
330
+ res.writeHead(404, { 'Content-Type': 'text/html' });
331
+ res.end('<h1>404 - Page Not Found</h1>');
332
+ });
333
+ // Start HMR with bundler integration
334
+ const hmrServer = (0, hmr_1.startHMR)(server, root, async (changedFile) => {
335
+ console.log(`🔄 Rebuilding: ${changedFile}`);
336
+ await buildClientBundle(root, tempDir, routes);
337
+ });
338
+ const bindHost = host === true ? '0.0.0.0' : (host === false ? 'localhost' : host);
339
+ server.listen(port, bindHost, () => {
340
+ console.log(`✅ Server running at http://${bindHost === '0.0.0.0' ? 'localhost' : bindHost}:${port}`);
341
+ if (host === true) {
342
+ try {
343
+ const os = require('os');
344
+ const nets = os.networkInterfaces();
345
+ const addresses = [];
346
+ for (const name of Object.keys(nets)) {
347
+ for (const net of nets[name]) {
348
+ if (net.family === 'IPv4' && !net.internal)
349
+ addresses.push(net.address);
350
+ }
351
+ }
352
+ for (const a of addresses) {
353
+ console.log(`🔗 On your network: http://${a}:${port}`);
354
+ }
355
+ }
356
+ catch (e) { }
357
+ }
358
+ console.log(`🔥 HMR enabled`);
359
+ });
360
+ return { server, hmrServer };
361
+ }
362
+ async function buildClientBundle(root, outdir, routes) {
363
+ try {
364
+ // Create entry file that imports all pages
365
+ const importLines = [];
366
+ const routeDefLines = [];
367
+ let importIndex = 0;
368
+ for (const route of routes) {
369
+ if (!fs_1.default.existsSync(route.component)) {
370
+ // skip removed/missing components (can happen during HMR file moves)
371
+ console.warn('Skipping missing component while building client bundle:', route.component);
372
+ continue;
373
+ }
374
+ // Only include client components ("use client") in the client bundle
375
+ let includeInClient = false;
376
+ try {
377
+ const content = fs_1.default.readFileSync(route.component, 'utf-8');
378
+ if (content.includes("'use client'") || content.includes('"use client"')) {
379
+ includeInClient = true;
380
+ }
381
+ }
382
+ catch (e) {
383
+ includeInClient = false;
384
+ }
385
+ if (!includeInClient) {
386
+ // Skip server-only pages to avoid bundling Node-only imports (fs/path)
387
+ console.debug('Skipping server-only page for client bundle:', route.component);
388
+ continue;
389
+ }
390
+ const relativePath = path_1.default.relative(root, route.component).replace(/\\/g, '/');
391
+ importLines.push(`import Page${importIndex} from '../../${relativePath}';`);
392
+ routeDefLines.push(` { path: '${route.path}', component: Page${importIndex} },`);
393
+ importIndex++;
394
+ }
395
+ const entryContent = `
396
+ import React from 'react';
397
+ import { hydrateRoot } from 'react-dom/client';
398
+
399
+ // Import all page components
400
+ ${importLines.join('\n')}
401
+
402
+ // Route definitions
403
+ const routeDefs = [
404
+ ${routeDefLines.join('\n')}
405
+ ];
406
+
407
+ function matchPath(pathname) {
408
+ const pathSegments = pathname.split('/').filter(Boolean);
409
+ for (const r of routeDefs) {
410
+ const routeSegments = r.path.split('/').filter(Boolean);
411
+
412
+ // catch-all
413
+ if (r.path.includes('*')) {
414
+ const catchIndex = routeSegments.findIndex(s => s.startsWith('*'));
415
+ const paramName = routeSegments[catchIndex].slice(1);
416
+ const params = {};
417
+ params[paramName] = pathSegments.slice(catchIndex).join('/');
418
+ return { component: r.component, params };
419
+ }
420
+
421
+ if (routeSegments.length !== pathSegments.length) continue;
422
+
423
+ let matched = true;
424
+ const params = {};
425
+
426
+ for (let i = 0; i < routeSegments.length; i++) {
427
+ const rs = routeSegments[i];
428
+ const ps = pathSegments[i];
429
+
430
+ if (rs.startsWith(':')) {
431
+ params[rs.slice(1)] = ps;
432
+ } else if (rs !== ps) {
433
+ matched = false;
434
+ break;
435
+ }
436
+ }
437
+
438
+ if (matched) {
439
+ return { component: r.component, params };
440
+ }
441
+ }
442
+
443
+ // fallback to root route
444
+ return { component: routeDefs.find(r => r.path === '/')?.component, params: {} };
445
+ }
446
+
447
+ // Hydrate based on current path
448
+ const currentPath = window.location.pathname;
449
+ const match = matchPath(currentPath);
450
+ // expose routes and params to Router
451
+ window.__SATSET_ROUTES__ = routeDefs.map(r => r.path);
452
+ window.__SATSET_PARAMS__ = match.params || {};
453
+
454
+ const PageComponent = match.component;
455
+
456
+ if (PageComponent) {
457
+ const root = document.getElementById('root');
458
+ if (root) {
459
+ hydrateRoot(root, React.createElement(PageComponent, match.params ? { params: match.params } : undefined));
460
+ }
461
+ }
462
+ `;
463
+ const entryPath = path_1.default.join(outdir, '_entry.tsx');
464
+ fs_1.default.writeFileSync(entryPath, entryContent);
465
+ // Ensure a global CSS exists in outdir
466
+ const cssPath = path_1.default.join(outdir, 'globals.css');
467
+ try {
468
+ if (!fs_1.default.existsSync(cssPath)) {
469
+ // Try to copy project-specific styles if present
470
+ const projectCss = path_1.default.join(root, 'src', 'styles', 'global.css');
471
+ if (fs_1.default.existsSync(projectCss)) {
472
+ try {
473
+ console.debug('[dev] copying project global css from', projectCss, 'to', cssPath);
474
+ fs_1.default.copyFileSync(projectCss, cssPath);
475
+ console.debug('[dev] copied global css');
476
+ }
477
+ catch (err) {
478
+ console.warn('[dev] failed to copy project css:', err);
479
+ fs_1.default.writeFileSync(cssPath, 'body{font-family:system-ui;}.container{max-width:900px;margin:0 auto;padding:24px}');
480
+ }
481
+ }
482
+ else {
483
+ fs_1.default.writeFileSync(cssPath, 'body{font-family:system-ui;}.container{max-width:900px;margin:0 auto;padding:24px}');
484
+ console.debug('[dev] wrote default globals.css to', cssPath);
485
+ }
486
+ }
487
+ }
488
+ catch (err) {
489
+ console.warn('[dev] error ensuring globals.css:', err);
490
+ }
491
+ // Bundle with esbuild
492
+ await bundler_1.bundler.bundle({
493
+ entryPoints: [entryPath],
494
+ outdir,
495
+ minify: false,
496
+ sourcemap: true,
497
+ watch: false,
498
+ root,
499
+ });
500
+ console.log('✅ Client bundle ready');
501
+ }
502
+ catch (error) {
503
+ console.error('❌ Build error:', error.message);
504
+ }
505
+ }
506
+ async function handleApiRoute(route, req, res, root, tempDir, params = {}) {
507
+ try {
508
+ // Compile API module to CJS before requiring to support TS/ESM sources
509
+ const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
510
+ const compiledApiPath = path_1.default.join(tempDir, baseName + '.api.server.js');
511
+ let ApiModule = null;
512
+ // Use the same safe bundler helper for API modules
513
+ async function safeBundleApi(entry, outfile) {
514
+ try {
515
+ await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
516
+ }
517
+ catch (err) {
518
+ const msg = String(err && err.message ? err.message : err);
519
+ if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
520
+ try {
521
+ if (fs_1.default.existsSync(tempDir)) {
522
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
523
+ }
524
+ fs_1.default.mkdirSync(tempDir, { recursive: true });
525
+ }
526
+ catch (cleanupErr) {
527
+ console.error('Failed to cleanup temp dir:', cleanupErr);
528
+ }
529
+ await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
530
+ return;
531
+ }
532
+ throw err;
533
+ }
534
+ }
535
+ try {
536
+ await safeBundleApi(route.component, compiledApiPath);
537
+ try {
538
+ delete require.cache[require.resolve(compiledApiPath)];
539
+ }
540
+ catch (e) { }
541
+ ApiModule = require(compiledApiPath);
542
+ }
543
+ catch (err) {
544
+ console.error('API bundling failed for', route.component, err);
545
+ const errMsg = String(err && err.message ? err.message : err);
546
+ const errorInfo = {
547
+ message: /ENOSPC|not enough space|There is not enough space/i.test(errMsg) ? 'API bundling failed: not enough disk space. Free disk space or delete .satset/temp' : 'API bundling failed',
548
+ stack: err && err.stack ? err.stack : String(err),
549
+ file: route.component,
550
+ };
551
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
552
+ res.writeHead(500, { 'Content-Type': 'text/html' });
553
+ res.end(overlayHTML);
554
+ return;
555
+ }
556
+ function resolveHandler(mod) {
557
+ if (!mod)
558
+ return null;
559
+ // default export function
560
+ if (typeof mod === 'function')
561
+ return { default: mod };
562
+ if (mod && typeof mod === 'object') {
563
+ if (typeof mod.default === 'function')
564
+ return { default: mod.default };
565
+ // method-named exports (GET, POST, etc.)
566
+ const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
567
+ const out = {};
568
+ for (const m of methods) {
569
+ if (typeof mod[m] === 'function')
570
+ out[m] = mod[m];
571
+ }
572
+ if (Object.keys(out).length)
573
+ return out;
574
+ }
575
+ return null;
576
+ }
577
+ const handlerObj = resolveHandler(ApiModule);
578
+ const method = (req.method || 'GET').toUpperCase();
579
+ async function callHandlerWithNodeStyle(fn) {
580
+ // If function declares at least two args, assume Node-style (req, res)
581
+ if (fn.length >= 2) {
582
+ const maybe = await fn(req, res, params);
583
+ // If the handler returns a SatsetResponse, send it
584
+ if (response_1.SatsetResponse.isSatsetResponse(maybe)) {
585
+ (0, response_1.sendSatsetResponse)(res, maybe);
586
+ }
587
+ // assume the handler handled the Node res
588
+ return true;
589
+ }
590
+ return false;
591
+ }
592
+ async function callHandlerWithWebStyle(fn) {
593
+ // Build a lightweight Request-like object for route handlers
594
+ const webReq = await (0, response_1.buildSatsetRequest)(req);
595
+ const result = await fn(webReq, { params });
596
+ if (response_1.SatsetResponse.isSatsetResponse(result)) {
597
+ (0, response_1.sendSatsetResponse)(res, result);
598
+ return true;
599
+ }
600
+ // If result is a native Response-like object with json/text, try to handle
601
+ if (result && typeof result === 'object' && typeof result.status === 'number' && (result.headers || result.json)) {
602
+ // Try basic mapping
603
+ const status = result.status || 200;
604
+ // Normalize headers: support Headers instance, array of tuples, or plain object
605
+ let headers = {};
606
+ const rawHeaders = result.headers;
607
+ if (rawHeaders) {
608
+ if (typeof rawHeaders.get === 'function' && typeof rawHeaders.entries === 'function') {
609
+ headers = Object.fromEntries(rawHeaders.entries());
610
+ }
611
+ else if (Array.isArray(rawHeaders)) {
612
+ headers = Object.fromEntries(rawHeaders);
613
+ }
614
+ else if (typeof rawHeaders === 'object') {
615
+ headers = rawHeaders;
616
+ }
617
+ }
618
+ let body = undefined;
619
+ try {
620
+ if (typeof result.json === 'function') {
621
+ body = await result.json();
622
+ res.writeHead(status, { 'Content-Type': 'application/json', ...headers });
623
+ res.end(JSON.stringify(body));
624
+ return true;
625
+ }
626
+ if (typeof result.text === 'function') {
627
+ body = await result.text();
628
+ res.writeHead(status, headers);
629
+ res.end(body);
630
+ return true;
631
+ }
632
+ }
633
+ catch (e) {
634
+ // fallthrough
635
+ }
636
+ }
637
+ // If result is an object, return as JSON
638
+ if (result && typeof result === 'object') {
639
+ const sat = response_1.SatsetResponse.json(result);
640
+ (0, response_1.sendSatsetResponse)(res, sat);
641
+ return true;
642
+ }
643
+ // If result is string or number, send as text
644
+ if (result != null) {
645
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
646
+ res.end(String(result));
647
+ return true;
648
+ }
649
+ // undefined -> assume handler will manage res; if not, leave to caller
650
+ return false;
651
+ }
652
+ // Prefer method-named export for App-style handlers
653
+ if (handlerObj && typeof handlerObj[method] === 'function') {
654
+ const fn = handlerObj[method];
655
+ // Try node-style first
656
+ const nodeTook = await callHandlerWithNodeStyle(fn);
657
+ if (nodeTook)
658
+ return;
659
+ // Fallback to Web-style
660
+ const webTook = await callHandlerWithWebStyle(fn);
661
+ if (webTook)
662
+ return;
663
+ // no response produced
664
+ res.writeHead(204);
665
+ res.end();
666
+ return;
667
+ }
668
+ // Default export fallback
669
+ if (handlerObj && typeof handlerObj.default === 'function') {
670
+ const fn = handlerObj.default;
671
+ const nodeTook = await callHandlerWithNodeStyle(fn);
672
+ if (nodeTook)
673
+ return;
674
+ const webTook = await callHandlerWithWebStyle(fn);
675
+ if (webTook)
676
+ return;
677
+ res.writeHead(204);
678
+ res.end();
679
+ return;
680
+ }
681
+ res.writeHead(405, { 'Content-Type': 'application/json' });
682
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
683
+ }
684
+ catch (error) {
685
+ const errorInfo = {
686
+ message: error.message,
687
+ stack: error.stack,
688
+ file: route.component,
689
+ };
690
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
691
+ res.writeHead(500, { 'Content-Type': 'text/html' });
692
+ res.end(overlayHTML);
693
+ }
694
+ }
695
+ async function handlePageRoute(route, req, res, root, tempDir, initialParams = {}, allRoutes = [], publicDirName = 'public') {
696
+ try {
697
+ // Load env
698
+ const env = (0, env_1.loadEnv)(root, 'development');
699
+ const envScript = (0, env_1.getPublicEnvScript)(env.publicVars);
700
+ // Server-side render the component
701
+ // Compile page module to a temporary server file so Node can require it (handles tsx/esm)
702
+ const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
703
+ const compiledServerPath = path_1.default.join(tempDir, baseName + '.server.js');
704
+ let PageModule = null;
705
+ // Helper: bundle server build with low-disk fallback
706
+ async function safeBundleServer(entry, outfile) {
707
+ try {
708
+ await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
709
+ }
710
+ catch (err) {
711
+ const msg = String(err && err.message ? err.message : err);
712
+ // detect low-disk / ENOSPC errors (esbuild reports "There is not enough space on the disk" in Windows)
713
+ if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
714
+ console.warn('Low disk space detected while bundling. Attempting to clear temp and retry without sourcemaps.');
715
+ try {
716
+ // clear temp dir to free space
717
+ if (fs_1.default.existsSync(tempDir)) {
718
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
719
+ }
720
+ fs_1.default.mkdirSync(tempDir, { recursive: true });
721
+ }
722
+ catch (cleanupErr) {
723
+ console.error('Failed to cleanup temp dir:', cleanupErr);
724
+ }
725
+ try {
726
+ await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
727
+ return;
728
+ }
729
+ catch (retryErr) {
730
+ // still failed
731
+ throw retryErr;
732
+ }
733
+ }
734
+ // Not a disk-space issue, rethrow
735
+ throw err;
736
+ }
737
+ }
738
+ try {
739
+ // Bundle the page for Node (CJS)
740
+ await safeBundleServer(route.component, compiledServerPath);
741
+ try {
742
+ delete require.cache[require.resolve(compiledServerPath)];
743
+ }
744
+ catch (e) {
745
+ // ignore
746
+ }
747
+ PageModule = require(compiledServerPath);
748
+ }
749
+ catch (err) {
750
+ // If bundling fails, show an overlay with the bundling error and do NOT require the original TSX (which would crash)
751
+ console.error('SSR bundling failed for', route.component, err);
752
+ let message = 'SSR bundling failed';
753
+ const errMsg = String(err && err.message ? err.message : err);
754
+ if (/ENOSPC|not enough space|There is not enough space/i.test(errMsg)) {
755
+ message = 'SSR bundling failed: not enough disk space. Try freeing disk space or deleting the .satset/temp folder.';
756
+ }
757
+ const errorInfo = {
758
+ message,
759
+ stack: err && err.stack ? err.stack : String(err),
760
+ file: route.component,
761
+ };
762
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
763
+ res.writeHead(500, { 'Content-Type': 'text/html' });
764
+ res.end(overlayHTML);
765
+ return;
766
+ }
767
+ function resolveExportedComponent(mod) {
768
+ if (!mod)
769
+ return null;
770
+ if (typeof mod === 'function')
771
+ return mod;
772
+ if (mod && typeof mod === 'object') {
773
+ if (typeof mod.default === 'function')
774
+ return mod.default;
775
+ if (mod.default && typeof mod.default === 'object' && typeof mod.default.default === 'function')
776
+ return mod.default.default;
777
+ for (const key of Object.keys(mod)) {
778
+ if (typeof mod[key] === 'function')
779
+ return mod[key];
780
+ }
781
+ }
782
+ return null;
783
+ }
784
+ const PageComponent = resolveExportedComponent(PageModule);
785
+ console.log('SSR: used component from', compiledServerPath, 'PageModule keys:', Object.keys(PageModule || {}), 'Resolved component type:', typeof PageComponent);
786
+ console.log('SSR: PageModule keys:', Object.keys(PageModule || {}), 'PageComponent type:', typeof PageComponent);
787
+ // Simple SSR with optional global layout composition
788
+ const React = require('react');
789
+ const { renderToString } = require('react-dom/server');
790
+ let pageHTML = '<div>Loading...</div>';
791
+ // Helper to render a component (sync or async) to HTML string
792
+ async function renderComponentToHtml(Comp, props = {}) {
793
+ if (!Comp || typeof Comp !== 'function') {
794
+ throw new Error('Invalid component to render');
795
+ }
796
+ const isAsync = Comp.constructor && Comp.constructor.name === 'AsyncFunction';
797
+ if (isAsync) {
798
+ const maybeNode = Comp(props);
799
+ const resolved = await Promise.resolve(maybeNode);
800
+ // If the async component returned a React element, render it
801
+ return renderToString(resolved);
802
+ }
803
+ return renderToString(React.createElement(Comp, props));
804
+ }
805
+ let statusCode = 200;
806
+ try {
807
+ // Ensure we actually have a function/component to render
808
+ if (!PageComponent || typeof PageComponent !== 'function') {
809
+ console.error('SSR Error: resolved page component is not a function. PageModule:', util_1.default.inspect(PageModule, { depth: 2 }));
810
+ 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>`;
811
+ res.writeHead(500, { 'Content-Type': 'text/html' });
812
+ res.end(diagHtml);
813
+ return;
814
+ }
815
+ // Try to find a global layout route (path '/layout') in the discovered routes
816
+ let LayoutComponent = null;
817
+ try {
818
+ const layoutRoute = allRoutes.find(r => r.path === '/layout');
819
+ if (layoutRoute && layoutRoute.component) {
820
+ const layoutBase = path_1.default.basename(layoutRoute.component).replace(/\.[^.]+$/, '');
821
+ const layoutCompiled = path_1.default.join(tempDir, layoutBase + '.layout.server.js');
822
+ await bundler_1.bundler.bundleServer({ entryPoint: layoutRoute.component, outfile: layoutCompiled, minify: false, root });
823
+ try {
824
+ delete require.cache[require.resolve(layoutCompiled)];
825
+ }
826
+ catch (e) { }
827
+ const LayoutModule = require(layoutCompiled);
828
+ // resolve exported component if present
829
+ function resolveExportedComp(mod) {
830
+ if (!mod)
831
+ return null;
832
+ if (typeof mod === 'function')
833
+ return mod;
834
+ if (mod && typeof mod === 'object') {
835
+ if (typeof mod.default === 'function')
836
+ return mod.default;
837
+ for (const key of Object.keys(mod)) {
838
+ if (typeof mod[key] === 'function')
839
+ return mod[key];
840
+ }
841
+ }
842
+ return null;
843
+ }
844
+ LayoutComponent = resolveExportedComp(LayoutModule);
845
+ if (!LayoutComponent) {
846
+ console.warn('Layout module found but no component exported:', layoutRoute.component);
847
+ }
848
+ else {
849
+ console.log('Using layout module for SSR:', layoutRoute.component);
850
+ }
851
+ }
852
+ }
853
+ catch (e) {
854
+ const msg = e?.message ?? String(e);
855
+ console.warn('Could not load layout module:', msg);
856
+ }
857
+ // Render page (possibly async)
858
+ if (LayoutComponent) {
859
+ // render page as child and pass to layout
860
+ const PageFragmentHtml = await renderComponentToHtml(PageComponent, { params: initialParams });
861
+ // We need to render the Layout with the page element as `children` prop.
862
+ // Create a small wrapper component that returns the layout with children
863
+ const Wrapper = (props) => React.createElement(LayoutComponent, null, React.createElement(PageComponent, { params: initialParams }));
864
+ pageHTML = await renderComponentToHtml(Wrapper);
865
+ }
866
+ else {
867
+ pageHTML = await renderComponentToHtml(PageComponent, { params: initialParams });
868
+ }
869
+ }
870
+ catch (err) {
871
+ console.error('SSR Error:', err);
872
+ // special handling: notFound() throws a sentinel that we can identify
873
+ if (err && err.__SATSET_NOT_FOUND) {
874
+ statusCode = 404;
875
+ // attempt to render a root-level /not-found route if present
876
+ try {
877
+ const nf = allRoutes.find(r => r.path === '/not-found' || r.path === '/404');
878
+ if (nf && nf.component) {
879
+ const nfBase = path_1.default.basename(nf.component).replace(/\.[^.]+$/, '');
880
+ const nfCompiled = path_1.default.join(tempDir, nfBase + '.notfound.server.js');
881
+ await bundler_1.bundler.bundleServer({ entryPoint: nf.component, outfile: nfCompiled, minify: false, root });
882
+ try {
883
+ delete require.cache[require.resolve(nfCompiled)];
884
+ }
885
+ catch (e) { }
886
+ const NfModule = require(nfCompiled);
887
+ const NfComp = (typeof NfModule === 'function') ? NfModule : (NfModule && typeof NfModule.default === 'function') ? NfModule.default : null;
888
+ if (NfComp) {
889
+ pageHTML = await renderComponentToHtml(NfComp, { params: initialParams });
890
+ }
891
+ else {
892
+ pageHTML = '<h1>404 - Page Not Found</h1>';
893
+ }
894
+ }
895
+ else {
896
+ pageHTML = '<h1>404 - Page Not Found</h1>';
897
+ }
898
+ }
899
+ catch (e) {
900
+ pageHTML = '<h1>404 - Page Not Found</h1>';
901
+ }
902
+ }
903
+ else {
904
+ // general error: try to render /error route if present
905
+ statusCode = 500;
906
+ try {
907
+ const er = allRoutes.find(r => r.path === '/error');
908
+ if (er && er.component) {
909
+ const erBase = path_1.default.basename(er.component).replace(/\.[^.]+$/, '');
910
+ const erCompiled = path_1.default.join(tempDir, erBase + '.error.server.js');
911
+ await bundler_1.bundler.bundleServer({ entryPoint: er.component, outfile: erCompiled, minify: false, root });
912
+ try {
913
+ delete require.cache[require.resolve(erCompiled)];
914
+ }
915
+ catch (e) { }
916
+ const ErModule = require(erCompiled);
917
+ const ErComp = (typeof ErModule === 'function') ? ErModule : (ErModule && typeof ErModule.default === 'function') ? ErModule.default : null;
918
+ if (ErComp) {
919
+ // pass error details to the error component for display
920
+ pageHTML = await renderComponentToHtml(ErComp, { error: err, reset: () => { } });
921
+ }
922
+ else {
923
+ throw err;
924
+ }
925
+ }
926
+ else {
927
+ throw err;
928
+ }
929
+ }
930
+ catch (e) {
931
+ // If all else fails, produce the dev overlay
932
+ const errorInfo = {
933
+ message: err?.message || String(err),
934
+ stack: err?.stack,
935
+ file: route.component,
936
+ };
937
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
938
+ res.writeHead(500, { 'Content-Type': 'text/html' });
939
+ res.end(overlayHTML);
940
+ return;
941
+ }
942
+ }
943
+ }
944
+ // expose routes + initial params to client for Router
945
+ const routePaths = allRoutes.length ? allRoutes.map(r => r.path) : [];
946
+ // Check whether layout and CSS ended up in the rendered HTML for debugging
947
+ try {
948
+ const hasHeader = pageHTML.includes('<header');
949
+ const hasFooter = pageHTML.includes('<footer');
950
+ console.log(`SSR: rendered page includes header=${hasHeader} footer=${hasFooter}`);
951
+ const cssPath = path_1.default.join(tempDir, 'globals.css');
952
+ console.log('SSR: globals.css exists at', cssPath, fs_1.default.existsSync(cssPath));
953
+ }
954
+ catch (e) {
955
+ // ignore
956
+ }
957
+ // attempt to obtain metadata from the page module (static or via getMetadata)
958
+ let metaHtml = '';
959
+ try {
960
+ const { renderMetaTags } = await import('../assets/metadata.js');
961
+ // metadata may be exported as `metadata` or `getMetadata` function
962
+ let metaObj = null;
963
+ if (PageModule && PageModule.metadata) {
964
+ metaObj = PageModule.metadata;
965
+ }
966
+ else if (PageModule && typeof PageModule.getMetadata === 'function') {
967
+ try {
968
+ metaObj = await PageModule.getMetadata({ params: initialParams });
969
+ }
970
+ catch (e) {
971
+ metaObj = null;
972
+ }
973
+ }
974
+ // The `<Head>` component was removed; rely on exported `metadata` or `getMetadata` from the page/layout module.
975
+ metaHtml = renderMetaTags(metaObj);
976
+ }
977
+ catch (e) {
978
+ // ignore
979
+ }
980
+ const initialParamsScript = `<script>window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)}; window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};</script>`;
981
+ // Determine favicon at request time (in case it was created/changed)
982
+ let computedFavicon = null;
983
+ try {
984
+ const publicPath = path_1.default.join(root, publicDirName);
985
+ if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.ico')))
986
+ computedFavicon = '/favicon.ico';
987
+ else if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.png')))
988
+ computedFavicon = '/favicon.png';
989
+ }
990
+ catch (e) {
991
+ // ignore
992
+ }
993
+ const faviconLink = computedFavicon ? `<link rel="icon" href="${computedFavicon}" />` : '';
994
+ const html = `
995
+ <!DOCTYPE html>
996
+ <html lang="en">
997
+ <head>
998
+ <meta charset="UTF-8" />
999
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1000
+ ${metaHtml}
1001
+ ${faviconLink}
1002
+ <link rel="stylesheet" href="/_satset/globals.css" />
1003
+ </head>
1004
+ <body>
1005
+ <div id="root">${pageHTML}</div>
1006
+ <script>${envScript}</script>
1007
+ ${initialParamsScript}
1008
+ <script type="module" src="/_satset/_entry.js"></script>
1009
+ <script src="/__hmr"></script>
1010
+ <!-- ... rest of scripts -->
1011
+ </body>
1012
+ </html>
1013
+ `;
1014
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1015
+ res.end(html);
1016
+ }
1017
+ catch (error) {
1018
+ const errorInfo = {
1019
+ message: error.message,
1020
+ stack: error.stack,
1021
+ file: route.component,
1022
+ };
1023
+ if (route.component && fs_1.default.existsSync(route.component)) {
1024
+ const stackLines = error.stack?.split('\n') || [];
1025
+ const lineMatch = stackLines[0]?.match(/:(\d+):(\d+)/);
1026
+ if (lineMatch) {
1027
+ const line = parseInt(lineMatch[1]);
1028
+ errorInfo.line = line;
1029
+ errorInfo.column = parseInt(lineMatch[2]);
1030
+ errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(route.component, line);
1031
+ }
1032
+ }
1033
+ const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
1034
+ res.writeHead(500, { 'Content-Type': 'text/html' });
1035
+ res.end(overlayHTML);
1036
+ }
1037
+ }
1038
+ function getContentType(ext) {
1039
+ const types = {
1040
+ '.html': 'text/html',
1041
+ '.css': 'text/css',
1042
+ '.js': 'application/javascript',
1043
+ '.json': 'application/json',
1044
+ '.png': 'image/png',
1045
+ '.jpg': 'image/jpeg',
1046
+ '.svg': 'image/svg+xml',
1047
+ '.woff': 'font/woff',
1048
+ '.woff2': 'font/woff2',
1049
+ };
1050
+ return types[ext] || 'text/plain';
1051
+ }
1052
+ //# sourceMappingURL=dev.js.map