@bouygues-telecom/staticjs 0.1.11 → 0.1.15
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 → _build}/scripts/create-static-app.js +0 -0
- 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/cli.js +0 -36
- package/dist/scripts/generate-test-multiapps.js +0 -51
|
@@ -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 {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher system for hot reloading
|
|
3
|
+
* Watches source files and triggers WebSocket broadcasts on changes
|
|
4
|
+
*/
|
|
5
|
+
import chokidar from 'chokidar';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { broadcastReload } from './websocket.js';
|
|
8
|
+
import { isDevelopment } from '../config/index.js';
|
|
9
|
+
import { invalidateRuntimeCache } from '../middleware/runtime.js';
|
|
10
|
+
let watcher = null;
|
|
11
|
+
let debounceTimer = null;
|
|
12
|
+
const DEBOUNCE_DELAY = 300; // 300ms debounce
|
|
13
|
+
/**
|
|
14
|
+
* Determine reload type based on file extension
|
|
15
|
+
*/
|
|
16
|
+
const getReloadType = (filePath) => {
|
|
17
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
18
|
+
switch (ext) {
|
|
19
|
+
case '.css':
|
|
20
|
+
case '.scss':
|
|
21
|
+
case '.sass':
|
|
22
|
+
case '.less':
|
|
23
|
+
return 'style';
|
|
24
|
+
case '.tsx':
|
|
25
|
+
case '.jsx':
|
|
26
|
+
case '.ts':
|
|
27
|
+
case '.js':
|
|
28
|
+
return 'page';
|
|
29
|
+
case '.json':
|
|
30
|
+
case '.config.js':
|
|
31
|
+
case '.config.ts':
|
|
32
|
+
return 'full';
|
|
33
|
+
case '.png':
|
|
34
|
+
case '.jpg':
|
|
35
|
+
case '.jpeg':
|
|
36
|
+
case '.gif':
|
|
37
|
+
case '.svg':
|
|
38
|
+
case '.ico':
|
|
39
|
+
case '.woff':
|
|
40
|
+
case '.woff2':
|
|
41
|
+
case '.ttf':
|
|
42
|
+
case '.eot':
|
|
43
|
+
return 'asset';
|
|
44
|
+
default:
|
|
45
|
+
return 'page';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Handle file change with debouncing
|
|
50
|
+
*/
|
|
51
|
+
const handleFileChange = (eventType, filePath) => {
|
|
52
|
+
// Clear existing timer
|
|
53
|
+
if (debounceTimer) {
|
|
54
|
+
clearTimeout(debounceTimer);
|
|
55
|
+
}
|
|
56
|
+
// Set new timer
|
|
57
|
+
debounceTimer = setTimeout(async () => {
|
|
58
|
+
const reloadType = getReloadType(filePath);
|
|
59
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
60
|
+
// Invalidate runtime cache when source files change
|
|
61
|
+
if (relativePath.includes('src/') || relativePath.includes('src\\')) {
|
|
62
|
+
await invalidateRuntimeCache();
|
|
63
|
+
}
|
|
64
|
+
// Broadcast reload message to WebSocket clients
|
|
65
|
+
broadcastReload(reloadType, {
|
|
66
|
+
file: relativePath,
|
|
67
|
+
event: eventType,
|
|
68
|
+
timestamp: Date.now()
|
|
69
|
+
});
|
|
70
|
+
debounceTimer = null;
|
|
71
|
+
}, DEBOUNCE_DELAY);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Initialize file watcher
|
|
75
|
+
*/
|
|
76
|
+
export const initializeFileWatcher = () => {
|
|
77
|
+
if (!isDevelopment) {
|
|
78
|
+
// Skipping file watcher in production mode
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// Define paths to watch based on current working directory
|
|
83
|
+
const cwd = process.cwd();
|
|
84
|
+
const watchPaths = [
|
|
85
|
+
'src', // Source directory
|
|
86
|
+
'src/**/*', // All source files in current directory
|
|
87
|
+
'package.json', // Dependency changes
|
|
88
|
+
'tsconfig.json', // TypeScript configuration
|
|
89
|
+
'eslint.config.js' // ESLint configuration
|
|
90
|
+
];
|
|
91
|
+
// If we're in the main project directory, also watch template directories
|
|
92
|
+
if (cwd.includes('static.js') && !cwd.includes('templates/')) {
|
|
93
|
+
watchPaths.push('templates/**/src/**/*', // Template source files
|
|
94
|
+
'templates/**/package.json', // Template dependencies
|
|
95
|
+
'templates/**/tsconfig.json' // Template TypeScript configs
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
// Create watcher with options
|
|
99
|
+
watcher = chokidar.watch(watchPaths, {
|
|
100
|
+
ignored: [
|
|
101
|
+
'**/node_modules/**',
|
|
102
|
+
'**/_build/**',
|
|
103
|
+
'**/node_modules/**',
|
|
104
|
+
'**/.git/**',
|
|
105
|
+
'**/coverage/**',
|
|
106
|
+
'**/*.log',
|
|
107
|
+
'**/.*'
|
|
108
|
+
],
|
|
109
|
+
ignoreInitial: true,
|
|
110
|
+
persistent: true,
|
|
111
|
+
followSymlinks: true,
|
|
112
|
+
depth: 10, // Allow deep directory watching
|
|
113
|
+
awaitWriteFinish: {
|
|
114
|
+
stabilityThreshold: 100,
|
|
115
|
+
pollInterval: 50
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Set up event handlers
|
|
119
|
+
watcher
|
|
120
|
+
.on('add', (filePath) => handleFileChange('add', filePath))
|
|
121
|
+
.on('change', (filePath) => handleFileChange('change', filePath))
|
|
122
|
+
.on('unlink', (filePath) => handleFileChange('unlink', filePath))
|
|
123
|
+
.on('addDir', (dirPath) => {
|
|
124
|
+
// Directory added (silent)
|
|
125
|
+
})
|
|
126
|
+
.on('unlinkDir', (dirPath) => {
|
|
127
|
+
// Directory removed, triggering full reload
|
|
128
|
+
// Trigger full reload when directories are removed
|
|
129
|
+
broadcastReload('full', {
|
|
130
|
+
directory: path.relative(process.cwd(), dirPath),
|
|
131
|
+
event: 'unlinkDir'
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
.on('error', (err) => {
|
|
135
|
+
console.error('[FileWatcher] Watcher error:', err);
|
|
136
|
+
})
|
|
137
|
+
.on('ready', () => {
|
|
138
|
+
const watched = watcher.getWatched();
|
|
139
|
+
const watchedPaths = Object.keys(watched);
|
|
140
|
+
// File watcher initialized
|
|
141
|
+
});
|
|
142
|
+
return watcher;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error('[FileWatcher] Failed to initialize file watcher:', error);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Get current file watcher instance
|
|
151
|
+
*/
|
|
152
|
+
export const getFileWatcher = () => watcher;
|
|
153
|
+
/**
|
|
154
|
+
* Get watched files count
|
|
155
|
+
*/
|
|
156
|
+
export const getWatchedFilesCount = () => {
|
|
157
|
+
if (!watcher)
|
|
158
|
+
return 0;
|
|
159
|
+
const watched = watcher.getWatched();
|
|
160
|
+
return Object.values(watched).reduce((total, files) => total + files.length, 0);
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Close file watcher and cleanup
|
|
164
|
+
*/
|
|
165
|
+
export const closeFileWatcher = async () => {
|
|
166
|
+
if (!watcher) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
// Closing file watcher
|
|
171
|
+
// Clear debounce timer
|
|
172
|
+
if (debounceTimer) {
|
|
173
|
+
clearTimeout(debounceTimer);
|
|
174
|
+
debounceTimer = null;
|
|
175
|
+
}
|
|
176
|
+
// Close watcher
|
|
177
|
+
watcher.close().then(() => {
|
|
178
|
+
// File watcher closed
|
|
179
|
+
watcher = null;
|
|
180
|
+
resolve();
|
|
181
|
+
}).catch((error) => {
|
|
182
|
+
console.error('[FileWatcher] Error closing file watcher:', error);
|
|
183
|
+
watcher = null;
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Manually trigger a reload (useful for testing)
|
|
190
|
+
*/
|
|
191
|
+
export const triggerReload = (type = 'page', data = {}) => {
|
|
192
|
+
// Manually triggering reload
|
|
193
|
+
broadcastReload(type, { ...data, manual: true });
|
|
194
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server startup and shutdown utilities
|
|
3
|
+
* Handles server initialization and graceful shutdown
|
|
4
|
+
*/
|
|
5
|
+
import { Server } from 'http';
|
|
6
|
+
import { Express } from 'express';
|
|
7
|
+
/**
|
|
8
|
+
* Start the server with proper error handling
|
|
9
|
+
*/
|
|
10
|
+
export declare const startServer: (app: Express, initializeViteServer?: (app: Express) => Promise<any>) => Promise<Server>;
|
|
11
|
+
/**
|
|
12
|
+
* Graceful shutdown handling
|
|
13
|
+
*/
|
|
14
|
+
export declare const gracefulShutdown: (server: Server, signal: string) => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Setup process event handlers for graceful shutdown
|
|
17
|
+
*/
|
|
18
|
+
export declare const setupProcessHandlers: (server: Server) => void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server startup and shutdown utilities
|
|
3
|
+
* Handles server initialization and graceful shutdown
|
|
4
|
+
*/
|
|
5
|
+
import { CONFIG } from "../config/index.js";
|
|
6
|
+
import { closeViteServer, getViteServer } from "./vite.js";
|
|
7
|
+
import { closeWebSocketServer, getConnectedClientsCount } from "./websocket.js";
|
|
8
|
+
import { closeFileWatcher, getWatchedFilesCount } from "./fileWatcher.js";
|
|
9
|
+
/**
|
|
10
|
+
* Start the server with proper error handling
|
|
11
|
+
*/
|
|
12
|
+
export const startServer = async (app, initializeViteServer) => {
|
|
13
|
+
// Initialize Vite server first (if provided)
|
|
14
|
+
if (initializeViteServer) {
|
|
15
|
+
await initializeViteServer(app);
|
|
16
|
+
}
|
|
17
|
+
return app.listen(CONFIG.PORT, () => {
|
|
18
|
+
const viteServer = getViteServer();
|
|
19
|
+
const wsClients = getConnectedClientsCount();
|
|
20
|
+
const watchedFiles = getWatchedFilesCount();
|
|
21
|
+
console.log(`
|
|
22
|
+
🚀 StaticJS Server Started
|
|
23
|
+
=====================================
|
|
24
|
+
Environment: ${CONFIG.NODE_ENV}
|
|
25
|
+
Port: ${CONFIG.PORT}
|
|
26
|
+
URL: http://localhost:${CONFIG.PORT}
|
|
27
|
+
Health Check: http://localhost:${CONFIG.PORT}/health
|
|
28
|
+
Pages API: http://localhost:${CONFIG.PORT}/api/pages
|
|
29
|
+
${viteServer ? 'Vite JS Compilation: ✅ Enabled' : 'Vite JS Compilation: ❌ Disabled'}
|
|
30
|
+
${CONFIG.WEBSOCKET_ENABLED ? `WebSocket Hot Reload: ✅ Enabled (${wsClients} clients)` : 'WebSocket Hot Reload: ❌ Disabled'}
|
|
31
|
+
${CONFIG.FILE_WATCHING_ENABLED ? `File Watching: ✅ Enabled (${watchedFiles} files)` : 'File Watching: ❌ Disabled'}
|
|
32
|
+
=====================================
|
|
33
|
+
`);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Graceful shutdown handling
|
|
38
|
+
*/
|
|
39
|
+
export const gracefulShutdown = async (server, signal) => {
|
|
40
|
+
console.log(`\n📡 Received ${signal}. Starting graceful shutdown...`);
|
|
41
|
+
try {
|
|
42
|
+
// Close file watcher first
|
|
43
|
+
if (CONFIG.FILE_WATCHING_ENABLED) {
|
|
44
|
+
console.log('🔍 Closing file watcher...');
|
|
45
|
+
await closeFileWatcher();
|
|
46
|
+
}
|
|
47
|
+
// Close WebSocket server
|
|
48
|
+
if (CONFIG.WEBSOCKET_ENABLED) {
|
|
49
|
+
console.log('🔌 Closing WebSocket server...');
|
|
50
|
+
await closeWebSocketServer();
|
|
51
|
+
}
|
|
52
|
+
// Close Vite server if it exists
|
|
53
|
+
console.log('⚡ Closing Vite server...');
|
|
54
|
+
await closeViteServer();
|
|
55
|
+
// Close HTTP server
|
|
56
|
+
console.log('🌐 Closing HTTP server...');
|
|
57
|
+
server.close((err) => {
|
|
58
|
+
if (err) {
|
|
59
|
+
console.error('❌ Error during server shutdown:', err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
console.log('✅ Server closed successfully');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('❌ Error during graceful shutdown:', error);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Force shutdown after 10 seconds
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
console.error('⚠️ Forced shutdown after timeout');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}, 10000);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Setup process event handlers for graceful shutdown
|
|
78
|
+
*/
|
|
79
|
+
export const setupProcessHandlers = (server) => {
|
|
80
|
+
// Handle shutdown signals
|
|
81
|
+
process.on('SIGTERM', () => gracefulShutdown(server, 'SIGTERM'));
|
|
82
|
+
process.on('SIGINT', () => gracefulShutdown(server, 'SIGINT'));
|
|
83
|
+
// Handle uncaught exceptions
|
|
84
|
+
process.on('uncaughtException', (err) => {
|
|
85
|
+
console.error('💥 Uncaught Exception:', err);
|
|
86
|
+
gracefulShutdown(server, 'uncaughtException');
|
|
87
|
+
});
|
|
88
|
+
// Handle unhandled promise rejections
|
|
89
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
90
|
+
console.error('💥 Unhandled Rejection at:', promise, 'reason:', reason);
|
|
91
|
+
gracefulShutdown(server, 'unhandledRejection');
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite server utilities
|
|
3
|
+
* Handles Vite server initialization and JavaScript serving for development mode
|
|
4
|
+
*/
|
|
5
|
+
import { ViteDevServer } from "vite";
|
|
6
|
+
import { Express } from "express";
|
|
7
|
+
/**
|
|
8
|
+
* Initialize Vite server for development mode
|
|
9
|
+
*/
|
|
10
|
+
export declare const initializeViteServer: (app: Express) => Promise<ViteDevServer | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Get the current Vite server instance
|
|
13
|
+
*/
|
|
14
|
+
export declare const getViteServer: () => ViteDevServer | null;
|
|
15
|
+
/**
|
|
16
|
+
* Close the Vite server
|
|
17
|
+
*/
|
|
18
|
+
export declare const closeViteServer: () => Promise<void>;
|