@bouygues-telecom/staticjs 0.1.11 → 1.0.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.
- package/_build/helpers/cachePages.d.ts +15 -0
- package/_build/helpers/cachePages.js +162 -0
- package/_build/helpers/createPage.d.ts +18 -0
- package/_build/helpers/createPage.js +41 -0
- package/_build/helpers/layoutDiscovery.d.ts +9 -0
- package/_build/helpers/layoutDiscovery.js +39 -0
- package/_build/helpers/readPages.d.ts +3 -0
- package/_build/helpers/readPages.js +24 -0
- package/_build/helpers/renderPageRuntime.d.ts +13 -0
- package/_build/helpers/renderPageRuntime.js +222 -0
- package/_build/scripts/build-html.d.ts +1 -0
- package/_build/scripts/build-html.js +138 -0
- package/_build/scripts/cli.d.ts +6 -0
- package/_build/scripts/cli.js +119 -0
- package/_build/scripts/create-static-app.d.ts +2 -0
- package/{dist/scripts/cli.js → _build/scripts/create-static-app.js} +2 -2
- package/_build/scripts/generate-test-multiapps.d.ts +6 -0
- package/_build/scripts/generate-test-multiapps.js +79 -0
- package/_build/server/config/index.d.ts +20 -0
- package/_build/server/config/index.js +25 -0
- package/_build/server/config/vite.config.d.ts +2 -0
- package/_build/server/config/vite.config.js +31 -0
- package/_build/server/config/vite.plugin.d.ts +11 -0
- package/_build/server/config/vite.plugin.js +75 -0
- package/_build/server/index.d.ts +22 -0
- package/_build/server/index.js +124 -0
- package/_build/server/middleware/errorHandling.d.ts +22 -0
- package/_build/server/middleware/errorHandling.js +48 -0
- package/_build/server/middleware/hotReload.d.ts +31 -0
- package/_build/server/middleware/hotReload.js +152 -0
- package/_build/server/middleware/logging.d.ts +13 -0
- package/_build/server/middleware/logging.js +23 -0
- package/_build/server/middleware/parsing.d.ts +9 -0
- package/_build/server/middleware/parsing.js +21 -0
- package/_build/server/middleware/performance.d.ts +13 -0
- package/_build/server/middleware/performance.js +26 -0
- package/_build/server/middleware/rateLimiting.d.ts +17 -0
- package/_build/server/middleware/rateLimiting.js +38 -0
- package/_build/server/middleware/runtime.d.ts +23 -0
- package/_build/server/middleware/runtime.js +186 -0
- package/_build/server/middleware/security.d.ts +24 -0
- package/_build/server/middleware/security.js +39 -0
- package/_build/server/middleware/static.d.ts +9 -0
- package/_build/server/middleware/static.js +63 -0
- package/_build/server/routes/api.d.ts +23 -0
- package/_build/server/routes/api.js +124 -0
- package/_build/server/scripts/revalidate.d.ts +2 -0
- package/{dist → _build/server}/scripts/revalidate.js +3 -3
- package/_build/server/utils/fileWatcher.d.ts +27 -0
- package/_build/server/utils/fileWatcher.js +194 -0
- package/_build/server/utils/startup.d.ts +18 -0
- package/_build/server/utils/startup.js +93 -0
- package/_build/server/utils/vite.d.ts +18 -0
- package/_build/server/utils/vite.js +61 -0
- package/_build/server/utils/websocket.d.ts +26 -0
- package/_build/server/utils/websocket.js +140 -0
- package/package.json +28 -16
- package/LICENSE +0 -190
- package/README.md +0 -158
- package/dist/config/vite.plugin.js +0 -80
- package/dist/helpers/cachePages.js +0 -26
- package/dist/helpers/createPage.js +0 -23
- package/dist/helpers/readPages.js +0 -19
- package/dist/scripts/build-html.js +0 -82
- package/dist/scripts/create-static-app.js +0 -36
- package/dist/scripts/generate-test-multiapps.js +0 -51
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime rendering middleware configuration
|
|
3
|
+
* Handles development mode runtime page rendering and JavaScript serving
|
|
4
|
+
*/
|
|
5
|
+
import { renderPageRuntime } from "../../helpers/renderPageRuntime.js";
|
|
6
|
+
import { isDevelopment } from "../index.js";
|
|
7
|
+
import { CONFIG } from "../config/index.js";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
// Cache for rendered pages and module cache invalidation
|
|
10
|
+
let pageCache = new Map();
|
|
11
|
+
let lastCacheInvalidation = Date.now();
|
|
12
|
+
/**
|
|
13
|
+
* Clear all caches when files change
|
|
14
|
+
*/
|
|
15
|
+
export const invalidateRuntimeCache = async () => {
|
|
16
|
+
try {
|
|
17
|
+
// Cache invalidation (silent)
|
|
18
|
+
pageCache.clear();
|
|
19
|
+
lastCacheInvalidation = Date.now();
|
|
20
|
+
// Import Vite server using ES modules
|
|
21
|
+
const { getViteServer } = await import('../utils/vite.js');
|
|
22
|
+
const viteServer = getViteServer();
|
|
23
|
+
if (viteServer) {
|
|
24
|
+
// Check if Vite has module graph and invalidate caches
|
|
25
|
+
if (viteServer.moduleGraph) {
|
|
26
|
+
viteServer.moduleGraph.invalidateAll();
|
|
27
|
+
// Clear Vite's transform cache if it exists
|
|
28
|
+
if (viteServer.transformCache && typeof viteServer.transformCache.clear === 'function') {
|
|
29
|
+
viteServer.transformCache.clear();
|
|
30
|
+
}
|
|
31
|
+
// Clear Vite's internal caches if they exist
|
|
32
|
+
if (viteServer.ssrTransform && typeof viteServer.ssrTransform.clear === 'function') {
|
|
33
|
+
viteServer.ssrTransform.clear();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Force garbage collection to clear any remaining cached modules
|
|
38
|
+
if (global.gc) {
|
|
39
|
+
global.gc();
|
|
40
|
+
}
|
|
41
|
+
// Cache invalidation completed
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('[Runtime] Error during cache invalidation:', error);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Runtime page rendering middleware for development mode
|
|
49
|
+
* Uses renderPageRuntime which handles all page resolution and dynamic routing
|
|
50
|
+
*/
|
|
51
|
+
export const runtimeRenderingMiddleware = async (req, res, next) => {
|
|
52
|
+
// Only handle GET requests for pages, but skip static assets and JS files
|
|
53
|
+
if (req.method === 'GET' && !req.path.match(/\.(js|css|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
|
|
54
|
+
/**
|
|
55
|
+
* Handle runtime rendering for development mode
|
|
56
|
+
* Leverages renderPageRuntime's built-in page resolution and parameter handling
|
|
57
|
+
*/
|
|
58
|
+
try {
|
|
59
|
+
const cacheKey = req.path;
|
|
60
|
+
let htmlContent = null;
|
|
61
|
+
// Check if we have cached content and it's still valid
|
|
62
|
+
if (pageCache.has(cacheKey)) {
|
|
63
|
+
const cached = pageCache.get(cacheKey);
|
|
64
|
+
if (cached.timestamp > lastCacheInvalidation) {
|
|
65
|
+
htmlContent = cached.content;
|
|
66
|
+
// Serving cached content
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// If no valid cached content, render fresh
|
|
70
|
+
if (!htmlContent) {
|
|
71
|
+
// Rendering fresh content
|
|
72
|
+
htmlContent = await renderPageRuntime(req.path);
|
|
73
|
+
if (htmlContent) {
|
|
74
|
+
// Cache the rendered content
|
|
75
|
+
pageCache.set(cacheKey, {
|
|
76
|
+
content: htmlContent,
|
|
77
|
+
timestamp: Date.now()
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (htmlContent) {
|
|
82
|
+
res.setHeader('Content-Type', 'text/html');
|
|
83
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
84
|
+
res.setHeader('Pragma', 'no-cache');
|
|
85
|
+
res.setHeader('Expires', '0');
|
|
86
|
+
res.send(htmlContent);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Continue to next middleware (static files or 404)
|
|
91
|
+
return next();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('[Runtime] Error rendering page:', error);
|
|
96
|
+
// Continue to next middleware on error
|
|
97
|
+
return next();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
next();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Register JavaScript serving middleware for each page
|
|
106
|
+
*/
|
|
107
|
+
export const registerJavaScriptMiddleware = (app, viteServer) => {
|
|
108
|
+
// Register specific routes for each page's JavaScript file
|
|
109
|
+
const pagesCache = JSON.parse(fs.readFileSync(`./${CONFIG.BUILD_DIR}/cache/pagesCache.json`, 'utf8'));
|
|
110
|
+
const excludedFiles = JSON.parse(fs.readFileSync(`./${CONFIG.BUILD_DIR}/cache/excludedFiles.json`, 'utf8'));
|
|
111
|
+
// Register a route for each page's JS file (skip dynamic routes)
|
|
112
|
+
Object.keys(pagesCache).forEach(pageName => {
|
|
113
|
+
if (!excludedFiles.includes(pageName) && !pageName.includes('[') && !pageName.includes(']')) {
|
|
114
|
+
const jsRoute = `/${pageName}.js`;
|
|
115
|
+
// Registering JS route for page
|
|
116
|
+
app.get(jsRoute, async (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
// Check if the page file has "no scripts" directive
|
|
119
|
+
const pageContent = fs.readFileSync(pagesCache[pageName], 'utf8');
|
|
120
|
+
const firstLine = pageContent.split('\n')[0];
|
|
121
|
+
if (firstLine.includes('no scripts')) {
|
|
122
|
+
return res.status(404).json({
|
|
123
|
+
success: false,
|
|
124
|
+
error: 'Not Found',
|
|
125
|
+
message: `JavaScript disabled for ${pageName}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Check file modification time and invalidate cache if needed
|
|
129
|
+
const stats = fs.statSync(pagesCache[pageName]);
|
|
130
|
+
const fileModTime = stats.mtime.getTime();
|
|
131
|
+
if (fileModTime > lastCacheInvalidation && viteServer.moduleGraph) {
|
|
132
|
+
const moduleId = pagesCache[pageName];
|
|
133
|
+
const module = viteServer.moduleGraph.getModuleById(moduleId);
|
|
134
|
+
if (module) {
|
|
135
|
+
viteServer.moduleGraph.invalidateModule(module);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Transform the page file using Vite
|
|
139
|
+
const transformStartTime = Date.now();
|
|
140
|
+
const result = await viteServer.transformRequest(`${pagesCache[pageName]}?t=${transformStartTime}`, {
|
|
141
|
+
ssr: false
|
|
142
|
+
});
|
|
143
|
+
if (result && result.code) {
|
|
144
|
+
// Create a hash of the code for cache busting
|
|
145
|
+
const crypto = await import('crypto');
|
|
146
|
+
const codeHash = crypto.createHash('md5').update(result.code).digest('hex').slice(0, 8);
|
|
147
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
148
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
149
|
+
res.setHeader('Pragma', 'no-cache');
|
|
150
|
+
res.setHeader('Expires', '0');
|
|
151
|
+
res.setHeader('X-Timestamp', Date.now().toString());
|
|
152
|
+
res.setHeader('X-Code-Hash', codeHash);
|
|
153
|
+
res.setHeader('X-File-Modified', fileModTime.toString());
|
|
154
|
+
return res.send(result.code);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
return res.status(404).json({
|
|
158
|
+
success: false,
|
|
159
|
+
error: 'Not Found',
|
|
160
|
+
message: `No JavaScript generated for ${pageName}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error(`Error compiling JavaScript for ${pageName}:`, error);
|
|
166
|
+
return res.status(500).json({
|
|
167
|
+
success: false,
|
|
168
|
+
error: 'Internal Server Error',
|
|
169
|
+
message: `Failed to compile JavaScript for ${pageName}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else if (pageName.includes('[') || pageName.includes(']')) {
|
|
175
|
+
// Dynamic routes are handled differently - could add logging here if needed
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Apply runtime middleware to Express app (development mode only)
|
|
181
|
+
*/
|
|
182
|
+
export const applyRuntime = (app) => {
|
|
183
|
+
if (isDevelopment) {
|
|
184
|
+
app.use(runtimeRenderingMiddleware);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security middleware configuration
|
|
3
|
+
* Handles helmet, CORS, and other security-related middleware
|
|
4
|
+
*/
|
|
5
|
+
import cors from "cors";
|
|
6
|
+
import { Express } from "express";
|
|
7
|
+
/**
|
|
8
|
+
* Security headers middleware using helmet
|
|
9
|
+
* Configures appropriate security headers for the application
|
|
10
|
+
*/
|
|
11
|
+
export declare const securityMiddleware: (req: import("http").IncomingMessage, res: import("http").ServerResponse, next: (err?: unknown) => void) => void;
|
|
12
|
+
/**
|
|
13
|
+
* CORS configuration for development
|
|
14
|
+
* Allows cross-origin requests in development mode
|
|
15
|
+
*/
|
|
16
|
+
export declare const corsMiddleware: (req: cors.CorsRequest, res: {
|
|
17
|
+
statusCode?: number | undefined;
|
|
18
|
+
setHeader(key: string, value: string): any;
|
|
19
|
+
end(): any;
|
|
20
|
+
}, next: (err?: any) => any) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Apply security middleware to Express app
|
|
23
|
+
*/
|
|
24
|
+
export declare const applySecurity: (app: Express) => void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security middleware configuration
|
|
3
|
+
* Handles helmet, CORS, and other security-related middleware
|
|
4
|
+
*/
|
|
5
|
+
import helmet from "helmet";
|
|
6
|
+
import cors from "cors";
|
|
7
|
+
import { isDevelopment } from "../index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Security headers middleware using helmet
|
|
10
|
+
* Configures appropriate security headers for the application
|
|
11
|
+
*/
|
|
12
|
+
export const securityMiddleware = helmet({
|
|
13
|
+
contentSecurityPolicy: {
|
|
14
|
+
directives: {
|
|
15
|
+
defaultSrc: ["'self'"],
|
|
16
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
17
|
+
scriptSrc: ["'self'"],
|
|
18
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
crossOriginEmbedderPolicy: false, // Allow embedding for development
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* CORS configuration for development
|
|
25
|
+
* Allows cross-origin requests in development mode
|
|
26
|
+
*/
|
|
27
|
+
export const corsMiddleware = cors({
|
|
28
|
+
origin: true,
|
|
29
|
+
credentials: true,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Apply security middleware to Express app
|
|
33
|
+
*/
|
|
34
|
+
export const applySecurity = (app) => {
|
|
35
|
+
app.use(securityMiddleware);
|
|
36
|
+
if (isDevelopment) {
|
|
37
|
+
app.use(corsMiddleware);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static file serving middleware configuration
|
|
3
|
+
* Handles static file serving with proper cache headers
|
|
4
|
+
*/
|
|
5
|
+
import { Express } from "express";
|
|
6
|
+
/**
|
|
7
|
+
* Apply static file serving middleware to Express app
|
|
8
|
+
*/
|
|
9
|
+
export declare const applyStatic: (app: Express) => void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static file serving middleware configuration
|
|
3
|
+
* Handles static file serving with proper cache headers
|
|
4
|
+
*/
|
|
5
|
+
import express from "express";
|
|
6
|
+
import { extname } from "path";
|
|
7
|
+
import { CONFIG, isDevelopment } from "../config/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Apply static file serving middleware to Express app
|
|
10
|
+
*/
|
|
11
|
+
export const applyStatic = (app) => {
|
|
12
|
+
/**
|
|
13
|
+
* Enhanced static file serving with proper cache headers
|
|
14
|
+
* In development mode, exclude JavaScript files so they can be handled by Vite
|
|
15
|
+
*/
|
|
16
|
+
app.use((req, res, next) => {
|
|
17
|
+
// In development mode, skip JavaScript files - let Vite handle them
|
|
18
|
+
if (isDevelopment && req.path.endsWith('.js')) {
|
|
19
|
+
console.log(`[DIAGNOSTIC] Skipping static middleware for JS file in development: ${req.path}`);
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
// Use express.static for non-JS files or in production
|
|
23
|
+
express.static(CONFIG.BUILD_DIR, {
|
|
24
|
+
maxAge: CONFIG.CACHE_MAX_AGE * 1000, // Convert to milliseconds
|
|
25
|
+
etag: true,
|
|
26
|
+
lastModified: true,
|
|
27
|
+
setHeaders: (res, path) => {
|
|
28
|
+
// Set cache headers based on file type
|
|
29
|
+
const ext = extname(path).toLowerCase();
|
|
30
|
+
// DIAGNOSTIC: Log static file serving
|
|
31
|
+
if (ext === '.js') {
|
|
32
|
+
console.log(`[DIAGNOSTIC] Static middleware serving JS file: ${path}`);
|
|
33
|
+
console.log(`[DIAGNOSTIC] Cache max age: ${CONFIG.CACHE_MAX_AGE}`);
|
|
34
|
+
console.log(`[DIAGNOSTIC] Is development: ${isDevelopment}`);
|
|
35
|
+
}
|
|
36
|
+
if (['.js', '.css', '.woff', '.woff2', '.ttf', '.eot'].includes(ext)) {
|
|
37
|
+
if (ext === '.js' && isDevelopment) {
|
|
38
|
+
// Don't cache JavaScript files in development to prevent stale code
|
|
39
|
+
const cacheControl = 'no-cache, no-store, must-revalidate';
|
|
40
|
+
res.setHeader('Cache-Control', cacheControl);
|
|
41
|
+
res.setHeader('Pragma', 'no-cache');
|
|
42
|
+
res.setHeader('Expires', '0');
|
|
43
|
+
console.log(`[DIAGNOSTIC] Setting no-cache for JS in development: ${cacheControl}`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Cache static assets for longer in production
|
|
47
|
+
const cacheControl = `public, max-age=${CONFIG.CACHE_MAX_AGE}`;
|
|
48
|
+
res.setHeader('Cache-Control', cacheControl);
|
|
49
|
+
if (ext === '.js') {
|
|
50
|
+
console.log(`[DIAGNOSTIC] Setting Cache-Control for JS: ${cacheControl}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (['.html', '.htm'].includes(ext)) {
|
|
55
|
+
// Don't cache HTML files in development
|
|
56
|
+
res.setHeader('Cache-Control', isDevelopment ? 'no-cache' : `public, max-age=${CONFIG.CACHE_MAX_AGE / 24}`);
|
|
57
|
+
}
|
|
58
|
+
// Security headers for static files
|
|
59
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
60
|
+
},
|
|
61
|
+
})(req, res, next);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API route handlers
|
|
3
|
+
* Handles health check, pages listing, and revalidate endpoints
|
|
4
|
+
*/
|
|
5
|
+
import { Request, Response, Express } from "express";
|
|
6
|
+
/**
|
|
7
|
+
* Health check endpoint
|
|
8
|
+
* Returns server status and basic information
|
|
9
|
+
*/
|
|
10
|
+
export declare const healthCheck: (req: Request, res: Response) => void;
|
|
11
|
+
/**
|
|
12
|
+
* API endpoint to list available pages
|
|
13
|
+
* In development mode, uses readPages helper; in production, scans static directory
|
|
14
|
+
*/
|
|
15
|
+
export declare const listPages: (req: Request, res: Response) => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Revalidate endpoint with enhanced error handling and rate limiting
|
|
18
|
+
*/
|
|
19
|
+
export declare const revalidateEndpoint: (req: Request, res: Response) => Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Register API routes with Express app
|
|
22
|
+
*/
|
|
23
|
+
export declare const registerApiRoutes: (app: Express) => void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API route handlers
|
|
3
|
+
* Handles health check, pages listing, and revalidate endpoints
|
|
4
|
+
*/
|
|
5
|
+
import { revalidate } from "../scripts/revalidate.js";
|
|
6
|
+
import { getAvailablePagesRuntime } from "../../helpers/renderPageRuntime.js";
|
|
7
|
+
import { readdir } from "fs/promises";
|
|
8
|
+
import { extname, join } from "path";
|
|
9
|
+
import { CONFIG, isDevelopment } from "../config/index.js";
|
|
10
|
+
import { revalidateLimiter } from "../middleware/rateLimiting.js";
|
|
11
|
+
/**
|
|
12
|
+
* Health check endpoint
|
|
13
|
+
* Returns server status and basic information
|
|
14
|
+
*/
|
|
15
|
+
export const healthCheck = (req, res) => {
|
|
16
|
+
const healthInfo = {
|
|
17
|
+
status: 'healthy',
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
uptime: process.uptime(),
|
|
20
|
+
environment: CONFIG.NODE_ENV,
|
|
21
|
+
version: process.version,
|
|
22
|
+
memory: process.memoryUsage(),
|
|
23
|
+
};
|
|
24
|
+
res.status(200).json(healthInfo);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* API endpoint to list available pages
|
|
28
|
+
* In development mode, uses readPages helper; in production, scans static directory
|
|
29
|
+
*/
|
|
30
|
+
export const listPages = async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
let pages;
|
|
33
|
+
if (isDevelopment) {
|
|
34
|
+
// Use runtime pages discovery in development
|
|
35
|
+
const runtimePages = getAvailablePagesRuntime();
|
|
36
|
+
pages = Object.keys(runtimePages).map(pageName => {
|
|
37
|
+
const filePath = runtimePages[pageName];
|
|
38
|
+
const isIndexFile = filePath.endsWith('/index.tsx');
|
|
39
|
+
return {
|
|
40
|
+
name: isIndexFile ? `${pageName}/index.tsx` : `${pageName}.tsx`,
|
|
41
|
+
path: pageName === 'index' ? '/' : `/${pageName}`,
|
|
42
|
+
file: filePath,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Use static file scanning in production
|
|
48
|
+
pages = await getAvailablePages();
|
|
49
|
+
}
|
|
50
|
+
const response = {
|
|
51
|
+
success: true,
|
|
52
|
+
pages,
|
|
53
|
+
count: pages.length,
|
|
54
|
+
mode: isDevelopment ? 'development (runtime)' : 'production (static)',
|
|
55
|
+
};
|
|
56
|
+
res.status(200).json(response);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('Error listing pages:', error);
|
|
60
|
+
const errorResponse = {
|
|
61
|
+
success: false,
|
|
62
|
+
error: 'Failed to list available pages',
|
|
63
|
+
message: isDevelopment ? error.message : 'Internal server error',
|
|
64
|
+
};
|
|
65
|
+
res.status(500).json(errorResponse);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Revalidate endpoint with enhanced error handling and rate limiting
|
|
70
|
+
*/
|
|
71
|
+
export const revalidateEndpoint = async (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
await revalidate(req, res);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Revalidate error:', error);
|
|
77
|
+
const errorResponse = {
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'Revalidation failed',
|
|
80
|
+
message: isDevelopment ? error.message : 'Internal server error',
|
|
81
|
+
};
|
|
82
|
+
res.status(500).json(errorResponse);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Scans the static directory for available HTML pages
|
|
87
|
+
* @returns {Promise<Array>} Array of available page paths
|
|
88
|
+
*/
|
|
89
|
+
async function getAvailablePages() {
|
|
90
|
+
const pages = [];
|
|
91
|
+
try {
|
|
92
|
+
const scanDirectory = async (dir, basePath = '') => {
|
|
93
|
+
const items = await readdir(join(CONFIG.BUILD_DIR, dir), { withFileTypes: true });
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
const itemPath = join(dir, item.name);
|
|
96
|
+
const urlPath = join(basePath, item.name);
|
|
97
|
+
if (item.isDirectory()) {
|
|
98
|
+
await scanDirectory(itemPath, urlPath);
|
|
99
|
+
}
|
|
100
|
+
else if (item.isFile() && extname(item.name).toLowerCase() === '.html') {
|
|
101
|
+
const pagePath = urlPath.replace(/\.html$/, '').replace(/\\/g, '/');
|
|
102
|
+
pages.push({
|
|
103
|
+
name: item.name,
|
|
104
|
+
path: pagePath === 'index' ? '/' : `/${pagePath}`,
|
|
105
|
+
file: itemPath.replace(/\\/g, '/'),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
await scanDirectory('');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.warn('Could not scan pages directory:', error.message);
|
|
114
|
+
}
|
|
115
|
+
return pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Register API routes with Express app
|
|
119
|
+
*/
|
|
120
|
+
export const registerApiRoutes = (app) => {
|
|
121
|
+
app.get('/health', healthCheck);
|
|
122
|
+
app.get('/api/pages', listPages);
|
|
123
|
+
app.post('/revalidate', revalidateLimiter, revalidateEndpoint);
|
|
124
|
+
};
|
|
@@ -7,8 +7,8 @@ export const revalidate = (req, res) => {
|
|
|
7
7
|
try {
|
|
8
8
|
const paths = req?.body?.paths || [];
|
|
9
9
|
const pathsArg = paths.length > 0 ? paths.join(" ") : "";
|
|
10
|
-
const cachePages = path.resolve(__dirname, "
|
|
11
|
-
const buildHtmlConfig = path.resolve(__dirname, "
|
|
10
|
+
const cachePages = path.resolve(__dirname, "../../../helpers/cachePages.js");
|
|
11
|
+
const buildHtmlConfig = path.resolve(__dirname, "../../../scripts/build-html.js");
|
|
12
12
|
const buildCommand = `NODE_TLS_REJECT_UNAUTHORIZED=0 node ${cachePages} ${pathsArg && `${pathsArg}`} && npx tsx ${buildHtmlConfig}`;
|
|
13
13
|
exec(buildCommand, (error, stdout, stderr) => {
|
|
14
14
|
if (error) {
|
|
@@ -20,7 +20,7 @@ export const revalidate = (req, res) => {
|
|
|
20
20
|
console.error(`stderr: ${stderr}`);
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
|
-
|
|
23
|
+
res
|
|
24
24
|
.status(200)
|
|
25
25
|
.send(`Revalidation triggered, paths: ${paths.length > 0 ? paths.join(", ") : "all pages"} built!`);
|
|
26
26
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher system for hot reloading
|
|
3
|
+
* Watches source files and triggers WebSocket broadcasts on changes
|
|
4
|
+
*/
|
|
5
|
+
import { FSWatcher } from 'chokidar';
|
|
6
|
+
type ReloadType = 'style' | 'page' | 'full' | 'asset';
|
|
7
|
+
/**
|
|
8
|
+
* Initialize file watcher
|
|
9
|
+
*/
|
|
10
|
+
export declare const initializeFileWatcher: () => FSWatcher | null;
|
|
11
|
+
/**
|
|
12
|
+
* Get current file watcher instance
|
|
13
|
+
*/
|
|
14
|
+
export declare const getFileWatcher: () => FSWatcher | null;
|
|
15
|
+
/**
|
|
16
|
+
* Get watched files count
|
|
17
|
+
*/
|
|
18
|
+
export declare const getWatchedFilesCount: () => number;
|
|
19
|
+
/**
|
|
20
|
+
* Close file watcher and cleanup
|
|
21
|
+
*/
|
|
22
|
+
export declare const closeFileWatcher: () => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Manually trigger a reload (useful for testing)
|
|
25
|
+
*/
|
|
26
|
+
export declare const triggerReload: (type?: ReloadType, data?: Record<string, any>) => void;
|
|
27
|
+
export {};
|