forked from lissy93/networking-toolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart.js
More file actions
164 lines (135 loc) · 4.16 KB
/
start.js
File metadata and controls
164 lines (135 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env node
/**
* This is just a wrapper around the built SvelteKit app,
* which can otherwise be started with `node build`
* The purpose of this wrapper is to add:
* - Automatic restarts with exponential backoff on crashes
* - Graceful shutdown on SIGTERM and SIGINT
* - Improved logging
*/
import { spawn } from 'child_process';
import { existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const BUILD_DIR = join(__dirname, 'build');
const ENTRY_FILE = join(BUILD_DIR, 'index.js');
const MAX_RESTARTS = 10;
const INITIAL_BACKOFF = 1000;
const MAX_BACKOFF = 30000;
let restartCount = 0;
let backoffTime = INITIAL_BACKOFF;
let child = null;
let isShuttingDown = false;
// Console logging with colors and icons
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m'
};
const log = (message, type = 'info') => {
const styles = {
error: { color: colors.red, icon: '❌' },
success: { color: colors.green, icon: '✅' },
warning: { color: colors.yellow, icon: '⚠️ ' },
info: { color: colors.cyan, icon: 'ℹ️ ' },
start: { color: colors.blue, icon: '🚀' },
stop: { color: colors.yellow, icon: '⏹️ ' },
wait: { color: colors.yellow, icon: '⏳' }
};
const { color, icon } = styles[type] || styles.info;
console.log(`${color}${icon} ${message}${colors.reset}`);
};
// Validate build exists and is correct type
function validateBuild() {
if (!existsSync(BUILD_DIR)) {
log('Build directory not found.', 'error');
log('Run: npm run build:node', 'info');
process.exit(1);
}
if (!existsSync(ENTRY_FILE)) {
log('Build entry file not found.', 'error');
log('The build might not be a Node.js build.', 'warning');
log('Run: npm run build:node', 'info');
process.exit(1);
}
// Check for handler.js which indicates adapter-node
const handlerFile = join(BUILD_DIR, 'handler.js');
if (!existsSync(handlerFile)) {
log('This appears to be a static build, not a Node.js build.', 'error');
log('Run: npm run build:node', 'info');
process.exit(1);
}
}
function startServer() {
if (isShuttingDown) return;
log(`Starting server... (attempt ${restartCount + 1})`, 'start');
child = spawn('node', ['build'], {
stdio: 'inherit',
env: { ...process.env, NODE_ENV: process.env.NODE_ENV || 'production' }
});
// Reset counters after successful run (30s uptime)
const successTimer = setTimeout(() => {
restartCount = 0;
backoffTime = INITIAL_BACKOFF;
}, 30000);
child.on('error', (err) => {
clearTimeout(successTimer);
log(`Failed to start server: ${err.message}`, 'error');
// Error event is always followed by exit event, so we handle restart there
});
child.on('exit', (code, signal) => {
clearTimeout(successTimer);
child = null;
if (isShuttingDown) {
log('Server stopped gracefully', 'success');
process.exit(0);
}
if (code === 0) {
log('Server exited normally', 'success');
process.exit(0);
}
restartCount++;
if (restartCount >= MAX_RESTARTS) {
log(`Server crashed ${MAX_RESTARTS} times. Giving up.`, 'error');
process.exit(1);
}
log(`Server crashed with ${signal ? `signal ${signal}` : `code ${code}`}`, 'warning');
log(`Restarting in ${backoffTime / 1000}s...`, 'wait');
setTimeout(() => {
backoffTime = Math.min(backoffTime * 2, MAX_BACKOFF);
startServer();
}, backoffTime);
});
}
function shutdown(signal) {
if (isShuttingDown) return;
isShuttingDown = true;
log(`Received ${signal}, shutting down gracefully...`, 'stop');
if (child) {
child.kill('SIGTERM');
// Force kill after 10s if not stopped
setTimeout(() => {
if (child) {
log('Force killing server...', 'warning');
child.kill('SIGKILL');
}
}, 10000);
} else {
process.exit(0);
}
}
// Handle shutdown signals
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('unhandledRejection', (err) => {
log(`Unhandled rejection: ${err.message}`, 'error');
shutdown('unhandledRejection');
});
// Validate and start
validateBuild();
log('Node.js build detected', 'success');
startServer();