neex 0.7.11 → 0.7.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  </picture>
8
8
  </a>
9
9
 
10
- # Neex v0.7.11
10
+ # Neex
11
11
 
12
12
  ### Neex - Modern Fullstack Framework Built on Express and Next.js. Fast to Start, Easy to Build, Ready to Deploy.
13
13
 
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.Runner = void 0;
40
- // src/runner.ts - Updated version
40
+ // src/runner.ts - Fixed version
41
41
  const child_process_1 = require("child_process");
42
42
  const fsPromises = __importStar(require("fs/promises"));
43
43
  const path = __importStar(require("path"));
@@ -52,8 +52,29 @@ class Runner {
52
52
  this.serverInfo = new Map();
53
53
  this.portRegex = /listening on (?:port |http:\/\/localhost:|https:\/\/localhost:)(\d+)/i;
54
54
  this.urlRegex = /(https?:\/\/localhost:[0-9]+(?:\/[^\s]*)?)/i;
55
+ this.isCleaningUp = false;
55
56
  this.options = options;
56
57
  this.activeProcesses = new Map();
58
+ this.setupSignalHandlers();
59
+ }
60
+ setupSignalHandlers() {
61
+ const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
62
+ signals.forEach((signal) => {
63
+ process.on(signal, () => {
64
+ if (!this.isCleaningUp) {
65
+ this.isCleaningUp = true;
66
+ logger_1.default.printLine(`\nReceived ${signal}. Cleaning up...`, 'warn');
67
+ this.cleanup(signal);
68
+ process.exit(0);
69
+ }
70
+ });
71
+ });
72
+ // Handle unexpected exits
73
+ process.on('beforeExit', () => {
74
+ if (!this.isCleaningUp) {
75
+ this.cleanup();
76
+ }
77
+ });
57
78
  }
58
79
  async expandWildcardCommands(commands) {
59
80
  const expandedCommands = [];
@@ -75,13 +96,13 @@ class Runner {
75
96
  let foundMatch = false;
76
97
  for (const scriptName in packageJson.scripts) {
77
98
  if (pattern.test(scriptName)) {
78
- expandedCommands.push(scriptName); // Or packageJson.scripts[scriptName] if you want the script value
99
+ expandedCommands.push(scriptName);
79
100
  foundMatch = true;
80
101
  }
81
102
  }
82
103
  if (!foundMatch) {
83
104
  logger_1.default.printLine(`No scripts found in package.json matching wildcard: ${command}`, 'warn');
84
- expandedCommands.push(command); // Add original command if no match
105
+ expandedCommands.push(command);
85
106
  }
86
107
  }
87
108
  else {
@@ -105,13 +126,11 @@ class Runner {
105
126
  return { executableCommand: commandToExecute, executionCwd: targetCwd };
106
127
  }
107
128
  else {
108
- // It's a script from package.json, but no 'cd ... && ...' pattern
109
129
  return { executableCommand: scriptValue, executionCwd: baseDir };
110
130
  }
111
131
  }
112
132
  }
113
133
  catch (error) {
114
- // Errors like package.json not found, or script not in package.json
115
134
  // Will treat as direct command
116
135
  }
117
136
  return { executableCommand: scriptNameOrCommand, executionCwd: undefined };
@@ -119,7 +138,6 @@ class Runner {
119
138
  detectServerInfo(command, data) {
120
139
  if (!this.options.isServerMode)
121
140
  return;
122
- // Get or create server info
123
141
  let serverInfo = this.serverInfo.get(command);
124
142
  if (!serverInfo) {
125
143
  serverInfo = {
@@ -128,25 +146,20 @@ class Runner {
128
146
  };
129
147
  this.serverInfo.set(command, serverInfo);
130
148
  }
131
- // Try to detect port from output
132
149
  const portMatch = data.match(this.portRegex);
133
150
  if (portMatch && portMatch[1]) {
134
151
  serverInfo.port = parseInt(portMatch[1], 10);
135
152
  serverInfo.status = 'running';
136
- // Only log if we just discovered the port
137
153
  if (!serverInfo.url) {
138
154
  logger_1.default.printLine(`Server ${command} running on port ${serverInfo.port}`, 'info');
139
155
  }
140
156
  }
141
- // Try to detect full URL from output
142
157
  const urlMatch = data.match(this.urlRegex);
143
158
  if (urlMatch && urlMatch[1]) {
144
159
  serverInfo.url = urlMatch[1];
145
160
  serverInfo.status = 'running';
146
- // Log the full URL once we detect it
147
161
  logger_1.default.printLine(`Server ${command} available at ${chalk_1.default.cyan(serverInfo.url)}`, 'info');
148
162
  }
149
- // Update server info
150
163
  this.serverInfo.set(command, serverInfo);
151
164
  }
152
165
  async runCommand(originalCommand, currentRetry = 0) {
@@ -172,11 +185,11 @@ class Runner {
172
185
  ...(0, npm_run_path_1.npmRunPathEnv)(),
173
186
  FORCE_COLOR: this.options.color ? '1' : '0'
174
187
  };
188
+ // Fix: Remove detached and handle process groups properly
175
189
  const proc = (0, child_process_1.spawn)(cmd, args, {
176
190
  stdio: ['ignore', 'pipe', 'pipe'],
177
191
  shell: true,
178
192
  env,
179
- detached: true,
180
193
  cwd
181
194
  });
182
195
  this.activeProcesses.set(originalCommand, proc);
@@ -313,9 +326,7 @@ class Runner {
313
326
  logger_1.default.printLine('No commands to run after wildcard expansion.', 'warn');
314
327
  return [];
315
328
  }
316
- // Initialize logger with the final list of commands
317
329
  logger_1.default.setCommands(commands);
318
- // Run in parallel or sequential mode
319
330
  if (this.options.parallel) {
320
331
  if (this.options.isServerMode) {
321
332
  logger_1.default.printLine('Starting servers in parallel mode', 'info');
@@ -330,36 +341,61 @@ class Runner {
330
341
  }
331
342
  }
332
343
  cleanup(signal = 'SIGTERM') {
344
+ if (this.isCleaningUp)
345
+ return;
346
+ this.isCleaningUp = true;
333
347
  logger_1.default.printLine('Cleaning up child processes...', 'warn');
348
+ const promises = [];
334
349
  this.activeProcesses.forEach((proc, command) => {
335
350
  if (proc.pid && !proc.killed) {
336
- try {
337
- // Kill process group
338
- process.kill(-proc.pid, signal);
339
- logger_1.default.printLine(`Sent ${signal} to process group ${proc.pid} (${command})`, 'info');
340
- }
341
- catch (e) {
342
- // Fallback if killing group failed
351
+ promises.push(this.killProcess(proc, command, signal));
352
+ }
353
+ });
354
+ // Wait for all processes to be killed
355
+ Promise.all(promises).then(() => {
356
+ this.activeProcesses.clear();
357
+ if (this.options.isServerMode && this.serverInfo.size > 0) {
358
+ logger_1.default.printLine('Server shutdown summary:', 'info');
359
+ this.serverInfo.forEach((info, command) => {
360
+ const statusColor = info.status === 'running' ? chalk_1.default.green :
361
+ info.status === 'error' ? chalk_1.default.red : chalk_1.default.yellow;
362
+ logger_1.default.printLine(` ${command}: ${statusColor(info.status)}`, 'info');
363
+ });
364
+ }
365
+ }).catch((error) => {
366
+ logger_1.default.printLine(`Error during cleanup: ${error.message}`, 'error');
367
+ });
368
+ }
369
+ async killProcess(proc, command, signal) {
370
+ return new Promise((resolve) => {
371
+ const timeout = setTimeout(() => {
372
+ if (proc.pid && !proc.killed) {
343
373
  try {
344
- proc.kill(signal);
345
- logger_1.default.printLine(`Sent ${signal} to process ${proc.pid} (${command})`, 'info');
374
+ proc.kill('SIGKILL');
375
+ logger_1.default.printLine(`Force killed process ${proc.pid} (${command}) with SIGKILL`, 'warn');
346
376
  }
347
- catch (errInner) {
348
- logger_1.default.printLine(`Failed to kill process ${proc.pid} (${command}): ${errInner.message}`, 'error');
377
+ catch (error) {
378
+ logger_1.default.printLine(`Failed to force kill process ${proc.pid} (${command}): ${error.message}`, 'error');
349
379
  }
350
380
  }
381
+ resolve();
382
+ }, 5000); // 5 second timeout
383
+ proc.on('exit', () => {
384
+ clearTimeout(timeout);
385
+ logger_1.default.printLine(`Process ${proc.pid} (${command}) exited gracefully`, 'info');
386
+ resolve();
387
+ });
388
+ try {
389
+ // Try to kill the process gracefully first
390
+ proc.kill(signal);
391
+ logger_1.default.printLine(`Sent ${signal} to process ${proc.pid} (${command})`, 'info');
392
+ }
393
+ catch (error) {
394
+ logger_1.default.printLine(`Failed to send ${signal} to process ${proc.pid} (${command}): ${error.message}`, 'error');
395
+ clearTimeout(timeout);
396
+ resolve();
351
397
  }
352
398
  });
353
- this.activeProcesses.clear();
354
- // Print server status summary if in server mode
355
- if (this.options.isServerMode && this.serverInfo.size > 0) {
356
- logger_1.default.printLine('Server shutdown summary:', 'info');
357
- this.serverInfo.forEach((info, command) => {
358
- const statusColor = info.status === 'running' ? chalk_1.default.green :
359
- info.status === 'error' ? chalk_1.default.red : chalk_1.default.yellow;
360
- logger_1.default.printLine(` ${command}: ${statusColor(info.status)}`, 'info');
361
- });
362
- }
363
399
  }
364
400
  }
365
401
  exports.Runner = Runner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.7.11",
3
+ "version": "0.7.15",
4
4
  "description": "A command-line interface for managing Neex projects, designed to simplify your development workflow.",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",