neex 0.6.47 → 0.6.50

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.
@@ -4,118 +4,407 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.StartManager = void 0;
7
- // src/start-manager.ts - Production application runner
7
+ // src/start-manager.ts - Production server manager for built TypeScript projects
8
8
  const child_process_1 = require("child_process");
9
+ const chokidar_1 = require("chokidar");
9
10
  const logger_manager_js_1 = require("./logger-manager.js");
10
11
  const chalk_1 = __importDefault(require("chalk"));
11
12
  const figures_1 = __importDefault(require("figures"));
12
- const fs_1 = __importDefault(require("fs"));
13
13
  const path_1 = __importDefault(require("path"));
14
+ const promises_1 = __importDefault(require("fs/promises"));
15
+ const fs_1 = require("fs");
16
+ const perf_hooks_1 = require("perf_hooks");
14
17
  class StartManager {
15
18
  constructor(options) {
16
- this.process = null;
17
- this.isStopping = false;
19
+ this.serverProcess = null;
20
+ this.watcher = null;
21
+ this.isStarting = false;
22
+ this.restartCount = 0;
23
+ this.startTime = 0;
24
+ this.isShuttingDown = false;
25
+ this.restartTimer = null;
18
26
  this.options = options;
19
27
  }
20
- async startProcess() {
21
- var _a, _b;
22
- if (this.process) {
23
- return;
28
+ async validateEntry() {
29
+ let entryPath = this.options.entry;
30
+ // Check if entry exists as provided
31
+ if ((0, fs_1.existsSync)(entryPath)) {
32
+ return path_1.default.resolve(entryPath);
24
33
  }
25
- const nodeArgs = this.options.nodeArgs || [];
26
- const args = [...nodeArgs, this.options.entry];
27
- if (this.options.verbose) {
28
- logger_manager_js_1.loggerManager.printLine(`Executing: node ${args.join(' ')}`, 'info');
29
- }
30
- this.process = (0, child_process_1.spawn)('node', args, {
31
- stdio: ['ignore', 'pipe', 'pipe'],
32
- shell: false,
33
- env: {
34
- ...process.env,
35
- NODE_ENV: 'production',
36
- FORCE_COLOR: this.options.color ? '1' : '0'
37
- },
38
- detached: true
39
- });
40
- const appName = this.options.name || path_1.default.basename(this.options.entry);
41
- if (!this.options.quiet) {
42
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Starting ${chalk_1.default.cyan(appName)} in production mode...`, 'info');
34
+ // Try with .js extension
35
+ if (!entryPath.endsWith('.js')) {
36
+ const jsPath = entryPath + '.js';
37
+ if ((0, fs_1.existsSync)(jsPath)) {
38
+ return path_1.default.resolve(jsPath);
39
+ }
43
40
  }
44
- (_a = this.process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
45
- if (!this.options.quiet) {
46
- process.stdout.write(data);
41
+ // Try in dist directory
42
+ const distPath = path_1.default.join('dist', entryPath);
43
+ if ((0, fs_1.existsSync)(distPath)) {
44
+ return path_1.default.resolve(distPath);
45
+ }
46
+ // Try in dist with .js extension
47
+ if (!entryPath.endsWith('.js')) {
48
+ const distJsPath = path_1.default.join('dist', entryPath + '.js');
49
+ if ((0, fs_1.existsSync)(distJsPath)) {
50
+ return path_1.default.resolve(distJsPath);
47
51
  }
48
- });
49
- (_b = this.process.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
52
+ }
53
+ // Try common entry points
54
+ const commonEntries = [
55
+ 'dist/index.js',
56
+ 'dist/server.js',
57
+ 'dist/app.js',
58
+ 'dist/main.js',
59
+ 'index.js',
60
+ 'server.js'
61
+ ];
62
+ for (const entry of commonEntries) {
63
+ if ((0, fs_1.existsSync)(entry)) {
64
+ if (!this.options.quiet) {
65
+ logger_manager_js_1.loggerManager.printLine(`Using entry point: ${entry}`, 'info');
66
+ }
67
+ return path_1.default.resolve(entry);
68
+ }
69
+ }
70
+ throw new Error(`Entry file not found: ${entryPath}`);
71
+ }
72
+ async loadPackageJson() {
73
+ try {
74
+ const packageJsonPath = path_1.default.join(process.cwd(), 'package.json');
75
+ if ((0, fs_1.existsSync)(packageJsonPath)) {
76
+ const content = await promises_1.default.readFile(packageJsonPath, 'utf8');
77
+ return JSON.parse(content);
78
+ }
79
+ }
80
+ catch (error) {
81
+ // Ignore package.json errors
82
+ }
83
+ return null;
84
+ }
85
+ getNodeArgs() {
86
+ const args = [];
87
+ // Add custom node arguments
88
+ if (this.options.nodeArgs.length > 0) {
89
+ args.push(...this.options.nodeArgs);
90
+ }
91
+ // Memory limit
92
+ if (this.options.memory > 0) {
93
+ args.push(`--max-old-space-size=${this.options.memory}`);
94
+ }
95
+ // Debugging
96
+ if (this.options.inspect) {
97
+ args.push(`--inspect=${this.options.inspectPort}`);
98
+ }
99
+ // Production optimizations
100
+ if (this.options.env === 'production') {
101
+ args.push('--optimize-for-size');
102
+ }
103
+ return args;
104
+ }
105
+ getEnvironment() {
106
+ const env = {
107
+ ...process.env,
108
+ NODE_ENV: this.options.env,
109
+ PORT: this.options.port.toString(),
110
+ HOST: this.options.host,
111
+ LOG_LEVEL: this.options.logLevel
112
+ };
113
+ // Add color support
114
+ if (this.options.color) {
115
+ env.FORCE_COLOR = '1';
116
+ }
117
+ return env;
118
+ }
119
+ async writePidFile(pid) {
120
+ if (this.options.pid) {
121
+ try {
122
+ await promises_1.default.writeFile(this.options.pid, pid.toString());
123
+ if (this.options.verbose) {
124
+ logger_manager_js_1.loggerManager.printLine(`PID file written: ${this.options.pid}`, 'info');
125
+ }
126
+ }
127
+ catch (error) {
128
+ logger_manager_js_1.loggerManager.printLine(`Failed to write PID file: ${error.message}`, 'warn');
129
+ }
130
+ }
131
+ }
132
+ async removePidFile() {
133
+ if (this.options.pid && (0, fs_1.existsSync)(this.options.pid)) {
134
+ try {
135
+ await promises_1.default.unlink(this.options.pid);
136
+ if (this.options.verbose) {
137
+ logger_manager_js_1.loggerManager.printLine(`PID file removed: ${this.options.pid}`, 'info');
138
+ }
139
+ }
140
+ catch (error) {
141
+ // Ignore cleanup errors
142
+ }
143
+ }
144
+ }
145
+ async startServer() {
146
+ if (this.isStarting || this.isShuttingDown) {
147
+ return;
148
+ }
149
+ this.isStarting = true;
150
+ this.startTime = perf_hooks_1.performance.now();
151
+ try {
152
+ const entryPath = await this.validateEntry();
153
+ const nodeArgs = this.getNodeArgs();
154
+ const env = this.getEnvironment();
50
155
  if (!this.options.quiet) {
51
- process.stderr.write(data);
156
+ const restartInfo = this.restartCount > 0 ? ` (restart #${this.restartCount})` : '';
157
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting server${restartInfo}...`, 'info');
158
+ if (this.options.verbose) {
159
+ logger_manager_js_1.loggerManager.printLine(`Entry: ${entryPath}`, 'info');
160
+ logger_manager_js_1.loggerManager.printLine(`Environment: ${this.options.env}`, 'info');
161
+ logger_manager_js_1.loggerManager.printLine(`Host: ${this.options.host}:${this.options.port}`, 'info');
162
+ if (nodeArgs.length > 0) {
163
+ logger_manager_js_1.loggerManager.printLine(`Node args: ${nodeArgs.join(' ')}`, 'info');
164
+ }
165
+ }
52
166
  }
53
- });
54
- this.process.on('error', (error) => {
55
- logger_manager_js_1.loggerManager.printLine(`Application error: ${error.message}`, 'error');
56
- });
57
- this.process.on('exit', (code) => {
58
- this.process = null;
59
- if (!this.isStopping) {
60
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Application ${appName} exited with code ${code}`, 'error');
61
- if (this.options.watch) {
62
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.arrowRight)} Restarting application...`, 'info');
63
- this.startProcess();
167
+ const args = [...nodeArgs, entryPath];
168
+ this.serverProcess = (0, child_process_1.spawn)('node', args, {
169
+ stdio: this.options.verbose ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'ignore', 'pipe'],
170
+ env,
171
+ detached: false,
172
+ cwd: process.cwd()
173
+ });
174
+ if (this.serverProcess.pid) {
175
+ await this.writePidFile(this.serverProcess.pid);
176
+ }
177
+ // Handle server output
178
+ if (this.options.verbose && this.serverProcess.stdout) {
179
+ this.serverProcess.stdout.on('data', (data) => {
180
+ const output = data.toString().trim();
181
+ if (output) {
182
+ logger_manager_js_1.loggerManager.printLine(`[SERVER] ${output}`, 'info');
183
+ }
184
+ });
185
+ }
186
+ // Handle server errors
187
+ if (this.serverProcess.stderr) {
188
+ this.serverProcess.stderr.on('data', (data) => {
189
+ const error = data.toString().trim();
190
+ if (error && !this.options.quiet) {
191
+ logger_manager_js_1.loggerManager.printLine(`[SERVER] ${error}`, 'error');
192
+ }
193
+ });
194
+ }
195
+ // Handle process events
196
+ this.serverProcess.on('error', (error) => {
197
+ this.isStarting = false;
198
+ if (!this.isShuttingDown) {
199
+ logger_manager_js_1.loggerManager.printLine(`Server process error: ${error.message}`, 'error');
200
+ this.handleServerExit(1);
201
+ }
202
+ });
203
+ this.serverProcess.on('exit', (code, signal) => {
204
+ this.isStarting = false;
205
+ if (!this.isShuttingDown) {
206
+ this.handleServerExit(code || 0, signal || undefined);
64
207
  }
208
+ });
209
+ // Wait for server to start
210
+ await this.waitForServerStart();
211
+ this.isStarting = false;
212
+ const duration = Math.round(perf_hooks_1.performance.now() - this.startTime);
213
+ if (!this.options.quiet) {
214
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server started successfully in ${duration}ms`, 'info');
215
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Running on ${chalk_1.default.cyan(`http://${this.options.host}:${this.options.port}`)}`, 'info');
216
+ }
217
+ }
218
+ catch (error) {
219
+ this.isStarting = false;
220
+ throw error;
221
+ }
222
+ }
223
+ async waitForServerStart() {
224
+ return new Promise((resolve, reject) => {
225
+ const timeout = setTimeout(() => {
226
+ reject(new Error(`Server startup timeout after ${this.options.timeout}ms`));
227
+ }, this.options.timeout);
228
+ if (this.serverProcess) {
229
+ // If process exits immediately, it's likely an error
230
+ const exitHandler = (code) => {
231
+ clearTimeout(timeout);
232
+ if (code !== 0) {
233
+ reject(new Error(`Server exited with code ${code}`));
234
+ }
235
+ else {
236
+ resolve();
237
+ }
238
+ };
239
+ this.serverProcess.once('exit', exitHandler);
240
+ // Give some time for the server to start
241
+ setTimeout(() => {
242
+ if (this.serverProcess && !this.serverProcess.killed) {
243
+ this.serverProcess.removeListener('exit', exitHandler);
244
+ clearTimeout(timeout);
245
+ resolve();
246
+ }
247
+ }, 1000);
248
+ }
249
+ else {
250
+ clearTimeout(timeout);
251
+ reject(new Error('Server process not created'));
65
252
  }
66
253
  });
67
254
  }
68
- async start() {
69
- if (!fs_1.default.existsSync(this.options.entry)) {
70
- throw new Error(`Entry file not found: ${this.options.entry}`);
255
+ handleServerExit(code, signal) {
256
+ this.removePidFile();
257
+ if (this.isShuttingDown) {
258
+ return;
259
+ }
260
+ const exitMessage = signal
261
+ ? `Server process killed with signal ${signal}`
262
+ : `Server process exited with code ${code}`;
263
+ if (code !== 0 && !this.options.quiet) {
264
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} ${exitMessage}`, 'error');
265
+ }
266
+ // Handle restart logic
267
+ if (this.options.restart && code !== 0 && this.restartCount < this.options.maxRestarts) {
268
+ this.scheduleRestart();
71
269
  }
72
- await this.startProcess();
270
+ else if (this.restartCount >= this.options.maxRestarts) {
271
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Maximum restart attempts reached (${this.options.maxRestarts})`, 'error');
272
+ process.exit(1);
273
+ }
274
+ else if (code !== 0) {
275
+ process.exit(code);
276
+ }
277
+ }
278
+ scheduleRestart() {
279
+ this.restartCount++;
280
+ const delay = this.options.restartDelay * Math.min(this.restartCount, 5); // Exponential backoff
73
281
  if (!this.options.quiet) {
74
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Application is running.`, 'info');
282
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Restarting server in ${delay}ms... (${this.restartCount}/${this.options.maxRestarts})`, 'info');
75
283
  }
284
+ this.restartTimer = setTimeout(() => {
285
+ this.restartTimer = null;
286
+ this.startServer().catch(error => {
287
+ logger_manager_js_1.loggerManager.printLine(`Restart failed: ${error.message}`, 'error');
288
+ this.handleServerExit(1);
289
+ });
290
+ }, delay);
76
291
  }
77
- async stop() {
78
- this.isStopping = true;
79
- const proc = this.process;
80
- if (!proc) {
292
+ setupWatcher() {
293
+ if (!this.options.watch)
294
+ return;
295
+ const watchPatterns = [
296
+ 'dist/**/*.js',
297
+ 'package.json',
298
+ 'dist/package.json'
299
+ ];
300
+ this.watcher = (0, chokidar_1.watch)(watchPatterns, {
301
+ ignoreInitial: true,
302
+ followSymlinks: false,
303
+ usePolling: false,
304
+ atomic: 500,
305
+ ignored: [
306
+ '**/node_modules/**',
307
+ '**/.git/**',
308
+ '**/*.log',
309
+ '**/*.map'
310
+ ]
311
+ });
312
+ this.watcher.on('change', (filePath) => {
313
+ if (this.options.verbose) {
314
+ logger_manager_js_1.loggerManager.printLine(`File changed: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
315
+ }
316
+ this.restart();
317
+ });
318
+ this.watcher.on('error', (error) => {
319
+ logger_manager_js_1.loggerManager.printLine(`Watcher error: ${error.message}`, 'error');
320
+ });
321
+ if (this.options.verbose) {
322
+ logger_manager_js_1.loggerManager.printLine(`Watching: ${watchPatterns.join(', ')}`, 'info');
323
+ }
324
+ }
325
+ async restart() {
326
+ if (this.isShuttingDown)
81
327
  return;
328
+ if (!this.options.quiet) {
329
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Restarting server...`, 'info');
82
330
  }
83
- this.process = null;
331
+ await this.stopServer();
332
+ // Small delay before restart
333
+ setTimeout(() => {
334
+ this.startServer().catch(error => {
335
+ logger_manager_js_1.loggerManager.printLine(`Restart failed: ${error.message}`, 'error');
336
+ process.exit(1);
337
+ });
338
+ }, 100);
339
+ }
340
+ async stopServer() {
341
+ if (!this.serverProcess)
342
+ return;
84
343
  return new Promise((resolve) => {
85
- const appName = this.options.name || path_1.default.basename(this.options.entry);
86
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Stopping application ${appName}...`, 'info');
87
- proc.on('exit', () => {
88
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.square)} Application stopped.`, 'info');
344
+ if (!this.serverProcess) {
89
345
  resolve();
90
- });
91
- proc.on('error', () => {
92
- // Handle errors during shutdown, e.g., if the process is already gone
93
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.square)} Application stopped.`, 'info');
346
+ return;
347
+ }
348
+ const proc = this.serverProcess;
349
+ this.serverProcess = null;
350
+ if (this.restartTimer) {
351
+ clearTimeout(this.restartTimer);
352
+ this.restartTimer = null;
353
+ }
354
+ const cleanup = () => {
355
+ this.removePidFile();
94
356
  resolve();
95
- });
357
+ };
358
+ proc.on('exit', cleanup);
359
+ proc.on('error', cleanup);
96
360
  try {
97
361
  if (proc.pid) {
98
- const pid = proc.pid;
99
- // Kill the entire process group
100
- process.kill(-pid, 'SIGTERM');
101
- // Set a timeout to force kill if it doesn't terminate gracefully
362
+ // Graceful shutdown
363
+ proc.kill('SIGTERM');
364
+ // Force kill after timeout
102
365
  setTimeout(() => {
103
- if (!proc.killed) {
366
+ if (proc.pid && !proc.killed) {
104
367
  try {
105
- process.kill(-pid, 'SIGKILL');
368
+ proc.kill('SIGKILL');
106
369
  }
107
370
  catch (e) {
108
- // Ignore errors if the process is already gone
371
+ // Ignore
109
372
  }
110
373
  }
111
- }, 5000).unref(); // .unref() allows the main process to exit if this is the only thing running
374
+ }, this.options.gracefulTimeout);
112
375
  }
113
376
  }
114
- catch (e) {
115
- // This can happen if the process is already dead
116
- resolve();
377
+ catch (error) {
378
+ cleanup();
117
379
  }
118
380
  });
119
381
  }
382
+ async start() {
383
+ try {
384
+ await this.startServer();
385
+ this.setupWatcher();
386
+ if (this.options.watch && !this.options.quiet) {
387
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Watching for changes...`, 'info');
388
+ }
389
+ }
390
+ catch (error) {
391
+ logger_manager_js_1.loggerManager.printLine(`Failed to start server: ${error.message}`, 'error');
392
+ process.exit(1);
393
+ }
394
+ }
395
+ async stop() {
396
+ this.isShuttingDown = true;
397
+ if (!this.options.quiet) {
398
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Stopping server...`, 'info');
399
+ }
400
+ if (this.watcher) {
401
+ await this.watcher.close();
402
+ this.watcher = null;
403
+ }
404
+ await this.stopServer();
405
+ if (!this.options.quiet) {
406
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server stopped successfully`, 'info');
407
+ }
408
+ }
120
409
  }
121
410
  exports.StartManager = StartManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.47",
3
+ "version": "0.6.50",
4
4
  "description": "The Modern Build System for Polyrepo-in-Monorepo Architecture",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",