satset-react 0.4.5 → 0.4.7
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/package.json +2 -3
- package/dist/adapter/index.d.ts +0 -4
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js +0 -8
- package/dist/adapter/index.js.map +0 -1
- package/dist/adapter/node.d.ts +0 -3
- package/dist/adapter/node.d.ts.map +0 -1
- package/dist/adapter/node.js +0 -244
- package/dist/adapter/node.js.map +0 -1
- package/dist/adapter/types.d.ts +0 -24
- package/dist/adapter/types.d.ts.map +0 -1
- package/dist/adapter/types.js +0 -3
- package/dist/adapter/types.js.map +0 -1
- package/dist/adapter/vercel.d.ts +0 -10
- package/dist/adapter/vercel.d.ts.map +0 -1
- package/dist/adapter/vercel.js +0 -320
- package/dist/adapter/vercel.js.map +0 -1
- package/dist/assets/favicon.d.ts +0 -2
- package/dist/assets/favicon.d.ts.map +0 -1
- package/dist/assets/favicon.js +0 -67
- package/dist/assets/favicon.js.map +0 -1
- package/dist/assets/index.d.ts +0 -7
- package/dist/assets/index.d.ts.map +0 -1
- package/dist/assets/index.js +0 -31
- package/dist/assets/index.js.map +0 -1
- package/dist/assets/metadata.d.ts +0 -29
- package/dist/assets/metadata.d.ts.map +0 -1
- package/dist/assets/metadata.js +0 -55
- package/dist/assets/metadata.js.map +0 -1
- package/dist/assets/robots.d.ts +0 -23
- package/dist/assets/robots.d.ts.map +0 -1
- package/dist/assets/robots.js +0 -95
- package/dist/assets/robots.js.map +0 -1
- package/dist/assets/sitemap.d.ts +0 -19
- package/dist/assets/sitemap.d.ts.map +0 -1
- package/dist/assets/sitemap.js +0 -78
- package/dist/assets/sitemap.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -5
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -165
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -40
- package/dist/cli/index.js.map +0 -1
- package/dist/components/ErrorBoundary.d.ts +0 -22
- package/dist/components/ErrorBoundary.d.ts.map +0 -1
- package/dist/components/ErrorBoundary.js +0 -61
- package/dist/components/ErrorBoundary.js.map +0 -1
- package/dist/components/Image.d.ts +0 -15
- package/dist/components/Image.d.ts.map +0 -1
- package/dist/components/Image.js +0 -67
- package/dist/components/Image.js.map +0 -1
- package/dist/components/Link.d.ts +0 -14
- package/dist/components/Link.d.ts.map +0 -1
- package/dist/components/Link.js +0 -66
- package/dist/components/Link.js.map +0 -1
- package/dist/components/Script.d.ts +0 -10
- package/dist/components/Script.d.ts.map +0 -1
- package/dist/components/Script.js +0 -60
- package/dist/components/Script.js.map +0 -1
- package/dist/components/index.d.ts +0 -6
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/index.js +0 -30
- package/dist/components/index.js.map +0 -1
- package/dist/components/theme.d.ts +0 -23
- package/dist/components/theme.d.ts.map +0 -1
- package/dist/components/theme.js +0 -118
- package/dist/components/theme.js.map +0 -1
- package/dist/config.d.ts +0 -23
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -9
- package/dist/config.js.map +0 -1
- package/dist/core/dynamic.d.ts +0 -10
- package/dist/core/dynamic.d.ts.map +0 -1
- package/dist/core/dynamic.js +0 -51
- package/dist/core/dynamic.js.map +0 -1
- package/dist/core/hydrate.d.ts +0 -8
- package/dist/core/hydrate.d.ts.map +0 -1
- package/dist/core/hydrate.js +0 -73
- package/dist/core/hydrate.js.map +0 -1
- package/dist/core/index.d.ts +0 -10
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -22
- package/dist/core/index.js.map +0 -1
- package/dist/core/response.d.ts +0 -26
- package/dist/core/response.d.ts.map +0 -1
- package/dist/core/response.js +0 -48
- package/dist/core/response.js.map +0 -1
- package/dist/core/ssr.d.ts +0 -29
- package/dist/core/ssr.d.ts.map +0 -1
- package/dist/core/ssr.js +0 -91
- package/dist/core/ssr.js.map +0 -1
- package/dist/core/translation.d.ts +0 -19
- package/dist/core/translation.d.ts.map +0 -1
- package/dist/core/translation.js +0 -84
- package/dist/core/translation.js.map +0 -1
- package/dist/core/types.d.ts +0 -47
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -3
- package/dist/core/types.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -25
- package/dist/index.js.map +0 -1
- package/dist/navigation/notFound.d.ts +0 -3
- package/dist/navigation/notFound.d.ts.map +0 -1
- package/dist/navigation/notFound.js +0 -15
- package/dist/navigation/notFound.js.map +0 -1
- package/dist/router/file-system.d.ts +0 -19
- package/dist/router/file-system.d.ts.map +0 -1
- package/dist/router/file-system.js +0 -364
- package/dist/router/file-system.js.map +0 -1
- package/dist/router/index.d.ts +0 -3
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/index.js +0 -10
- package/dist/router/index.js.map +0 -1
- package/dist/router/router.d.ts +0 -17
- package/dist/router/router.d.ts.map +0 -1
- package/dist/router/router.js +0 -158
- package/dist/router/router.js.map +0 -1
- package/dist/router/types.d.ts +0 -11
- package/dist/router/types.d.ts.map +0 -1
- package/dist/router/types.js +0 -3
- package/dist/router/types.js.map +0 -1
- package/dist/server/build.d.ts +0 -5
- package/dist/server/build.d.ts.map +0 -1
- package/dist/server/build.js +0 -780
- package/dist/server/build.js.map +0 -1
- package/dist/server/bundler.d.ts +0 -28
- package/dist/server/bundler.d.ts.map +0 -1
- package/dist/server/bundler.js +0 -401
- package/dist/server/bundler.js.map +0 -1
- package/dist/server/dev.d.ts +0 -11
- package/dist/server/dev.d.ts.map +0 -1
- package/dist/server/dev.js +0 -1818
- package/dist/server/dev.js.map +0 -1
- package/dist/server/env.d.ts +0 -10
- package/dist/server/env.d.ts.map +0 -1
- package/dist/server/env.js +0 -102
- package/dist/server/env.js.map +0 -1
- package/dist/server/error-overlay.d.ts +0 -12
- package/dist/server/error-overlay.d.ts.map +0 -1
- package/dist/server/error-overlay.js +0 -396
- package/dist/server/error-overlay.js.map +0 -1
- package/dist/server/hmr.d.ts +0 -8
- package/dist/server/hmr.d.ts.map +0 -1
- package/dist/server/hmr.js +0 -166
- package/dist/server/hmr.js.map +0 -1
- package/dist/server/index.d.ts +0 -10
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -25
- package/dist/server/index.js.map +0 -1
- package/dist/server/response.d.ts +0 -30
- package/dist/server/response.d.ts.map +0 -1
- package/dist/server/response.js +0 -238
- package/dist/server/response.js.map +0 -1
- package/dist/server/storage.d.ts +0 -13
- package/dist/server/storage.d.ts.map +0 -1
- package/dist/server/storage.js +0 -26
- package/dist/server/storage.js.map +0 -1
- package/dist/server/tui.d.ts +0 -23
- package/dist/server/tui.d.ts.map +0 -1
- package/dist/server/tui.js +0 -182
- package/dist/server/tui.js.map +0 -1
- package/dist/server/types.d.ts +0 -14
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/types.js +0 -3
- package/dist/server/types.js.map +0 -1
package/dist/server/dev.js
DELETED
|
@@ -1,1818 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.startDevServer = startDevServer;
|
|
40
|
-
const http_1 = __importDefault(require("http"));
|
|
41
|
-
const fs_1 = __importDefault(require("fs"));
|
|
42
|
-
const path_1 = __importDefault(require("path"));
|
|
43
|
-
const file_system_1 = require("../router/file-system");
|
|
44
|
-
const hmr_1 = require("./hmr");
|
|
45
|
-
const env_1 = require("./env");
|
|
46
|
-
const error_overlay_1 = require("./error-overlay");
|
|
47
|
-
const bundler_1 = require("./bundler");
|
|
48
|
-
const response_1 = require("./response");
|
|
49
|
-
const storage_1 = require("./storage");
|
|
50
|
-
const translation_1 = require("../core/translation");
|
|
51
|
-
const util_1 = __importDefault(require("util"));
|
|
52
|
-
const tui_1 = require("./tui");
|
|
53
|
-
let buildCache = new Map();
|
|
54
|
-
const buildLocks = new Map();
|
|
55
|
-
async function serializedBundle(key, fn) {
|
|
56
|
-
// If a build is already in progress for this key, wait for it
|
|
57
|
-
while (buildLocks.has(key)) {
|
|
58
|
-
try {
|
|
59
|
-
await buildLocks.get(key);
|
|
60
|
-
}
|
|
61
|
-
catch (e) {
|
|
62
|
-
// ignore previous errors
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const promise = fn();
|
|
66
|
-
buildLocks.set(key, promise);
|
|
67
|
-
try {
|
|
68
|
-
return await promise;
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
if (buildLocks.get(key) === promise) {
|
|
72
|
-
buildLocks.delete(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function formatTime() {
|
|
77
|
-
const d = new Date();
|
|
78
|
-
const h = String(d.getHours()).padStart(2, '0');
|
|
79
|
-
const m = String(d.getMinutes()).padStart(2, '0');
|
|
80
|
-
const s = String(d.getSeconds()).padStart(2, '0');
|
|
81
|
-
return `${h}:${m}:${s}`;
|
|
82
|
-
}
|
|
83
|
-
function getDictionaries(root) {
|
|
84
|
-
const langDir = path_1.default.join(root, 'src', 'lang');
|
|
85
|
-
const dictionaries = {};
|
|
86
|
-
if (fs_1.default.existsSync(langDir)) {
|
|
87
|
-
try {
|
|
88
|
-
const files = fs_1.default.readdirSync(langDir);
|
|
89
|
-
for (const file of files) {
|
|
90
|
-
if (file.endsWith('.json')) {
|
|
91
|
-
const locale = path_1.default.basename(file, '.json');
|
|
92
|
-
try {
|
|
93
|
-
const content = fs_1.default.readFileSync(path_1.default.join(langDir, file), 'utf-8');
|
|
94
|
-
dictionaries[locale] = JSON.parse(content);
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
console.warn(`[i18n] Failed to load dictionary for ${locale}:`, e);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
console.warn('[i18n] Failed to read lang directory:', e);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return dictionaries;
|
|
107
|
-
}
|
|
108
|
-
function parseRequestCookies(req) {
|
|
109
|
-
const header = req.headers['cookie'];
|
|
110
|
-
if (typeof header !== 'string' || !header.length)
|
|
111
|
-
return null;
|
|
112
|
-
const store = {};
|
|
113
|
-
const parts = header.split(';');
|
|
114
|
-
for (const part of parts) {
|
|
115
|
-
const trimmed = part.trim();
|
|
116
|
-
if (!trimmed)
|
|
117
|
-
continue;
|
|
118
|
-
const eqIndex = trimmed.indexOf('=');
|
|
119
|
-
if (eqIndex === -1)
|
|
120
|
-
continue;
|
|
121
|
-
const name = trimmed.slice(0, eqIndex).trim();
|
|
122
|
-
if (!name)
|
|
123
|
-
continue;
|
|
124
|
-
const value = trimmed.slice(eqIndex + 1).trim();
|
|
125
|
-
try {
|
|
126
|
-
store[name] = decodeURIComponent(value);
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
store[name] = value;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return store;
|
|
133
|
-
}
|
|
134
|
-
function isClientOnlyPage(componentPath) {
|
|
135
|
-
try {
|
|
136
|
-
const content = fs_1.default.readFileSync(componentPath, 'utf-8');
|
|
137
|
-
const lines = content.split(/\r?\n/);
|
|
138
|
-
for (const raw of lines) {
|
|
139
|
-
const line = raw.trim();
|
|
140
|
-
if (!line)
|
|
141
|
-
continue;
|
|
142
|
-
if (/^['"]use client['"]\s*;?$/.test(line)) {
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch (e) {
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
async function startDevServer(config = {}) {
|
|
153
|
-
// Initialize TUI first
|
|
154
|
-
tui_1.tui.start();
|
|
155
|
-
// Override console for TUI
|
|
156
|
-
const originalLog = console.log;
|
|
157
|
-
console.log = (...args) => tui_1.tui.log(util_1.default.format(...args), 'info');
|
|
158
|
-
console.warn = (...args) => tui_1.tui.log(util_1.default.format(...args), 'warn');
|
|
159
|
-
console.error = (...args) => tui_1.tui.log(util_1.default.format(...args), 'error');
|
|
160
|
-
console.info = (...args) => tui_1.tui.log(util_1.default.format(...args), 'info');
|
|
161
|
-
console.debug = (...args) => tui_1.tui.log(util_1.default.format(...args), 'info'); // Treat debug as info for TUI
|
|
162
|
-
const { port = 3000, host = 'localhost', root = process.cwd(), publicDir = 'public', favicon, } = config;
|
|
163
|
-
tui_1.tui.setPort(port);
|
|
164
|
-
// Clean previous dev artifacts
|
|
165
|
-
const satsetDir = path_1.default.join(root, '.satset');
|
|
166
|
-
if (fs_1.default.existsSync(satsetDir)) {
|
|
167
|
-
try {
|
|
168
|
-
fs_1.default.rmSync(satsetDir, { recursive: true, force: true });
|
|
169
|
-
}
|
|
170
|
-
catch (e) {
|
|
171
|
-
// ignore
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Favicon link - set during startup
|
|
175
|
-
let faviconHref = null;
|
|
176
|
-
// Load environment variables
|
|
177
|
-
const env = (0, env_1.loadEnv)(root, 'development');
|
|
178
|
-
const startTime = Date.now();
|
|
179
|
-
// Scan routes (mutable so HMR can refresh when files are added/removed)
|
|
180
|
-
let { routes, apiRoutes } = (0, file_system_1.getRoutes)(root);
|
|
181
|
-
// Ensure favicon if configured or generate default if missing
|
|
182
|
-
async function ensureFavicon(rootDir, publicDirName, faviconSetting) {
|
|
183
|
-
const publicPath = path_1.default.join(rootDir, publicDirName);
|
|
184
|
-
try {
|
|
185
|
-
if (!fs_1.default.existsSync(publicPath))
|
|
186
|
-
fs_1.default.mkdirSync(publicPath, { recursive: true });
|
|
187
|
-
}
|
|
188
|
-
catch (e) { }
|
|
189
|
-
// If a setting is provided, handle several types
|
|
190
|
-
if (faviconSetting) {
|
|
191
|
-
// github:username -> use generateFavicon helper
|
|
192
|
-
if (typeof faviconSetting === 'string' && faviconSetting.startsWith('github:')) {
|
|
193
|
-
const username = faviconSetting.split(':', 2)[1];
|
|
194
|
-
try {
|
|
195
|
-
const { generateFavicon } = await Promise.resolve().then(() => __importStar(require('../assets/favicon.js')));
|
|
196
|
-
await generateFavicon(rootDir, username);
|
|
197
|
-
faviconHref = '/favicon.png';
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
catch (e) {
|
|
201
|
-
console.warn('Could not generate favicon from github username:', e);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
// URL -> download into public
|
|
205
|
-
if (/^https?:\/\//i.test(faviconSetting)) {
|
|
206
|
-
try {
|
|
207
|
-
const url = faviconSetting;
|
|
208
|
-
const ext = path_1.default.extname(new URL(url).pathname) || '.png';
|
|
209
|
-
const destName = ext === '.ico' ? 'favicon.ico' : 'favicon.png';
|
|
210
|
-
const destPath = path_1.default.join(publicPath, destName);
|
|
211
|
-
await downloadToFile(url, destPath);
|
|
212
|
-
faviconHref = `/${destName}`;
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
catch (e) {
|
|
216
|
-
console.warn('Failed to download favicon from URL:', e);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// Local path relative to project
|
|
220
|
-
try {
|
|
221
|
-
const srcPath = path_1.default.isAbsolute(faviconSetting) ? faviconSetting : path_1.default.join(rootDir, faviconSetting);
|
|
222
|
-
if (fs_1.default.existsSync(srcPath)) {
|
|
223
|
-
const ext = path_1.default.extname(srcPath) || '.png';
|
|
224
|
-
const destName = ext === '.ico' ? 'favicon.ico' : 'favicon.png';
|
|
225
|
-
const destPath = path_1.default.join(publicPath, destName);
|
|
226
|
-
fs_1.default.copyFileSync(srcPath, destPath);
|
|
227
|
-
faviconHref = `/${destName}`;
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
catch (e) {
|
|
232
|
-
console.warn('Failed to use local favicon path:', e);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// If still no favicon, try to generate a default favicon.png if it doesn't exist
|
|
236
|
-
const pngPath = path_1.default.join(publicPath, 'favicon.png');
|
|
237
|
-
const icoPath = path_1.default.join(publicPath, 'favicon.ico');
|
|
238
|
-
if (fs_1.default.existsSync(pngPath) || fs_1.default.existsSync(icoPath)) {
|
|
239
|
-
faviconHref = fs_1.default.existsSync(icoPath) ? '/favicon.ico' : '/favicon.png';
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
try {
|
|
243
|
-
const { generateFavicon } = await Promise.resolve().then(() => __importStar(require('../assets/favicon.js')));
|
|
244
|
-
await generateFavicon(rootDir);
|
|
245
|
-
faviconHref = '/favicon.png';
|
|
246
|
-
}
|
|
247
|
-
catch (e) {
|
|
248
|
-
// ignore - favicon generation optional
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function downloadToFile(url, dest) {
|
|
252
|
-
return new Promise((resolve, reject) => {
|
|
253
|
-
const proto = url.startsWith('https') ? require('https') : require('http');
|
|
254
|
-
const file = fs_1.default.createWriteStream(dest);
|
|
255
|
-
proto.get(url, (resp) => {
|
|
256
|
-
if (resp.statusCode !== 200)
|
|
257
|
-
return reject(new Error('Failed to download'));
|
|
258
|
-
resp.pipe(file);
|
|
259
|
-
file.on('finish', () => { file.close(); resolve(); });
|
|
260
|
-
}).on('error', (err) => {
|
|
261
|
-
try {
|
|
262
|
-
fs_1.default.unlinkSync(dest);
|
|
263
|
-
}
|
|
264
|
-
catch (e) { }
|
|
265
|
-
reject(err);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
// Run now
|
|
270
|
-
await ensureFavicon(root, publicDir, favicon);
|
|
271
|
-
// Setup build output directory
|
|
272
|
-
const tempDir = path_1.default.join(satsetDir, 'temp');
|
|
273
|
-
if (!fs_1.default.existsSync(tempDir)) {
|
|
274
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
275
|
-
}
|
|
276
|
-
// Initial build
|
|
277
|
-
await buildClientBundle(root, tempDir, routes, env.parsed);
|
|
278
|
-
// Load dictionaries
|
|
279
|
-
const dictionaries = getDictionaries(root);
|
|
280
|
-
// Create HTTP server
|
|
281
|
-
const server = http_1.default.createServer(async (req, res) => {
|
|
282
|
-
const url = req.url || '/';
|
|
283
|
-
// Parse cookies early for redirection
|
|
284
|
-
const cookieStore = parseRequestCookies(req);
|
|
285
|
-
(0, response_1.setCurrentRequestCookies)(cookieStore);
|
|
286
|
-
const rawPath = url.split('?')[0].split('#')[0] || '/';
|
|
287
|
-
// Helper to get base URL
|
|
288
|
-
const getBaseUrl = () => {
|
|
289
|
-
if (process.env.SATSET_PUBLIC_SITE_URL)
|
|
290
|
-
return process.env.SATSET_PUBLIC_SITE_URL;
|
|
291
|
-
const host = req.headers.host || 'localhost';
|
|
292
|
-
const proto = req.headers['x-forwarded-proto'] || 'http';
|
|
293
|
-
return `${proto}://${host}`;
|
|
294
|
-
};
|
|
295
|
-
// Sitemap.xml Handler
|
|
296
|
-
if (rawPath === '/sitemap.xml') {
|
|
297
|
-
try {
|
|
298
|
-
const sitemapFiles = ['sitemap.ts', 'sitemap.js', 'sitemap.tsx'];
|
|
299
|
-
let sitemapFile = null;
|
|
300
|
-
for (const f of sitemapFiles) {
|
|
301
|
-
const p = path_1.default.join(root, 'src', f);
|
|
302
|
-
if (fs_1.default.existsSync(p)) {
|
|
303
|
-
sitemapFile = p;
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (sitemapFile) {
|
|
308
|
-
const outFile = path_1.default.join(tempDir, 'sitemap.server.js');
|
|
309
|
-
await serializedBundle(outFile, () => bundler_1.bundler.bundleServer({ entryPoint: sitemapFile, 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 { generateSitemapXml } = await Promise.resolve().then(() => __importStar(require('../assets/sitemap.js')));
|
|
319
|
-
const xml = generateSitemapXml(data);
|
|
320
|
-
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
|
321
|
-
res.end(xml);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
// Fallback: Generate from routes on-the-fly
|
|
326
|
-
const { generateSitemap } = await Promise.resolve().then(() => __importStar(require('../assets/sitemap.js')));
|
|
327
|
-
const xml = generateSitemap({ baseUrl: getBaseUrl(), routes });
|
|
328
|
-
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
|
329
|
-
res.end(xml);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
catch (e) {
|
|
333
|
-
console.error('Error generating sitemap:', e);
|
|
334
|
-
res.writeHead(500);
|
|
335
|
-
res.end('Error generating sitemap');
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
// Robots.txt Handler
|
|
340
|
-
if (rawPath === '/robots.txt') {
|
|
341
|
-
try {
|
|
342
|
-
const robotsFiles = ['robots.ts', 'robots.js'];
|
|
343
|
-
let robotsFile = null;
|
|
344
|
-
for (const f of robotsFiles) {
|
|
345
|
-
const p = path_1.default.join(root, 'src', f);
|
|
346
|
-
if (fs_1.default.existsSync(p)) {
|
|
347
|
-
robotsFile = p;
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
if (robotsFile) {
|
|
352
|
-
const outFile = path_1.default.join(tempDir, 'robots.server.js');
|
|
353
|
-
await serializedBundle(outFile, () => bundler_1.bundler.bundleServer({ entryPoint: robotsFile, outfile: outFile, root }));
|
|
354
|
-
try {
|
|
355
|
-
delete require.cache[require.resolve(outFile)];
|
|
356
|
-
}
|
|
357
|
-
catch (e) { }
|
|
358
|
-
const mod = require(outFile);
|
|
359
|
-
const fn = mod.default;
|
|
360
|
-
if (typeof fn === 'function') {
|
|
361
|
-
const data = await fn();
|
|
362
|
-
const { generateRobotsTxtFromData } = await Promise.resolve().then(() => __importStar(require('../assets/robots.js')));
|
|
363
|
-
const txt = generateRobotsTxtFromData(data);
|
|
364
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
365
|
-
res.end(txt);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
// Fallback
|
|
370
|
-
const { generateRobotsTxt } = await Promise.resolve().then(() => __importStar(require('../assets/robots.js')));
|
|
371
|
-
const txt = generateRobotsTxt();
|
|
372
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
373
|
-
res.end(txt);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
catch (e) {
|
|
377
|
-
console.error('Error generating robots.txt:', e);
|
|
378
|
-
res.writeHead(500);
|
|
379
|
-
res.end('Error generating robots.txt');
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// Virtual Routing / Locale Detection
|
|
384
|
-
let locale = 'en-US';
|
|
385
|
-
let effectivePath = rawPath;
|
|
386
|
-
const segments = rawPath.split('/').filter(Boolean);
|
|
387
|
-
const firstSegment = segments[0];
|
|
388
|
-
// 1. Detect from URL (Virtual Path)
|
|
389
|
-
if (firstSegment && /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/.test(firstSegment)) {
|
|
390
|
-
locale = firstSegment;
|
|
391
|
-
effectivePath = stripLocaleFromPath(rawPath);
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
// 2. Detect from Cookie / Header
|
|
395
|
-
if (cookieStore && cookieStore['SATSET_LANG']) {
|
|
396
|
-
locale = cookieStore['SATSET_LANG'];
|
|
397
|
-
}
|
|
398
|
-
else if (req.headers['accept-language']) {
|
|
399
|
-
const accept = req.headers['accept-language'].split(',')[0].trim();
|
|
400
|
-
const match = accept.match(/^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?/);
|
|
401
|
-
if (match)
|
|
402
|
-
locale = match[0];
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
// Serve bundled JS files
|
|
406
|
-
if (url.startsWith('/_satset/')) {
|
|
407
|
-
// Remove query parameters from the URL before resolving file path
|
|
408
|
-
const cleanUrl = url.split('?')[0];
|
|
409
|
-
const filePath = path_1.default.join(tempDir, cleanUrl.replace('/_satset/', ''));
|
|
410
|
-
if (fs_1.default.existsSync(filePath)) {
|
|
411
|
-
const ext = path_1.default.extname(filePath);
|
|
412
|
-
const contentType = getContentType(ext);
|
|
413
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
414
|
-
fs_1.default.createReadStream(filePath).pipe(res);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// Serve static files from public/
|
|
419
|
-
if (url.startsWith('/public/') || url.startsWith('/assets/')) {
|
|
420
|
-
const filePath = path_1.default.join(root, publicDir, url.replace(/^\/(public|assets)\//, ''));
|
|
421
|
-
if (fs_1.default.existsSync(filePath)) {
|
|
422
|
-
const ext = path_1.default.extname(filePath);
|
|
423
|
-
const contentType = getContentType(ext);
|
|
424
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
425
|
-
fs_1.default.createReadStream(filePath).pipe(res);
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
// Serve direct public files at root URLs (e.g., /image.png -> public/image.png)
|
|
430
|
-
// This mirrors Vite's behavior where static files in `public/` are available at '/{file}'
|
|
431
|
-
try {
|
|
432
|
-
const parsed = path_1.default.parse(rawPath);
|
|
433
|
-
if (parsed.ext) {
|
|
434
|
-
const publicFilePath = path_1.default.join(root, publicDir, rawPath.replace(/^\/+/, ''));
|
|
435
|
-
if (fs_1.default.existsSync(publicFilePath)) {
|
|
436
|
-
const contentType = getContentType(parsed.ext.toLowerCase());
|
|
437
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
438
|
-
fs_1.default.createReadStream(publicFilePath).pipe(res);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
catch (e) {
|
|
444
|
-
// ignore file serving errors
|
|
445
|
-
}
|
|
446
|
-
// Handle error reporting endpoint
|
|
447
|
-
if (url === '/__satset_error' && req.method === 'POST') {
|
|
448
|
-
let body = '';
|
|
449
|
-
req.on('data', chunk => { body += chunk; });
|
|
450
|
-
req.on('end', () => {
|
|
451
|
-
try {
|
|
452
|
-
const errorInfo = JSON.parse(body);
|
|
453
|
-
console.error('🔴 Runtime Error:', errorInfo.message);
|
|
454
|
-
if (errorInfo.file && errorInfo.line) {
|
|
455
|
-
errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(errorInfo.file, errorInfo.line);
|
|
456
|
-
}
|
|
457
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
458
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
459
|
-
res.end(overlayHTML);
|
|
460
|
-
}
|
|
461
|
-
catch (e) {
|
|
462
|
-
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
463
|
-
res.end('Bad Request');
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const middlewareHandled = await runMiddleware(root, tempDir, req, res, effectivePath);
|
|
469
|
-
if (middlewareHandled) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
// Simple Server Actions endpoint (POST JSON: { name: 'actionName', data: { ... } })
|
|
473
|
-
if (url === '/_satset/action' && req.method === 'POST') {
|
|
474
|
-
let body = '';
|
|
475
|
-
req.on('data', chunk => { body += chunk; });
|
|
476
|
-
req.on('end', async () => {
|
|
477
|
-
try {
|
|
478
|
-
const payload = JSON.parse(body || '{}');
|
|
479
|
-
const actionName = payload?.name;
|
|
480
|
-
const data = payload?.data || {};
|
|
481
|
-
if (!actionName) {
|
|
482
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
483
|
-
res.end(JSON.stringify({ error: 'Missing action name' }));
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
// try to find actions file
|
|
487
|
-
const candidates = ['src/app/actions.ts', 'src/app/actions.js', 'src/app/actions.tsx'];
|
|
488
|
-
let actionsPath = null;
|
|
489
|
-
for (const c of candidates) {
|
|
490
|
-
const p = path_1.default.join(root, c.replace(/^src\//, 'src/'));
|
|
491
|
-
if (fs_1.default.existsSync(p)) {
|
|
492
|
-
actionsPath = p;
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (!actionsPath) {
|
|
497
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
498
|
-
res.end(JSON.stringify({ error: 'No actions file found' }));
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
const baseName = path_1.default.basename(actionsPath).replace(/\.[^.]+$/, '');
|
|
502
|
-
const compiled = path_1.default.join(tempDir, baseName + '.actions.server.js');
|
|
503
|
-
try {
|
|
504
|
-
try {
|
|
505
|
-
await serializedBundle(compiled, () => bundler_1.bundler.bundleServer({ entryPoint: actionsPath, outfile: compiled, minify: false, root, sourcemap: true }));
|
|
506
|
-
}
|
|
507
|
-
catch (err) {
|
|
508
|
-
const msg = String(err && err.message ? err.message : err);
|
|
509
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
510
|
-
console.warn('Low disk space detected while bundling actions. Attempting to clear temp and retry without sourcemaps.');
|
|
511
|
-
try {
|
|
512
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
513
|
-
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
514
|
-
}
|
|
515
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
516
|
-
}
|
|
517
|
-
catch (cleanupErr) {
|
|
518
|
-
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
519
|
-
}
|
|
520
|
-
await serializedBundle(compiled, () => bundler_1.bundler.bundleServer({ entryPoint: actionsPath, outfile: compiled, minify: false, root, sourcemap: false }));
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
throw err;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
try {
|
|
527
|
-
delete require.cache[require.resolve(compiled)];
|
|
528
|
-
}
|
|
529
|
-
catch (e) { }
|
|
530
|
-
const ActionsModule = require(compiled);
|
|
531
|
-
const fn = ActionsModule && (ActionsModule[actionName] || (ActionsModule.default && ActionsModule.default[actionName]));
|
|
532
|
-
if (!fn || typeof fn !== 'function') {
|
|
533
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
534
|
-
res.end(JSON.stringify({ error: `Action not found: ${actionName}` }));
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
// basic FormData-like helper
|
|
538
|
-
const formLike = { _map: data, get(k) { return this._map[k]; }, entries() { return Object.entries(this._map); } };
|
|
539
|
-
try {
|
|
540
|
-
global.__SATSET_ACTION_RES__ = res;
|
|
541
|
-
}
|
|
542
|
-
catch (e) { }
|
|
543
|
-
let result;
|
|
544
|
-
try {
|
|
545
|
-
result = await fn(formLike);
|
|
546
|
-
}
|
|
547
|
-
finally {
|
|
548
|
-
try {
|
|
549
|
-
delete global.__SATSET_ACTION_RES__;
|
|
550
|
-
}
|
|
551
|
-
catch (e) { }
|
|
552
|
-
}
|
|
553
|
-
// SatsetResponse handling
|
|
554
|
-
const respMod = require('./response');
|
|
555
|
-
if (result instanceof respMod.SatsetResponse) {
|
|
556
|
-
respMod.sendSatsetResponse(res, result);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
560
|
-
res.end(JSON.stringify({ result }));
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
catch (e) {
|
|
564
|
-
const errInfo = { message: (e && e.message) || String(e), stack: e && e.stack, file: actionsPath };
|
|
565
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errInfo);
|
|
566
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
567
|
-
res.end(overlayHTML);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
catch (e) {
|
|
572
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
573
|
-
res.end(JSON.stringify({ error: 'Invalid request' }));
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
const apiMatch = (0, file_system_1.matchRoute)(effectivePath, apiRoutes);
|
|
579
|
-
if (apiMatch) {
|
|
580
|
-
await handleApiRoute(apiMatch.route, req, res, root, tempDir, apiMatch.params, locale);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
if (effectivePath.startsWith('/api/')) {
|
|
584
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
585
|
-
res.end(JSON.stringify({ error: { code: 404, message: 'API route not found' }, path: url }));
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
const matched = (0, file_system_1.matchRoute)(effectivePath, routes);
|
|
589
|
-
if (matched) {
|
|
590
|
-
await handlePageRoute(matched.route, req, res, root, tempDir, matched.params, routes, publicDir, locale);
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const notFoundRoute = routes.find(r => r.path === '/404') ||
|
|
594
|
-
routes.find(r => r.path === '/not-found');
|
|
595
|
-
if (notFoundRoute) {
|
|
596
|
-
await handlePageRoute(notFoundRoute, req, res, root, tempDir, {}, routes, publicDir, locale);
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
600
|
-
res.end('<h1>404 - Page Not Found</h1>');
|
|
601
|
-
});
|
|
602
|
-
// Start HMR with bundler integration
|
|
603
|
-
const hmrServer = (0, hmr_1.startHMR)(server, root, async (changedFile) => {
|
|
604
|
-
const rel = path_1.default.relative(root, changedFile);
|
|
605
|
-
console.log(`🔄 [${formatTime()}] ${rel} updated`);
|
|
606
|
-
// Re-scan routes so newly added/removed pages and API routes are picked up
|
|
607
|
-
const scanned = (0, file_system_1.getRoutes)(root);
|
|
608
|
-
routes = scanned.routes;
|
|
609
|
-
apiRoutes = scanned.apiRoutes;
|
|
610
|
-
tui_1.tui.setRoutes(routes.map(r => r.path));
|
|
611
|
-
console.log(`📁 Updated to ${routes.length} pages and ${apiRoutes.length} API routes`);
|
|
612
|
-
await buildClientBundle(root, tempDir, routes, env.parsed);
|
|
613
|
-
});
|
|
614
|
-
const bindHost = host === true ? '0.0.0.0' : (host === false ? 'localhost' : host);
|
|
615
|
-
server.listen(port, bindHost, () => {
|
|
616
|
-
tui_1.tui.setRoutes(routes.map(r => r.path));
|
|
617
|
-
tui_1.tui.setHMRStatus('Active');
|
|
618
|
-
const readyIn = Date.now() - startTime;
|
|
619
|
-
const displayHost = bindHost === '0.0.0.0' ? 'localhost' : bindHost;
|
|
620
|
-
console.log(`Server ready in ${readyIn}ms`);
|
|
621
|
-
console.log(`Listening on http://${displayHost}:${port}`);
|
|
622
|
-
if (host === true) {
|
|
623
|
-
try {
|
|
624
|
-
const os = require('os');
|
|
625
|
-
const nets = os.networkInterfaces();
|
|
626
|
-
const addresses = [];
|
|
627
|
-
for (const name of Object.keys(nets)) {
|
|
628
|
-
for (const net of nets[name]) {
|
|
629
|
-
if (net.family === 'IPv4' && !net.internal)
|
|
630
|
-
addresses.push(net.address);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
// Use the first available network address for TUI
|
|
634
|
-
if (addresses.length > 0) {
|
|
635
|
-
tui_1.tui.setNetworkUrl(`http://${addresses[0]}:${port}`);
|
|
636
|
-
}
|
|
637
|
-
for (const a of addresses) {
|
|
638
|
-
console.log(`Network: http://${a}:${port}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
catch (e) { }
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
return { server, hmrServer };
|
|
645
|
-
}
|
|
646
|
-
async function buildClientBundle(root, outdir, routes, env = {}) {
|
|
647
|
-
try {
|
|
648
|
-
const importLines = [];
|
|
649
|
-
const routeDefLines = [];
|
|
650
|
-
let importIndex = 0;
|
|
651
|
-
let clientPages = 0;
|
|
652
|
-
let skippedPages = 0;
|
|
653
|
-
for (const route of routes) {
|
|
654
|
-
if (!fs_1.default.existsSync(route.component)) {
|
|
655
|
-
// skip removed/missing components (can happen during HMR file moves)
|
|
656
|
-
console.warn('Skipping missing component while building client bundle:', route.component);
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
// Only include client components ("use client") in the client bundle
|
|
660
|
-
let includeInClient = false;
|
|
661
|
-
try {
|
|
662
|
-
const content = fs_1.default.readFileSync(route.component, 'utf-8');
|
|
663
|
-
if (content.includes("'use client'") || content.includes('"use client"')) {
|
|
664
|
-
includeInClient = true;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
catch (e) {
|
|
668
|
-
includeInClient = false;
|
|
669
|
-
}
|
|
670
|
-
if (!includeInClient) {
|
|
671
|
-
skippedPages++;
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
674
|
-
const relativePath = path_1.default.relative(root, route.component).replace(/\\/g, '/');
|
|
675
|
-
// Use dynamic import for code splitting
|
|
676
|
-
routeDefLines.push(` { path: '${route.path}', component: React.lazy(() => import('../../${relativePath}')) },`);
|
|
677
|
-
importIndex++;
|
|
678
|
-
clientPages++;
|
|
679
|
-
}
|
|
680
|
-
const dictionaries = getDictionaries(root);
|
|
681
|
-
const layoutRoute = routes.find(r => r.path === '/layout');
|
|
682
|
-
let layoutImport = '';
|
|
683
|
-
let appCreationBlock;
|
|
684
|
-
if (layoutRoute && layoutRoute.component) {
|
|
685
|
-
const layoutRelative = path_1.default.relative(root, layoutRoute.component).replace(/\\/g, '/');
|
|
686
|
-
layoutImport = `import Layout from '../../${layoutRelative}';`;
|
|
687
|
-
appCreationBlock = `
|
|
688
|
-
const pageElement = React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props));
|
|
689
|
-
const withProvider = React.createElement(
|
|
690
|
-
I18nProvider,
|
|
691
|
-
{
|
|
692
|
-
initialLocale,
|
|
693
|
-
dictionaries: window.__SATSET_DICTIONARIES__
|
|
694
|
-
},
|
|
695
|
-
pageElement
|
|
696
|
-
);
|
|
697
|
-
const App = React.createElement(Layout, null, withProvider);`;
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
appCreationBlock = `
|
|
701
|
-
const App = React.createElement(
|
|
702
|
-
I18nProvider,
|
|
703
|
-
{
|
|
704
|
-
initialLocale,
|
|
705
|
-
dictionaries: window.__SATSET_DICTIONARIES__
|
|
706
|
-
},
|
|
707
|
-
React.createElement(React.Suspense, { fallback: null }, React.createElement(PageComponent, props))
|
|
708
|
-
);`;
|
|
709
|
-
}
|
|
710
|
-
const entryContent = `
|
|
711
|
-
import React from 'react';
|
|
712
|
-
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
713
|
-
import { I18nProvider } from 'satset-react';
|
|
714
|
-
${layoutImport}
|
|
715
|
-
|
|
716
|
-
// Import all page components
|
|
717
|
-
${importLines.join('\n')}
|
|
718
|
-
// Route definitions
|
|
719
|
-
const routeDefs = [
|
|
720
|
-
${routeDefLines.join('\n')}
|
|
721
|
-
];
|
|
722
|
-
|
|
723
|
-
function stripLocale(pathname) {
|
|
724
|
-
if (!pathname) return '/';
|
|
725
|
-
const raw = pathname.split('?')[0].split('#')[0];
|
|
726
|
-
const segments = raw.split('/').filter(Boolean);
|
|
727
|
-
if (!segments.length) return '/';
|
|
728
|
-
const first = segments[0];
|
|
729
|
-
const localePattern = /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/;
|
|
730
|
-
if (localePattern.test(first)) {
|
|
731
|
-
const rest = segments.slice(1);
|
|
732
|
-
return rest.length ? '/' + rest.join('/') : '/';
|
|
733
|
-
}
|
|
734
|
-
return raw.startsWith('/') ? raw : '/' + raw;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function matchPath(pathname) {
|
|
738
|
-
const normalized = stripLocale(pathname);
|
|
739
|
-
const pathSegments = normalized.split('/').filter(Boolean);
|
|
740
|
-
for (const r of routeDefs) {
|
|
741
|
-
const routeSegments = r.path.split('/').filter(Boolean);
|
|
742
|
-
|
|
743
|
-
// catch-all
|
|
744
|
-
if (r.path.includes('*')) {
|
|
745
|
-
const catchIndex = routeSegments.findIndex(s => s.startsWith('*'));
|
|
746
|
-
const paramName = routeSegments[catchIndex].slice(1);
|
|
747
|
-
const params = {};
|
|
748
|
-
params[paramName] = pathSegments.slice(catchIndex).join('/');
|
|
749
|
-
return { component: r.component, params };
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (routeSegments.length !== pathSegments.length) continue;
|
|
753
|
-
|
|
754
|
-
let matched = true;
|
|
755
|
-
const params = {};
|
|
756
|
-
|
|
757
|
-
for (let i = 0; i < routeSegments.length; i++) {
|
|
758
|
-
const rs = routeSegments[i];
|
|
759
|
-
const ps = pathSegments[i];
|
|
760
|
-
|
|
761
|
-
if (rs.startsWith(':')) {
|
|
762
|
-
params[rs.slice(1)] = ps;
|
|
763
|
-
} else if (rs !== ps) {
|
|
764
|
-
matched = false;
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
if (matched) {
|
|
770
|
-
return { component: r.component, params };
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// fallback to root route
|
|
775
|
-
return { component: routeDefs.find(r => r.path === '/')?.component, params: {} };
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Hydrate based on current path
|
|
779
|
-
const currentPath = stripLocale(window.location.pathname);
|
|
780
|
-
const match = matchPath(currentPath);
|
|
781
|
-
// expose routes and params to Router
|
|
782
|
-
window.__SATSET_ROUTES__ = routeDefs.map(r => r.path);
|
|
783
|
-
window.__SATSET_PARAMS__ = match.params || {};
|
|
784
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(dictionaries)};
|
|
785
|
-
|
|
786
|
-
const PageComponent = match.component;
|
|
787
|
-
|
|
788
|
-
if (PageComponent) {
|
|
789
|
-
const props = match.params ? { params: match.params } : undefined;
|
|
790
|
-
const initialLocale = window.__SATSET_LOCALE__ || 'en-US';
|
|
791
|
-
|
|
792
|
-
${appCreationBlock}
|
|
793
|
-
|
|
794
|
-
const root = document.getElementById('root');
|
|
795
|
-
if (root) {
|
|
796
|
-
if (root.hasChildNodes()) {
|
|
797
|
-
hydrateRoot(root, App);
|
|
798
|
-
} else {
|
|
799
|
-
const rootInstance = createRoot(root);
|
|
800
|
-
rootInstance.render(App);
|
|
801
|
-
}
|
|
802
|
-
} else {
|
|
803
|
-
React.startTransition(() => {
|
|
804
|
-
hydrateRoot(document, App);
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
`;
|
|
809
|
-
const entryPath = path_1.default.join(outdir, '_entry.tsx');
|
|
810
|
-
fs_1.default.writeFileSync(entryPath, entryContent);
|
|
811
|
-
// Ensure a global CSS exists in outdir
|
|
812
|
-
const cssPath = path_1.default.join(outdir, 'globals.css');
|
|
813
|
-
try {
|
|
814
|
-
if (!fs_1.default.existsSync(cssPath)) {
|
|
815
|
-
// Try to copy project-specific styles if present
|
|
816
|
-
const projectCss = path_1.default.join(root, 'src', 'styles', 'global.css');
|
|
817
|
-
if (fs_1.default.existsSync(projectCss)) {
|
|
818
|
-
try {
|
|
819
|
-
console.debug('[dev] copying project global css from', projectCss, 'to', cssPath);
|
|
820
|
-
fs_1.default.copyFileSync(projectCss, cssPath);
|
|
821
|
-
console.debug('[dev] copied global css');
|
|
822
|
-
}
|
|
823
|
-
catch (err) {
|
|
824
|
-
console.warn('[dev] failed to copy project css:', err);
|
|
825
|
-
fs_1.default.writeFileSync(cssPath, 'body{font-family:system-ui;}.container{max-width:900px;margin:0 auto;padding:24px}');
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
fs_1.default.writeFileSync(cssPath, 'body{font-family:system-ui;}.container{max-width:900px;margin:0 auto;padding:24px}');
|
|
830
|
-
console.debug('[dev] wrote default globals.css to', cssPath);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
catch (err) {
|
|
835
|
-
console.warn('[dev] error ensuring globals.css:', err);
|
|
836
|
-
}
|
|
837
|
-
const define = {};
|
|
838
|
-
for (const key of Object.keys(env)) {
|
|
839
|
-
if (key.startsWith('SATSET_PUBLIC_') || key.startsWith('NEXT_PUBLIC_')) {
|
|
840
|
-
define[`process.env.${key}`] = JSON.stringify(env[key]);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
await bundler_1.bundler.bundle({
|
|
844
|
-
entryPoints: [entryPath],
|
|
845
|
-
outdir,
|
|
846
|
-
minify: false,
|
|
847
|
-
sourcemap: true,
|
|
848
|
-
watch: false,
|
|
849
|
-
root,
|
|
850
|
-
define,
|
|
851
|
-
});
|
|
852
|
-
console.log(` 📦 Bundle Client ready (${skippedPages} pages skipped)`);
|
|
853
|
-
}
|
|
854
|
-
catch (error) {
|
|
855
|
-
console.error(`❌ ERROR [${formatTime()}] Client bundle failed:`, error && error.message ? error.message : String(error));
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
async function handleApiRoute(route, req, res, root, tempDir, params = {}, locale = 'en-US') {
|
|
859
|
-
const dictionaries = getDictionaries(root);
|
|
860
|
-
return storage_1.requestContext.run({ locale, dictionaries, params, pathname: req.url || '/' }, async () => {
|
|
861
|
-
try {
|
|
862
|
-
// Compile API module to CJS before requiring to support TS/ESM sources.
|
|
863
|
-
// Cache the compiled file between requests and only re-bundle when the
|
|
864
|
-
// source file is newer. Use a per-route file name derived from the
|
|
865
|
-
// component's relative path to avoid collisions between different API
|
|
866
|
-
// handlers that share the same base file name (e.g. multiple `route.ts`).
|
|
867
|
-
const relComponentPath = path_1.default
|
|
868
|
-
.relative(root, route.component)
|
|
869
|
-
.replace(/\\/g, '/')
|
|
870
|
-
.replace(/\.[^.]+$/, '');
|
|
871
|
-
const safeComponentKey = relComponentPath.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
872
|
-
const compiledApiPath = path_1.default.join(tempDir, safeComponentKey + '.api.server.js');
|
|
873
|
-
console.log('[API Debug] route:', route.path, 'component:', route.component);
|
|
874
|
-
console.log('[API Debug] safeKey:', safeComponentKey, 'compiledPath:', compiledApiPath);
|
|
875
|
-
let ApiModule = null;
|
|
876
|
-
async function safeBundleApi(entry, outfile) {
|
|
877
|
-
return serializedBundle(outfile, async () => {
|
|
878
|
-
try {
|
|
879
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
880
|
-
}
|
|
881
|
-
catch (err) {
|
|
882
|
-
const msg = String(err && err.message ? err.message : err);
|
|
883
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
884
|
-
try {
|
|
885
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
886
|
-
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
887
|
-
}
|
|
888
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
889
|
-
}
|
|
890
|
-
catch (cleanupErr) {
|
|
891
|
-
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
892
|
-
}
|
|
893
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
throw err;
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
try {
|
|
901
|
-
let needsBundle = true;
|
|
902
|
-
if (fs_1.default.existsSync(compiledApiPath)) {
|
|
903
|
-
try {
|
|
904
|
-
const srcStat = fs_1.default.statSync(route.component);
|
|
905
|
-
const outStat = fs_1.default.statSync(compiledApiPath);
|
|
906
|
-
if (outStat.mtimeMs >= srcStat.mtimeMs) {
|
|
907
|
-
needsBundle = false;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
catch (e) { }
|
|
911
|
-
}
|
|
912
|
-
if (needsBundle) {
|
|
913
|
-
await safeBundleApi(route.component, compiledApiPath);
|
|
914
|
-
}
|
|
915
|
-
try {
|
|
916
|
-
delete require.cache[require.resolve(compiledApiPath)];
|
|
917
|
-
}
|
|
918
|
-
catch (e) { }
|
|
919
|
-
ApiModule = require(compiledApiPath);
|
|
920
|
-
}
|
|
921
|
-
catch (err) {
|
|
922
|
-
console.error('API bundling failed for', route.component, err);
|
|
923
|
-
const errMsg = String(err && err.message ? err.message : err);
|
|
924
|
-
const message = /ENOSPC|not enough space|There is not enough space/i.test(errMsg)
|
|
925
|
-
? 'API bundling failed: not enough disk space. Free disk space or delete .satset/temp'
|
|
926
|
-
: 'API bundling failed';
|
|
927
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
928
|
-
res.end(JSON.stringify({ error: { code: 500, message }, file: route.component }));
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
function resolveHandler(mod) {
|
|
932
|
-
if (!mod)
|
|
933
|
-
return null;
|
|
934
|
-
// default export function
|
|
935
|
-
if (typeof mod === 'function')
|
|
936
|
-
return { default: mod };
|
|
937
|
-
if (mod && typeof mod === 'object') {
|
|
938
|
-
if (typeof mod.default === 'function')
|
|
939
|
-
return { default: mod.default };
|
|
940
|
-
// method-named exports (GET, POST, etc.)
|
|
941
|
-
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
|
942
|
-
const out = {};
|
|
943
|
-
for (const m of methods) {
|
|
944
|
-
if (typeof mod[m] === 'function')
|
|
945
|
-
out[m] = mod[m];
|
|
946
|
-
}
|
|
947
|
-
if (Object.keys(out).length)
|
|
948
|
-
return out;
|
|
949
|
-
}
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
const handlerObj = resolveHandler(ApiModule);
|
|
953
|
-
const method = (req.method || 'GET').toUpperCase();
|
|
954
|
-
async function callHandlerWithNodeStyle(fn) {
|
|
955
|
-
// If function declares at least two args, assume Node-style (req, res)
|
|
956
|
-
if (fn.length >= 2) {
|
|
957
|
-
// Inject locale into request object for Node-style handlers
|
|
958
|
-
req.locale = locale;
|
|
959
|
-
req.lang = locale;
|
|
960
|
-
const maybe = await fn(req, res, params);
|
|
961
|
-
// If the handler returns a SatsetResponse, send it
|
|
962
|
-
if (response_1.SatsetResponse.isSatsetResponse(maybe)) {
|
|
963
|
-
(0, response_1.sendSatsetResponse)(res, maybe);
|
|
964
|
-
}
|
|
965
|
-
// assume the handler handled the Node res
|
|
966
|
-
return true;
|
|
967
|
-
}
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
async function callHandlerWithWebStyle(fn) {
|
|
971
|
-
// Build a lightweight Request-like object for route handlers
|
|
972
|
-
const webReq = await (0, response_1.buildSatsetRequest)(req);
|
|
973
|
-
// Inject locale into context
|
|
974
|
-
const context = { params, locale, lang: locale };
|
|
975
|
-
const result = await fn(webReq, context);
|
|
976
|
-
if (response_1.SatsetResponse.isSatsetResponse(result)) {
|
|
977
|
-
(0, response_1.sendSatsetResponse)(res, result);
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
// If result is a native Response-like object with json/text, try to handle
|
|
981
|
-
if (result && typeof result === 'object' && typeof result.status === 'number' && (result.headers || result.json)) {
|
|
982
|
-
// Try basic mapping
|
|
983
|
-
const status = result.status || 200;
|
|
984
|
-
// Normalize headers: support Headers instance, array of tuples, or plain object
|
|
985
|
-
let headers = {};
|
|
986
|
-
const rawHeaders = result.headers;
|
|
987
|
-
if (rawHeaders) {
|
|
988
|
-
if (typeof rawHeaders.get === 'function' && typeof rawHeaders.entries === 'function') {
|
|
989
|
-
headers = Object.fromEntries(rawHeaders.entries());
|
|
990
|
-
}
|
|
991
|
-
else if (Array.isArray(rawHeaders)) {
|
|
992
|
-
headers = Object.fromEntries(rawHeaders);
|
|
993
|
-
}
|
|
994
|
-
else if (typeof rawHeaders === 'object') {
|
|
995
|
-
headers = rawHeaders;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
let body = undefined;
|
|
999
|
-
try {
|
|
1000
|
-
if (typeof result.json === 'function') {
|
|
1001
|
-
body = await result.json();
|
|
1002
|
-
res.writeHead(status, { 'Content-Type': 'application/json', ...headers });
|
|
1003
|
-
res.end(JSON.stringify(body));
|
|
1004
|
-
return true;
|
|
1005
|
-
}
|
|
1006
|
-
if (typeof result.text === 'function') {
|
|
1007
|
-
body = await result.text();
|
|
1008
|
-
res.writeHead(status, headers);
|
|
1009
|
-
res.end(body);
|
|
1010
|
-
return true;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
catch (e) {
|
|
1014
|
-
// fallthrough
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
// If result is an object, return as JSON
|
|
1018
|
-
if (result && typeof result === 'object') {
|
|
1019
|
-
const sat = response_1.SatsetResponse.json(result);
|
|
1020
|
-
(0, response_1.sendSatsetResponse)(res, sat);
|
|
1021
|
-
return true;
|
|
1022
|
-
}
|
|
1023
|
-
// If result is string or number, send as text
|
|
1024
|
-
if (result != null) {
|
|
1025
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1026
|
-
res.end(String(result));
|
|
1027
|
-
return true;
|
|
1028
|
-
}
|
|
1029
|
-
// undefined -> assume handler will manage res; if not, leave to caller
|
|
1030
|
-
return false;
|
|
1031
|
-
}
|
|
1032
|
-
// Prefer method-named export for App-style handlers
|
|
1033
|
-
if (handlerObj && typeof handlerObj[method] === 'function') {
|
|
1034
|
-
const fn = handlerObj[method];
|
|
1035
|
-
// Try node-style first
|
|
1036
|
-
const nodeTook = await callHandlerWithNodeStyle(fn);
|
|
1037
|
-
if (nodeTook)
|
|
1038
|
-
return;
|
|
1039
|
-
// Fallback to Web-style
|
|
1040
|
-
const webTook = await callHandlerWithWebStyle(fn);
|
|
1041
|
-
if (webTook)
|
|
1042
|
-
return;
|
|
1043
|
-
// no response produced
|
|
1044
|
-
res.writeHead(204);
|
|
1045
|
-
res.end();
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
// Default export fallback
|
|
1049
|
-
if (handlerObj && typeof handlerObj.default === 'function') {
|
|
1050
|
-
const fn = handlerObj.default;
|
|
1051
|
-
const nodeTook = await callHandlerWithNodeStyle(fn);
|
|
1052
|
-
if (nodeTook)
|
|
1053
|
-
return;
|
|
1054
|
-
const webTook = await callHandlerWithWebStyle(fn);
|
|
1055
|
-
if (webTook)
|
|
1056
|
-
return;
|
|
1057
|
-
res.writeHead(204);
|
|
1058
|
-
res.end();
|
|
1059
|
-
return;
|
|
1060
|
-
}
|
|
1061
|
-
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
1062
|
-
res.end(JSON.stringify({ error: { code: 405, message: 'Method not allowed' } }));
|
|
1063
|
-
}
|
|
1064
|
-
catch (error) {
|
|
1065
|
-
const errorPayload = {
|
|
1066
|
-
code: 500,
|
|
1067
|
-
message: error && error.message ? error.message : 'Internal Server Error',
|
|
1068
|
-
};
|
|
1069
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1070
|
-
res.end(JSON.stringify({ error: errorPayload, file: route.component, stack: error && error.stack }));
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
async function handlePageRoute(route, req, res, root, tempDir, initialParams = {}, allRoutes = [], publicDirName = 'public', locale = 'en-US') {
|
|
1075
|
-
const dictionaries = getDictionaries(root);
|
|
1076
|
-
return storage_1.requestContext.run({ locale, dictionaries, params: initialParams, pathname: req.url || '/' }, async () => {
|
|
1077
|
-
try {
|
|
1078
|
-
const env = (0, env_1.loadEnv)(root, 'development');
|
|
1079
|
-
const envScript = (0, env_1.getPublicEnvScript)(env.publicVars);
|
|
1080
|
-
if (isClientOnlyPage(route.component)) {
|
|
1081
|
-
const routePaths = allRoutes.length ? allRoutes.map(r => r.path) : [];
|
|
1082
|
-
let metaHtml = '';
|
|
1083
|
-
let htmlLang = locale;
|
|
1084
|
-
let faviconLink = '';
|
|
1085
|
-
try {
|
|
1086
|
-
// dictionaries already loaded in context
|
|
1087
|
-
// Auto-inject hreflang tags for SEO
|
|
1088
|
-
try {
|
|
1089
|
-
const supportedLocales = Object.keys(dictionaries);
|
|
1090
|
-
if (supportedLocales.length > 0) {
|
|
1091
|
-
const host = req.headers.host || 'localhost';
|
|
1092
|
-
// Determine protocol (assume http for dev, but honor x-forwarded-proto if present)
|
|
1093
|
-
const proto = req.headers['x-forwarded-proto'] || 'http';
|
|
1094
|
-
const origin = `${proto}://${host}`;
|
|
1095
|
-
const urlObj = new URL(req.url || '/', origin);
|
|
1096
|
-
const currentPath = urlObj.pathname;
|
|
1097
|
-
// Determine clean path (strip locale prefix if present in URL)
|
|
1098
|
-
let cleanPath = currentPath;
|
|
1099
|
-
if (currentPath === `/${locale}` || currentPath.startsWith(`/${locale}/`)) {
|
|
1100
|
-
cleanPath = currentPath.substring(locale.length + 1);
|
|
1101
|
-
if (!cleanPath.startsWith('/'))
|
|
1102
|
-
cleanPath = '/' + cleanPath;
|
|
1103
|
-
}
|
|
1104
|
-
supportedLocales.forEach(lang => {
|
|
1105
|
-
let href = origin;
|
|
1106
|
-
if (lang !== 'en-US') { // Assuming en-US is default/root
|
|
1107
|
-
href += `/${lang}`;
|
|
1108
|
-
}
|
|
1109
|
-
href += cleanPath === '/' ? '' : cleanPath;
|
|
1110
|
-
metaHtml += `<link rel="alternate" hreflang="${lang}" href="${href}" />\n`;
|
|
1111
|
-
});
|
|
1112
|
-
// Add x-default pointing to default language (en-US)
|
|
1113
|
-
const defaultHref = origin + (cleanPath === '/' ? '' : cleanPath);
|
|
1114
|
-
metaHtml += `<link rel="alternate" hreflang="x-default" href="${defaultHref}" />\n`;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
catch (e) {
|
|
1118
|
-
// ignore hreflang generation errors
|
|
1119
|
-
}
|
|
1120
|
-
try {
|
|
1121
|
-
const { renderMetaTags } = await Promise.resolve().then(() => __importStar(require('../assets/metadata.js')));
|
|
1122
|
-
const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
|
|
1123
|
-
const compiledMetaPath = path_1.default.join(tempDir, baseName + '.meta.server.js');
|
|
1124
|
-
try {
|
|
1125
|
-
await serializedBundle(compiledMetaPath, () => bundler_1.bundler.bundleServer({
|
|
1126
|
-
entryPoint: route.component,
|
|
1127
|
-
outfile: compiledMetaPath,
|
|
1128
|
-
minify: false,
|
|
1129
|
-
root,
|
|
1130
|
-
sourcemap: false,
|
|
1131
|
-
}));
|
|
1132
|
-
try {
|
|
1133
|
-
delete require.cache[require.resolve(compiledMetaPath)];
|
|
1134
|
-
}
|
|
1135
|
-
catch (e) {
|
|
1136
|
-
}
|
|
1137
|
-
const PageModule = require(compiledMetaPath);
|
|
1138
|
-
let metaObj = null;
|
|
1139
|
-
if (PageModule && PageModule.metadata) {
|
|
1140
|
-
metaObj = PageModule.metadata;
|
|
1141
|
-
}
|
|
1142
|
-
else if (PageModule && typeof PageModule.getMetadata === 'function') {
|
|
1143
|
-
const t = (key, params) => {
|
|
1144
|
-
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1145
|
-
let text = dict[key] || key;
|
|
1146
|
-
if (params) {
|
|
1147
|
-
Object.entries(params).forEach(([k, v]) => {
|
|
1148
|
-
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
return text;
|
|
1152
|
-
};
|
|
1153
|
-
try {
|
|
1154
|
-
metaObj = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1155
|
-
}
|
|
1156
|
-
catch (e) {
|
|
1157
|
-
metaObj = null;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
if (metaObj && typeof metaObj.lang === 'string' && metaObj.lang.trim()) {
|
|
1161
|
-
htmlLang = metaObj.lang.trim();
|
|
1162
|
-
}
|
|
1163
|
-
metaHtml = renderMetaTags(metaObj);
|
|
1164
|
-
}
|
|
1165
|
-
catch (e) {
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
catch (e) {
|
|
1169
|
-
}
|
|
1170
|
-
try {
|
|
1171
|
-
let computedFavicon = null;
|
|
1172
|
-
const publicPath = path_1.default.join(root, publicDirName);
|
|
1173
|
-
if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.ico')))
|
|
1174
|
-
computedFavicon = '/favicon.ico';
|
|
1175
|
-
else if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.png')))
|
|
1176
|
-
computedFavicon = '/favicon.png';
|
|
1177
|
-
if (computedFavicon) {
|
|
1178
|
-
faviconLink = `<link rel="icon" href="${computedFavicon}" />`;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
catch (e) {
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
catch (e) {
|
|
1185
|
-
}
|
|
1186
|
-
const initialParamsScript = `<script>
|
|
1187
|
-
window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)};
|
|
1188
|
-
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
1189
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1190
|
-
window.__SATSET_LOCALE__ = "${locale}";
|
|
1191
|
-
</script>`;
|
|
1192
|
-
const html = `
|
|
1193
|
-
<!DOCTYPE html>
|
|
1194
|
-
<html lang="${htmlLang}" suppressHydrationWarning>
|
|
1195
|
-
<head>
|
|
1196
|
-
<meta charset="UTF-8" />
|
|
1197
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1198
|
-
${metaHtml}
|
|
1199
|
-
${faviconLink}
|
|
1200
|
-
<link rel="stylesheet" href="/_satset/globals.css" />
|
|
1201
|
-
</head>
|
|
1202
|
-
<body>
|
|
1203
|
-
<div id="root"></div>
|
|
1204
|
-
<script>${envScript}</script>
|
|
1205
|
-
${initialParamsScript}
|
|
1206
|
-
<script type="module" src="/_satset/_entry.js"></script>
|
|
1207
|
-
<script src="/__hmr"></script>
|
|
1208
|
-
</body>
|
|
1209
|
-
</html>
|
|
1210
|
-
`;
|
|
1211
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1212
|
-
res.end(html);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
// Server-side render the component
|
|
1216
|
-
// Compile page module to a temporary server file so Node can require it (handles tsx/esm)
|
|
1217
|
-
const baseName = path_1.default.basename(route.component).replace(/\.[^.]+$/, '');
|
|
1218
|
-
const compiledServerPath = path_1.default.join(tempDir, baseName + '.server.js');
|
|
1219
|
-
let PageModule = null;
|
|
1220
|
-
// Helper: bundle server build with low-disk fallback
|
|
1221
|
-
async function safeBundleServer(entry, outfile) {
|
|
1222
|
-
return serializedBundle(outfile, async () => {
|
|
1223
|
-
try {
|
|
1224
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
1225
|
-
}
|
|
1226
|
-
catch (err) {
|
|
1227
|
-
const msg = String(err && err.message ? err.message : err);
|
|
1228
|
-
// detect low-disk / ENOSPC errors (esbuild reports "There is not enough space on the disk" in Windows)
|
|
1229
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
1230
|
-
console.warn('Low disk space detected while bundling. Attempting to clear temp and retry without sourcemaps.');
|
|
1231
|
-
try {
|
|
1232
|
-
// clear temp dir to free space
|
|
1233
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
1234
|
-
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
1235
|
-
}
|
|
1236
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
1237
|
-
}
|
|
1238
|
-
catch (cleanupErr) {
|
|
1239
|
-
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
1240
|
-
}
|
|
1241
|
-
try {
|
|
1242
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
|
-
catch (retryErr) {
|
|
1246
|
-
// still failed
|
|
1247
|
-
throw retryErr;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
// Not a disk-space issue, rethrow
|
|
1251
|
-
throw err;
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
}
|
|
1255
|
-
try {
|
|
1256
|
-
// Bundle the page for Node (CJS)
|
|
1257
|
-
await safeBundleServer(route.component, compiledServerPath);
|
|
1258
|
-
try {
|
|
1259
|
-
delete require.cache[require.resolve(compiledServerPath)];
|
|
1260
|
-
}
|
|
1261
|
-
catch (e) {
|
|
1262
|
-
// ignore
|
|
1263
|
-
}
|
|
1264
|
-
PageModule = require(compiledServerPath);
|
|
1265
|
-
}
|
|
1266
|
-
catch (err) {
|
|
1267
|
-
// If bundling fails, show an overlay with the bundling error and do NOT require the original TSX (which would crash)
|
|
1268
|
-
console.error('SSR bundling failed for', route.component, err);
|
|
1269
|
-
let message = 'SSR bundling failed';
|
|
1270
|
-
const errMsg = String(err && err.message ? err.message : err);
|
|
1271
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(errMsg)) {
|
|
1272
|
-
message = 'SSR bundling failed: not enough disk space. Try freeing disk space or deleting the .satset/temp folder.';
|
|
1273
|
-
}
|
|
1274
|
-
const errorInfo = {
|
|
1275
|
-
message,
|
|
1276
|
-
stack: err && err.stack ? err.stack : String(err),
|
|
1277
|
-
file: route.component,
|
|
1278
|
-
};
|
|
1279
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1280
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1281
|
-
res.end(overlayHTML);
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
function resolveExportedComponent(mod) {
|
|
1285
|
-
if (!mod)
|
|
1286
|
-
return null;
|
|
1287
|
-
if (typeof mod === 'function')
|
|
1288
|
-
return mod;
|
|
1289
|
-
if (mod && typeof mod === 'object') {
|
|
1290
|
-
if (typeof mod.default === 'function')
|
|
1291
|
-
return mod.default;
|
|
1292
|
-
if (mod.default && typeof mod.default === 'object' && typeof mod.default.default === 'function')
|
|
1293
|
-
return mod.default.default;
|
|
1294
|
-
for (const key of Object.keys(mod)) {
|
|
1295
|
-
if (typeof mod[key] === 'function')
|
|
1296
|
-
return mod[key];
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
return null;
|
|
1300
|
-
}
|
|
1301
|
-
const PageComponent = resolveExportedComponent(PageModule);
|
|
1302
|
-
console.log('SSR: used component from', compiledServerPath, 'PageModule keys:', Object.keys(PageModule || {}), 'Resolved component type:', typeof PageComponent);
|
|
1303
|
-
console.log('SSR: PageModule keys:', Object.keys(PageModule || {}), 'PageComponent type:', typeof PageComponent);
|
|
1304
|
-
// Simple SSR with optional global layout composition
|
|
1305
|
-
const React = require('react');
|
|
1306
|
-
const { renderToString } = require('react-dom/server');
|
|
1307
|
-
let pageHTML = '<div>Loading...</div>';
|
|
1308
|
-
async function renderComponentToHtml(Comp, props = {}) {
|
|
1309
|
-
if (!Comp || typeof Comp !== 'function') {
|
|
1310
|
-
throw new Error('Invalid component to render');
|
|
1311
|
-
}
|
|
1312
|
-
const isAsync = Comp.constructor && Comp.constructor.name === 'AsyncFunction';
|
|
1313
|
-
if (isAsync) {
|
|
1314
|
-
const maybeNode = Comp(props);
|
|
1315
|
-
const resolved = await Promise.resolve(maybeNode);
|
|
1316
|
-
return renderToString(resolved);
|
|
1317
|
-
}
|
|
1318
|
-
return renderToString(React.createElement(Comp, props));
|
|
1319
|
-
}
|
|
1320
|
-
let statusCode = 200;
|
|
1321
|
-
// Determine locale for SSR
|
|
1322
|
-
// locale is now passed as argument
|
|
1323
|
-
// const dictionaries = getDictionaries(root); // Already defined in outer scope and context
|
|
1324
|
-
try {
|
|
1325
|
-
if (!PageComponent || typeof PageComponent !== 'function') {
|
|
1326
|
-
console.error('SSR Error: resolved page component is not a function. PageModule:', util_1.default.inspect(PageModule, { depth: 2 }));
|
|
1327
|
-
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>`;
|
|
1328
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1329
|
-
res.end(diagHtml);
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
// Recursive Layout Loading
|
|
1333
|
-
const layouts = [];
|
|
1334
|
-
try {
|
|
1335
|
-
const findLayoutFiles = (dir) => {
|
|
1336
|
-
const results = [];
|
|
1337
|
-
let current = dir;
|
|
1338
|
-
const rootSrc = path_1.default.join(root, 'src');
|
|
1339
|
-
// Go up until we reach the root
|
|
1340
|
-
while (current.startsWith(root)) {
|
|
1341
|
-
const extensions = ['.tsx', '.jsx', '.ts', '.js'];
|
|
1342
|
-
for (const ext of extensions) {
|
|
1343
|
-
const layoutPath = path_1.default.join(current, `layout${ext}`);
|
|
1344
|
-
if (fs_1.default.existsSync(layoutPath)) {
|
|
1345
|
-
// Check if already added (to avoid duplicates if loop logic is flawed)
|
|
1346
|
-
if (!results.includes(layoutPath))
|
|
1347
|
-
results.unshift(layoutPath);
|
|
1348
|
-
break;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
if (current === root)
|
|
1352
|
-
break;
|
|
1353
|
-
current = path_1.default.dirname(current);
|
|
1354
|
-
}
|
|
1355
|
-
return results;
|
|
1356
|
-
};
|
|
1357
|
-
const pageDir = path_1.default.dirname(route.component);
|
|
1358
|
-
const layoutFiles = findLayoutFiles(pageDir);
|
|
1359
|
-
for (const layoutFile of layoutFiles) {
|
|
1360
|
-
const layoutBase = path_1.default.basename(layoutFile).replace(/\.[^.]+$/, '');
|
|
1361
|
-
const crypto = require('crypto');
|
|
1362
|
-
const hash = crypto.createHash('md5').update(layoutFile).digest('hex').substring(0, 8);
|
|
1363
|
-
const layoutCompiled = path_1.default.join(tempDir, `${layoutBase}.${hash}.layout.server.js`);
|
|
1364
|
-
await serializedBundle(layoutCompiled, () => bundler_1.bundler.bundleServer({ entryPoint: layoutFile, outfile: layoutCompiled, minify: false, root }));
|
|
1365
|
-
try {
|
|
1366
|
-
delete require.cache[require.resolve(layoutCompiled)];
|
|
1367
|
-
}
|
|
1368
|
-
catch (e) { }
|
|
1369
|
-
const LayoutModule = require(layoutCompiled);
|
|
1370
|
-
let LayoutComp = null;
|
|
1371
|
-
if (typeof LayoutModule === 'function')
|
|
1372
|
-
LayoutComp = LayoutModule;
|
|
1373
|
-
else if (LayoutModule && typeof LayoutModule === 'object') {
|
|
1374
|
-
if (typeof LayoutModule.default === 'function')
|
|
1375
|
-
LayoutComp = LayoutModule.default;
|
|
1376
|
-
else {
|
|
1377
|
-
for (const key of Object.keys(LayoutModule)) {
|
|
1378
|
-
if (typeof LayoutModule[key] === 'function') {
|
|
1379
|
-
LayoutComp = LayoutModule[key];
|
|
1380
|
-
break;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
if (LayoutComp)
|
|
1386
|
-
layouts.push(LayoutComp);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
catch (e) {
|
|
1390
|
-
console.warn('Could not load layout modules:', e);
|
|
1391
|
-
}
|
|
1392
|
-
const isPageAsync = PageComponent &&
|
|
1393
|
-
PageComponent.constructor &&
|
|
1394
|
-
PageComponent.constructor.name === 'AsyncFunction';
|
|
1395
|
-
let pageNode;
|
|
1396
|
-
if (isPageAsync) {
|
|
1397
|
-
const maybeNode = PageComponent({ params: initialParams, searchParams: {} });
|
|
1398
|
-
pageNode = await Promise.resolve(maybeNode);
|
|
1399
|
-
}
|
|
1400
|
-
else {
|
|
1401
|
-
pageNode = React.createElement(PageComponent, { params: initialParams, searchParams: {} });
|
|
1402
|
-
}
|
|
1403
|
-
let appElement = pageNode;
|
|
1404
|
-
// Wrap from inner to outer (all layouts)
|
|
1405
|
-
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1406
|
-
appElement = React.createElement(layouts[i], { params: initialParams, locale }, appElement);
|
|
1407
|
-
}
|
|
1408
|
-
// Wrap with I18nProvider at the top level so RootLayout can use useTranslation
|
|
1409
|
-
appElement = React.createElement(translation_1.I18nProvider, { initialLocale: locale, dictionaries }, appElement);
|
|
1410
|
-
// Apply root layout if exists (already handled in loop)
|
|
1411
|
-
// if (layouts.length > 0) {
|
|
1412
|
-
// appElement = React.createElement(layouts[0], { params: initialParams, locale }, appElement);
|
|
1413
|
-
// }
|
|
1414
|
-
const Wrapper = () => appElement;
|
|
1415
|
-
pageHTML = await renderComponentToHtml(Wrapper);
|
|
1416
|
-
}
|
|
1417
|
-
catch (err) {
|
|
1418
|
-
console.error('SSR Error:', err);
|
|
1419
|
-
if (err && err.__SATSET_REDIRECT) {
|
|
1420
|
-
const info = err.__SATSET_REDIRECT || {};
|
|
1421
|
-
const url = typeof info.url === 'string' && info.url.length ? info.url : '/';
|
|
1422
|
-
const status = typeof info.status === 'number' ? info.status : 307;
|
|
1423
|
-
const sat = response_1.SatsetResponse.redirect(url, status);
|
|
1424
|
-
(0, response_1.sendSatsetResponse)(res, sat);
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
if (err && err.__SATSET_NOT_FOUND) {
|
|
1428
|
-
statusCode = 404;
|
|
1429
|
-
const errorPayload = { code: 404, message: 'Page not found' };
|
|
1430
|
-
try {
|
|
1431
|
-
const nf = allRoutes.find(r => r.path === '/404') ||
|
|
1432
|
-
allRoutes.find(r => r.path === '/not-found');
|
|
1433
|
-
if (nf && nf.component) {
|
|
1434
|
-
const nfBase = path_1.default.basename(nf.component).replace(/\.[^.]+$/, '');
|
|
1435
|
-
const nfCompiled = path_1.default.join(tempDir, nfBase + '.notfound.server.js');
|
|
1436
|
-
await bundler_1.bundler.bundleServer({ entryPoint: nf.component, outfile: nfCompiled, minify: false, root });
|
|
1437
|
-
try {
|
|
1438
|
-
delete require.cache[require.resolve(nfCompiled)];
|
|
1439
|
-
}
|
|
1440
|
-
catch (e) { }
|
|
1441
|
-
const NfModule = require(nfCompiled);
|
|
1442
|
-
const NfComp = (typeof NfModule === 'function') ? NfModule : (NfModule && typeof NfModule.default === 'function') ? NfModule.default : null;
|
|
1443
|
-
if (NfComp) {
|
|
1444
|
-
pageHTML = await renderComponentToHtml(NfComp, { params: initialParams, error: errorPayload });
|
|
1445
|
-
}
|
|
1446
|
-
else {
|
|
1447
|
-
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
else {
|
|
1451
|
-
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
catch (e) {
|
|
1455
|
-
pageHTML = '<h1>404 - Page Not Found</h1>';
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
else {
|
|
1459
|
-
const maybeStatus = (err && err.statusCode) ??
|
|
1460
|
-
(err && err.status) ??
|
|
1461
|
-
(err && typeof err.code === 'number' ? err.code : undefined);
|
|
1462
|
-
if (typeof maybeStatus === 'number' && maybeStatus >= 400 && maybeStatus <= 599) {
|
|
1463
|
-
statusCode = maybeStatus;
|
|
1464
|
-
}
|
|
1465
|
-
else {
|
|
1466
|
-
statusCode = 500;
|
|
1467
|
-
}
|
|
1468
|
-
const errorPayload = {
|
|
1469
|
-
code: statusCode,
|
|
1470
|
-
message: (err && err.message) ? err.message : (statusCode === 404 ? 'Page not found' : 'Server error'),
|
|
1471
|
-
};
|
|
1472
|
-
const candidatePaths = [];
|
|
1473
|
-
candidatePaths.push(`/${statusCode}`);
|
|
1474
|
-
if (statusCode === 404)
|
|
1475
|
-
candidatePaths.push('/404', '/not-found');
|
|
1476
|
-
if (statusCode === 500)
|
|
1477
|
-
candidatePaths.push('/500');
|
|
1478
|
-
candidatePaths.push('/error');
|
|
1479
|
-
let errorRoute = null;
|
|
1480
|
-
for (const p of candidatePaths) {
|
|
1481
|
-
const found = allRoutes.find(r => r.path === p);
|
|
1482
|
-
if (found) {
|
|
1483
|
-
errorRoute = found;
|
|
1484
|
-
break;
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
try {
|
|
1488
|
-
if (errorRoute && errorRoute.component) {
|
|
1489
|
-
const erBase = path_1.default.basename(errorRoute.component).replace(/\.[^.]+$/, '');
|
|
1490
|
-
const erCompiled = path_1.default.join(tempDir, erBase + '.error.server.js');
|
|
1491
|
-
await bundler_1.bundler.bundleServer({ entryPoint: errorRoute.component, outfile: erCompiled, minify: false, root });
|
|
1492
|
-
try {
|
|
1493
|
-
delete require.cache[require.resolve(erCompiled)];
|
|
1494
|
-
}
|
|
1495
|
-
catch (e) { }
|
|
1496
|
-
const ErModule = require(erCompiled);
|
|
1497
|
-
const ErComp = (typeof ErModule === 'function') ? ErModule : (ErModule && typeof ErModule.default === 'function') ? ErModule.default : null;
|
|
1498
|
-
if (ErComp) {
|
|
1499
|
-
pageHTML = await renderComponentToHtml(ErComp, { error: errorPayload, reset: () => { } });
|
|
1500
|
-
}
|
|
1501
|
-
else {
|
|
1502
|
-
throw err;
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
else {
|
|
1506
|
-
throw err;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
catch (e) {
|
|
1510
|
-
// Attempt to extract title from metadata for the error overlay
|
|
1511
|
-
let pageTitle;
|
|
1512
|
-
try {
|
|
1513
|
-
if (PageModule) {
|
|
1514
|
-
if (PageModule.metadata && PageModule.metadata.title) {
|
|
1515
|
-
pageTitle = PageModule.metadata.title;
|
|
1516
|
-
}
|
|
1517
|
-
else if (typeof PageModule.getMetadata === 'function') {
|
|
1518
|
-
const t = (key, params) => {
|
|
1519
|
-
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1520
|
-
let text = dict[key] || key;
|
|
1521
|
-
if (params) {
|
|
1522
|
-
Object.entries(params).forEach(([k, v]) => {
|
|
1523
|
-
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
return text;
|
|
1527
|
-
};
|
|
1528
|
-
const meta = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1529
|
-
if (meta && meta.title)
|
|
1530
|
-
pageTitle = meta.title;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
catch (metaErr) {
|
|
1535
|
-
// ignore metadata extraction errors during error handling
|
|
1536
|
-
}
|
|
1537
|
-
const errorInfo = {
|
|
1538
|
-
message: err?.message || String(err),
|
|
1539
|
-
stack: err?.stack,
|
|
1540
|
-
file: route.component,
|
|
1541
|
-
title: pageTitle,
|
|
1542
|
-
};
|
|
1543
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1544
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1545
|
-
res.end(overlayHTML);
|
|
1546
|
-
return;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
// expose routes + initial params to client for Router
|
|
1551
|
-
const routePaths = allRoutes.length ? allRoutes.map(r => r.path) : [];
|
|
1552
|
-
// Check whether layout and CSS ended up in the rendered HTML for debugging
|
|
1553
|
-
try {
|
|
1554
|
-
const hasHeader = pageHTML.includes('<header');
|
|
1555
|
-
const hasFooter = pageHTML.includes('<footer');
|
|
1556
|
-
console.log(`SSR: rendered page includes header=${hasHeader} footer=${hasFooter}`);
|
|
1557
|
-
const cssPath = path_1.default.join(tempDir, 'globals.css');
|
|
1558
|
-
console.log('SSR: globals.css exists at', cssPath, fs_1.default.existsSync(cssPath));
|
|
1559
|
-
}
|
|
1560
|
-
catch (e) {
|
|
1561
|
-
// ignore
|
|
1562
|
-
}
|
|
1563
|
-
// attempt to obtain metadata from the page module (static or via getMetadata)
|
|
1564
|
-
let metaHtml = '';
|
|
1565
|
-
let htmlLang = 'en';
|
|
1566
|
-
try {
|
|
1567
|
-
const { renderMetaTags } = await Promise.resolve().then(() => __importStar(require('../assets/metadata.js')));
|
|
1568
|
-
// metadata may be exported as `metadata` or `getMetadata` function
|
|
1569
|
-
let metaObj = null;
|
|
1570
|
-
if (PageModule && PageModule.metadata) {
|
|
1571
|
-
metaObj = PageModule.metadata;
|
|
1572
|
-
}
|
|
1573
|
-
else if (PageModule && typeof PageModule.getMetadata === 'function') {
|
|
1574
|
-
try {
|
|
1575
|
-
const t = (key, params) => {
|
|
1576
|
-
const dict = dictionaries[locale] || dictionaries['en-US'] || dictionaries[Object.keys(dictionaries)[0]] || {};
|
|
1577
|
-
let text = dict[key] || key;
|
|
1578
|
-
if (params) {
|
|
1579
|
-
Object.entries(params).forEach(([k, v]) => {
|
|
1580
|
-
text = text.replace(new RegExp(`{${k}}`, 'g'), v);
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
return text;
|
|
1584
|
-
};
|
|
1585
|
-
metaObj = await PageModule.getMetadata({ params: initialParams, locale, t });
|
|
1586
|
-
}
|
|
1587
|
-
catch (e) {
|
|
1588
|
-
metaObj = null;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
// The `<Head>` component was removed; rely on exported `metadata` or `getMetadata` from the page/layout module.
|
|
1592
|
-
// Detect html lang from metadata if provided
|
|
1593
|
-
if (metaObj && typeof metaObj.lang === 'string' && metaObj.lang.trim()) {
|
|
1594
|
-
htmlLang = metaObj.lang.trim();
|
|
1595
|
-
}
|
|
1596
|
-
metaHtml = renderMetaTags(metaObj);
|
|
1597
|
-
}
|
|
1598
|
-
catch (e) {
|
|
1599
|
-
// ignore
|
|
1600
|
-
}
|
|
1601
|
-
const initialParamsScript = `<script>
|
|
1602
|
-
window.__SATSET_ROUTES__ = ${JSON.stringify(routePaths)};
|
|
1603
|
-
window.__SATSET_PARAMS__ = ${JSON.stringify(initialParams)};
|
|
1604
|
-
window.__SATSET_DICTIONARIES__ = ${JSON.stringify(getDictionaries(root))};
|
|
1605
|
-
window.__SATSET_LOCALE__ = "${locale}";
|
|
1606
|
-
</script>`;
|
|
1607
|
-
// Determine favicon at request time (in case it was created/changed)
|
|
1608
|
-
let computedFavicon = null;
|
|
1609
|
-
try {
|
|
1610
|
-
const publicPath = path_1.default.join(root, publicDirName);
|
|
1611
|
-
if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.ico')))
|
|
1612
|
-
computedFavicon = '/favicon.ico';
|
|
1613
|
-
else if (fs_1.default.existsSync(path_1.default.join(publicPath, 'favicon.png')))
|
|
1614
|
-
computedFavicon = '/favicon.png';
|
|
1615
|
-
}
|
|
1616
|
-
catch (e) {
|
|
1617
|
-
// ignore
|
|
1618
|
-
}
|
|
1619
|
-
const faviconLink = computedFavicon ? `<link rel="icon" href="${computedFavicon}" />` : '';
|
|
1620
|
-
let html = '';
|
|
1621
|
-
const trimmedPageHTML = pageHTML.trim();
|
|
1622
|
-
// Check if the page content is a full HTML document (starts with <html> or <!DOCTYPE html)
|
|
1623
|
-
// We allow for comments at the start (e.g. <!-- -->)
|
|
1624
|
-
const isFullDocument = /^\s*(?:<!--[\s\S]*?-->\s*)*<html/i.test(trimmedPageHTML) || /^\s*<!doctype/i.test(trimmedPageHTML);
|
|
1625
|
-
try {
|
|
1626
|
-
fs_1.default.appendFileSync(path_1.default.join(root, 'ssr-debug.log'), `[${new Date().toISOString()}] URL: ${req.url}\n`);
|
|
1627
|
-
fs_1.default.appendFileSync(path_1.default.join(root, 'ssr-debug.log'), `isFullDocument: ${isFullDocument}\n`);
|
|
1628
|
-
fs_1.default.appendFileSync(path_1.default.join(root, 'ssr-debug.log'), `First 100 chars: ${trimmedPageHTML.substring(0, 100)}\n`);
|
|
1629
|
-
fs_1.default.appendFileSync(path_1.default.join(root, 'ssr-debug.log'), `Total length: ${trimmedPageHTML.length}\n`);
|
|
1630
|
-
fs_1.default.appendFileSync(path_1.default.join(root, 'ssr-debug.log'), '-'.repeat(20) + '\n');
|
|
1631
|
-
}
|
|
1632
|
-
catch (e) { }
|
|
1633
|
-
if (isFullDocument) {
|
|
1634
|
-
console.log('SSR: Detected full HTML document (Root Layout present)');
|
|
1635
|
-
html = trimmedPageHTML;
|
|
1636
|
-
if (!/^\s*<!doctype/i.test(html)) {
|
|
1637
|
-
html = '<!DOCTYPE html>\n' + html;
|
|
1638
|
-
}
|
|
1639
|
-
const headContent = `
|
|
1640
|
-
<meta charset="UTF-8" />
|
|
1641
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1642
|
-
${metaHtml}
|
|
1643
|
-
${faviconLink}
|
|
1644
|
-
<link rel="stylesheet" href="/_satset/globals.css" />`;
|
|
1645
|
-
if (html.includes('</head>')) {
|
|
1646
|
-
html = html.replace('</head>', `${headContent}</head>`);
|
|
1647
|
-
}
|
|
1648
|
-
const bodyScripts = `
|
|
1649
|
-
<script>${envScript}</script>
|
|
1650
|
-
${initialParamsScript}
|
|
1651
|
-
<script type="module" src="/_satset/_entry.js"></script>
|
|
1652
|
-
<script src="/__hmr"></script>`;
|
|
1653
|
-
if (html.includes('</body>')) {
|
|
1654
|
-
html = html.replace('</body>', `${bodyScripts}</body>`);
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
else {
|
|
1658
|
-
html = `
|
|
1659
|
-
<!DOCTYPE html>
|
|
1660
|
-
<html lang="${htmlLang}" suppressHydrationWarning>
|
|
1661
|
-
<head>
|
|
1662
|
-
<meta charset="UTF-8" />
|
|
1663
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1664
|
-
${metaHtml}
|
|
1665
|
-
${faviconLink}
|
|
1666
|
-
<link rel="stylesheet" href="/_satset/globals.css" />
|
|
1667
|
-
</head>
|
|
1668
|
-
<body>
|
|
1669
|
-
<div id="root">${pageHTML}</div>
|
|
1670
|
-
<script>${envScript}</script>
|
|
1671
|
-
${initialParamsScript}
|
|
1672
|
-
<script type="module" src="/_satset/_entry.js"></script>
|
|
1673
|
-
<script src="/__hmr"></script>
|
|
1674
|
-
</body>
|
|
1675
|
-
</html>
|
|
1676
|
-
`;
|
|
1677
|
-
}
|
|
1678
|
-
res.writeHead(statusCode, { 'Content-Type': 'text/html' });
|
|
1679
|
-
res.end(html);
|
|
1680
|
-
}
|
|
1681
|
-
catch (error) {
|
|
1682
|
-
const errorInfo = {
|
|
1683
|
-
message: error.message,
|
|
1684
|
-
stack: error.stack,
|
|
1685
|
-
file: route.component,
|
|
1686
|
-
};
|
|
1687
|
-
if (route.component && fs_1.default.existsSync(route.component)) {
|
|
1688
|
-
const stackLines = error.stack?.split('\n') || [];
|
|
1689
|
-
const lineMatch = stackLines[0]?.match(/:(\d+):(\d+)/);
|
|
1690
|
-
if (lineMatch) {
|
|
1691
|
-
const line = parseInt(lineMatch[1]);
|
|
1692
|
-
errorInfo.line = line;
|
|
1693
|
-
errorInfo.column = parseInt(lineMatch[2]);
|
|
1694
|
-
errorInfo.code = (0, error_overlay_1.extractCodeSnippet)(route.component, line);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1698
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1699
|
-
res.end(overlayHTML);
|
|
1700
|
-
}
|
|
1701
|
-
finally {
|
|
1702
|
-
(0, response_1.setCurrentRequestCookies)(null);
|
|
1703
|
-
}
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
async function runMiddleware(root, tempDir, req, res, effectivePath) {
|
|
1707
|
-
try {
|
|
1708
|
-
const candidates = ['middleware.ts', 'middleware.tsx', 'middleware.js', 'middleware.jsx'];
|
|
1709
|
-
let srcPath = null;
|
|
1710
|
-
for (const c of candidates) {
|
|
1711
|
-
const p = path_1.default.join(root, c);
|
|
1712
|
-
if (fs_1.default.existsSync(p)) {
|
|
1713
|
-
srcPath = p;
|
|
1714
|
-
break;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
if (!srcPath) {
|
|
1718
|
-
return false;
|
|
1719
|
-
}
|
|
1720
|
-
const baseName = path_1.default.basename(srcPath).replace(/\.[^.]+$/, '');
|
|
1721
|
-
const compiledPath = path_1.default.join(tempDir, baseName + '.middleware.server.js');
|
|
1722
|
-
async function safeBundle(entry, outfile) {
|
|
1723
|
-
try {
|
|
1724
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: true });
|
|
1725
|
-
}
|
|
1726
|
-
catch (err) {
|
|
1727
|
-
const msg = String(err && err.message ? err.message : err);
|
|
1728
|
-
if (/ENOSPC|not enough space|There is not enough space/i.test(msg)) {
|
|
1729
|
-
try {
|
|
1730
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
1731
|
-
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
1732
|
-
}
|
|
1733
|
-
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
1734
|
-
}
|
|
1735
|
-
catch (cleanupErr) {
|
|
1736
|
-
console.error('Failed to cleanup temp dir:', cleanupErr);
|
|
1737
|
-
}
|
|
1738
|
-
await bundler_1.bundler.bundleServer({ entryPoint: entry, outfile, minify: false, root, sourcemap: false });
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
throw err;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
try {
|
|
1745
|
-
await safeBundle(srcPath, compiledPath);
|
|
1746
|
-
try {
|
|
1747
|
-
delete require.cache[require.resolve(compiledPath)];
|
|
1748
|
-
}
|
|
1749
|
-
catch (e) { }
|
|
1750
|
-
const mod = require(compiledPath);
|
|
1751
|
-
const fn = mod && (typeof mod.middleware === 'function' ? mod.middleware : (typeof mod.default === 'function' ? mod.default : null));
|
|
1752
|
-
if (!fn) {
|
|
1753
|
-
return false;
|
|
1754
|
-
}
|
|
1755
|
-
const webReq = await (0, response_1.buildSatsetRequest)(req);
|
|
1756
|
-
webReq.effectivePath = effectivePath;
|
|
1757
|
-
const result = await fn(webReq);
|
|
1758
|
-
if (response_1.SatsetResponse.isSatsetResponse(result)) {
|
|
1759
|
-
const headers = result.headers || {};
|
|
1760
|
-
const isNext = result.status === 204 &&
|
|
1761
|
-
!headers.Location &&
|
|
1762
|
-
!headers['X-Satset-Rewrite'] &&
|
|
1763
|
-
(result.body == null);
|
|
1764
|
-
if (isNext) {
|
|
1765
|
-
return false;
|
|
1766
|
-
}
|
|
1767
|
-
(0, response_1.sendSatsetResponse)(res, result);
|
|
1768
|
-
return true;
|
|
1769
|
-
}
|
|
1770
|
-
return false;
|
|
1771
|
-
}
|
|
1772
|
-
catch (err) {
|
|
1773
|
-
const errMsg = String(err && err.message ? err.message : err);
|
|
1774
|
-
const errorInfo = {
|
|
1775
|
-
message: errMsg,
|
|
1776
|
-
stack: err.stack,
|
|
1777
|
-
file: srcPath,
|
|
1778
|
-
};
|
|
1779
|
-
const overlayHTML = (0, error_overlay_1.generateErrorOverlayHTML)(errorInfo);
|
|
1780
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1781
|
-
res.end(overlayHTML);
|
|
1782
|
-
return true;
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
catch (e) {
|
|
1786
|
-
return false;
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
function stripLocaleFromPath(pathname) {
|
|
1790
|
-
if (!pathname)
|
|
1791
|
-
return '/';
|
|
1792
|
-
const raw = pathname.split('?')[0].split('#')[0];
|
|
1793
|
-
const segments = raw.split('/').filter(Boolean);
|
|
1794
|
-
if (segments.length === 0)
|
|
1795
|
-
return '/';
|
|
1796
|
-
const first = segments[0];
|
|
1797
|
-
const localePattern = /^[a-zA-Z]{2}(?:-[a-zA-Z]{2})?$/;
|
|
1798
|
-
if (localePattern.test(first)) {
|
|
1799
|
-
const rest = segments.slice(1);
|
|
1800
|
-
return rest.length ? `/${rest.join('/')}` : '/';
|
|
1801
|
-
}
|
|
1802
|
-
return raw.startsWith('/') ? raw : `/${raw}`;
|
|
1803
|
-
}
|
|
1804
|
-
function getContentType(ext) {
|
|
1805
|
-
const types = {
|
|
1806
|
-
'.html': 'text/html',
|
|
1807
|
-
'.css': 'text/css',
|
|
1808
|
-
'.js': 'application/javascript',
|
|
1809
|
-
'.json': 'application/json',
|
|
1810
|
-
'.png': 'image/png',
|
|
1811
|
-
'.jpg': 'image/jpeg',
|
|
1812
|
-
'.svg': 'image/svg+xml',
|
|
1813
|
-
'.woff': 'font/woff',
|
|
1814
|
-
'.woff2': 'font/woff2',
|
|
1815
|
-
};
|
|
1816
|
-
return types[ext] || 'text/plain';
|
|
1817
|
-
}
|
|
1818
|
-
//# sourceMappingURL=dev.js.map
|