neex 0.6.40 → 0.6.41

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.
@@ -68,6 +68,10 @@ class DevRunner {
68
68
  });
69
69
  }
70
70
  async handleFileChange(event) {
71
+ const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
72
+ if (this.options.showInfo) {
73
+ logger_1.default.printLine(`${prefix} File changed: ${chalk_1.default.yellow(event.relativePath)}`, 'info');
74
+ }
71
75
  if (this.options.clearConsole) {
72
76
  console.clear();
73
77
  }
@@ -98,6 +102,9 @@ class DevRunner {
98
102
  return results;
99
103
  }
100
104
  catch (error) {
105
+ if (this.options.showInfo) {
106
+ logger_1.default.printLine(`Execution failed: ${error.message}`, 'error');
107
+ }
101
108
  return [];
102
109
  }
103
110
  }
@@ -148,12 +155,16 @@ class DevRunner {
148
155
  await this.fileWatcher.start();
149
156
  }
150
157
  // Run initial commands
158
+ const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
159
+ if (this.options.showInfo) {
160
+ logger_1.default.printLine(`${prefix} Starting development server...`, 'info');
161
+ }
151
162
  await this.runCommands();
152
163
  // Set up graceful shutdown
153
164
  this.setupGracefulShutdown();
154
165
  if (this.options.showInfo) {
155
- logger_1.default.printLine(`Development server started. Watching for changes...`, 'info');
156
- logger_1.default.printLine(`Press ${chalk_1.default.cyan('Ctrl+C')} to stop`, 'info');
166
+ logger_1.default.printLine(`${prefix} Development server started. Watching for changes...`, 'info');
167
+ logger_1.default.printLine(`${prefix} Press ${chalk_1.default.cyan('Ctrl+C')} to stop`, 'info');
157
168
  }
158
169
  }
159
170
  async restart() {
@@ -161,6 +172,9 @@ class DevRunner {
161
172
  if (!this.isRunning) {
162
173
  return;
163
174
  }
175
+ if (this.options.showInfo) {
176
+ logger_1.default.printLine(`${prefix} Restarting due to file changes...`, 'info');
177
+ }
164
178
  this.restartCount++;
165
179
  // Stop current processes
166
180
  if (this.runner) {
@@ -172,6 +186,9 @@ class DevRunner {
172
186
  this.printDevBanner();
173
187
  // Run commands again
174
188
  await this.runCommands();
189
+ if (this.options.showInfo) {
190
+ logger_1.default.printLine(`${prefix} Restart completed. Watching for changes...`, 'info');
191
+ }
175
192
  }
176
193
  async stop() {
177
194
  const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
@@ -179,7 +196,7 @@ class DevRunner {
179
196
  return;
180
197
  }
181
198
  if (this.options.showInfo) {
182
- logger_1.default.printLine(`Stopping development server...`, 'info');
199
+ logger_1.default.printLine(`${prefix} Stopping development server...`, 'info');
183
200
  }
184
201
  this.isRunning = false;
185
202
  // Stop file watcher
@@ -193,9 +210,9 @@ class DevRunner {
193
210
  const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
194
211
  const uptimeStr = this.formatUptime(uptime);
195
212
  if (this.options.showInfo) {
196
- logger_1.default.printLine(`${this.options.runnerName} development server stopped after ${uptimeStr}`, 'info');
213
+ logger_1.default.printLine(`${prefix} ${this.options.runnerName} development server stopped after ${uptimeStr}`, 'info');
197
214
  if (this.restartCount > 0) {
198
- logger_1.default.printLine(`Total restarts: ${this.restartCount}`, 'info');
215
+ logger_1.default.printLine(`${prefix} Total restarts: ${this.restartCount}`, 'info');
199
216
  }
200
217
  }
201
218
  }
@@ -3,16 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ // src/logger.ts
6
7
  const chalk_1 = __importDefault(require("chalk"));
7
8
  const figures_1 = __importDefault(require("figures"));
9
+ const string_width_1 = __importDefault(require("string-width"));
10
+ const utils_1 = require("./utils");
8
11
  class Logger {
9
- printSummary(results) {
10
- throw new Error('Method not implemented.');
11
- }
12
12
  constructor() {
13
+ this.prefixLength = 0;
13
14
  this.outputBuffer = new Map();
14
15
  this.commandColors = new Map();
15
16
  this.startTimes = new Map();
17
+ this.spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
18
+ this.spinnerIndex = 0;
19
+ this.spinnerIntervals = new Map();
20
+ this.isSpinnerActive = false;
16
21
  }
17
22
  static getInstance() {
18
23
  if (!Logger.instance) {
@@ -20,28 +25,60 @@ class Logger {
20
25
  }
21
26
  return Logger.instance;
22
27
  }
28
+ getSpinnerFrame() {
29
+ const frame = this.spinnerFrames[this.spinnerIndex];
30
+ this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
31
+ return frame;
32
+ }
23
33
  showBanner() {
24
34
  console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Neex ') + '\n');
25
35
  }
26
36
  setCommands(commands) {
27
- // this.showBanner(); // Banner is now called manually where needed
37
+ // Clear any existing spinner intervals
38
+ this.stopAllSpinners();
39
+ // Show Neex banner
40
+ this.showBanner();
41
+ // Calculate prefix length for aligning output
42
+ this.prefixLength = Math.max(...commands.map(cmd => (0, string_width_1.default)(cmd))) + 3;
43
+ // Initialize buffers and colors for each command
28
44
  commands.forEach(cmd => {
29
45
  this.outputBuffer.set(cmd, []);
30
- if (!this.commandColors.has(cmd)) {
31
- this.commandColors.set(cmd, this.getRandomColor());
32
- }
46
+ this.commandColors.set(cmd, this.generateColor(cmd));
33
47
  });
48
+ // Log commands that will be executed
49
+ console.log(chalk_1.default.dim('» Commands to execute:'));
50
+ commands.forEach(cmd => {
51
+ const color = this.commandColors.get(cmd) || chalk_1.default.white;
52
+ console.log(chalk_1.default.dim(' ┌') + color(` ${cmd}`));
53
+ });
54
+ console.log(''); // Add a blank line after commands list
34
55
  }
35
- getRandomColor() {
56
+ generateColor(command) {
57
+ // Generate distinct colors for commands based on the command string
36
58
  const vibrantColors = [
37
- '#FF5733', '#33FF57', '#3357FF', '#FF33A1', '#A133FF',
38
- '#33FFA1', '#FFC300', '#C70039', '#900C3F', '#581845'
59
+ '#00BFFF',
60
+ '#32CD32',
61
+ '#FF6347',
62
+ '#9370DB',
63
+ '#FF8C00',
64
+ '#20B2AA',
65
+ '#0066FF',
66
+ '#4169E1',
67
+ '#FFD700',
68
+ '#8A2BE2' // Blue Violet
39
69
  ];
40
- const colorIndex = Math.floor(Math.random() * vibrantColors.length);
70
+ let hash = 0;
71
+ for (let i = 0; i < command.length; i++) {
72
+ hash = (hash << 5) - hash + command.charCodeAt(i);
73
+ hash |= 0; // Convert to 32bit integer
74
+ }
75
+ const colorIndex = Math.abs(hash) % vibrantColors.length;
41
76
  return chalk_1.default.hex(vibrantColors[colorIndex]);
42
77
  }
43
78
  formatPrefix(command) {
44
- return '';
79
+ const color = this.commandColors.get(command) || chalk_1.default.white;
80
+ const prefix = `${command}:`.padEnd(this.prefixLength);
81
+ return color(prefix);
45
82
  }
46
83
  bufferOutput(output) {
47
84
  const currentBuffer = this.outputBuffer.get(output.command) || [];
@@ -49,92 +86,186 @@ class Logger {
49
86
  this.outputBuffer.set(output.command, currentBuffer);
50
87
  }
51
88
  printBuffer(command) {
52
- this.flushBuffer(command);
89
+ const buffer = this.outputBuffer.get(command) || [];
90
+ const color = this.commandColors.get(command) || chalk_1.default.white;
91
+ // Stop spinner for this command if running
92
+ this.stopSpinner(command);
93
+ buffer.forEach(output => {
94
+ const prefix = this.formatPrefix(output.command);
95
+ const content = output.data.trim();
96
+ if (content) {
97
+ const lines = content.split('\n');
98
+ lines.forEach(line => {
99
+ if (line.trim()) {
100
+ const outputLine = `${prefix} ${line}`;
101
+ // Show stderr in appropriate colors
102
+ if (output.type === 'stderr') {
103
+ // Not all stderr is an error, check for warning or info patterns
104
+ if (line.toLowerCase().includes('warn') || line.toLowerCase().includes('warning')) {
105
+ console.log(`${prefix} ${chalk_1.default.yellow(line)}`);
106
+ }
107
+ else if (line.toLowerCase().includes('error')) {
108
+ console.log(`${prefix} ${chalk_1.default.red(line)}`);
109
+ }
110
+ else {
111
+ console.log(`${prefix} ${line}`);
112
+ }
113
+ }
114
+ else {
115
+ console.log(outputLine);
116
+ }
117
+ }
118
+ });
119
+ }
120
+ });
121
+ // Clear buffer after printing
122
+ this.outputBuffer.set(command, []);
53
123
  }
54
124
  clearBuffer(command) {
55
125
  this.outputBuffer.set(command, []);
56
126
  }
57
- flushBuffer(command) {
58
- const buffer = this.outputBuffer.get(command);
59
- if (buffer) {
60
- buffer.forEach(output => {
61
- const prefix = this.formatPrefix(output.command);
62
- const color = this.commandColors.get(output.command) || chalk_1.default.white;
63
- const formattedOutput = output.data
64
- .split('\n')
65
- .filter(line => line.trim() !== '')
66
- .map(line => `${prefix}${color(line)}`)
67
- .join('\n');
68
- if (formattedOutput) {
69
- if (output.type === 'stderr') {
70
- console.error(formattedOutput);
71
- }
72
- else {
73
- console.log(formattedOutput);
74
- }
75
- }
76
- });
77
- this.outputBuffer.set(command, []);
78
- }
79
- }
80
127
  printLine(message, level = 'info') {
81
- // This is intentionally left blank to suppress verbose logs
82
- }
83
- printEnd(result, minimalOutput) {
84
- this.printExit(result.command, result.code);
128
+ if (level === 'error') {
129
+ console.error(chalk_1.default.red(`${figures_1.default.cross} ${message}`));
130
+ }
131
+ else if (level === 'warn') {
132
+ console.warn(chalk_1.default.yellow(`${figures_1.default.warning} ${message}`));
133
+ }
134
+ else {
135
+ console.log(chalk_1.default.blue(`${figures_1.default.info} ${message}`));
136
+ }
85
137
  }
86
138
  printStart(command) {
139
+ // Record start time
87
140
  this.startTimes.set(command, new Date());
141
+ const prefix = this.formatPrefix(command);
88
142
  const color = this.commandColors.get(command) || chalk_1.default.white;
89
- console.log(`${color('Starting...')}`);
143
+ // Stop any previous spinner for this command (e.g. if retrying)
144
+ this.stopSpinner(command);
145
+ // Clear the line before printing "Starting..."
146
+ if (this.isSpinnerActive) { // Check if any spinner was active to avoid clearing unnecessarily
147
+ process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
148
+ }
149
+ console.log(`${prefix} ${color('Starting...')}`);
150
+ // Start spinner for this command
151
+ this.startSpinner(command);
152
+ }
153
+ startSpinner(command) {
154
+ // Only create a spinner if one doesn't already exist for this command
155
+ if (this.spinnerIntervals.has(command)) {
156
+ return;
157
+ }
158
+ this.isSpinnerActive = true;
159
+ const color = this.commandColors.get(command) || chalk_1.default.white;
160
+ const prefix = this.formatPrefix(command);
161
+ const interval = setInterval(() => {
162
+ const frame = this.getSpinnerFrame();
163
+ process.stdout.write(`\r${prefix} ${color(frame)} ${chalk_1.default.dim('Running...')}`);
164
+ }, 80);
165
+ this.spinnerIntervals.set(command, interval);
166
+ }
167
+ stopSpinner(command) {
168
+ const interval = this.spinnerIntervals.get(command);
169
+ if (interval) {
170
+ clearInterval(interval);
171
+ this.spinnerIntervals.delete(command);
172
+ // Clear the spinner line
173
+ if (this.isSpinnerActive) {
174
+ process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
175
+ }
176
+ }
177
+ }
178
+ stopAllSpinners() {
179
+ this.spinnerIntervals.forEach((interval, command) => {
180
+ clearInterval(interval);
181
+ });
182
+ this.spinnerIntervals.clear();
183
+ this.isSpinnerActive = false;
184
+ // Clear the spinner line if any spinner was active
185
+ if (this.isSpinnerActive) {
186
+ process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
187
+ }
90
188
  }
91
189
  printSuccess(result) {
92
190
  const { command, duration } = result;
93
- const color = this.commandColors.get(command) || chalk_1.default.green;
191
+ this.stopSpinner(command);
94
192
  const prefix = this.formatPrefix(command);
95
- console.log(color(`${prefix}${figures_1.default.tick} Success! (${duration.toFixed(2)}ms)`));
193
+ const color = this.commandColors.get(command) || chalk_1.default.white;
194
+ const durationStr = duration
195
+ ? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}`
196
+ : '';
197
+ console.log(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} ${chalk_1.default.green('Completed')}${durationStr}`);
96
198
  }
97
199
  printError(result) {
98
- const { command, duration, code } = result;
99
- const color = this.commandColors.get(command) || chalk_1.default.red;
200
+ const { command, error, code, duration } = result;
201
+ this.stopSpinner(command);
100
202
  const prefix = this.formatPrefix(command);
101
- console.error(color(`${prefix}${figures_1.default.cross} Failed with exit code ${code} (${duration.toFixed(2)}ms)`));
102
- }
103
- printExit(command, code) {
104
- const startTime = this.startTimes.get(command);
105
- const duration = startTime ? new Date().getTime() - startTime.getTime() : 0;
106
- const result = { command, code: code !== null && code !== void 0 ? code : 1, duration };
107
- this.flushBuffer(command);
108
- if (code === 0) {
109
- this.printSuccess(result);
203
+ const durationStr = duration ? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}` : '';
204
+ const errorCode = code !== null ? ` ${chalk_1.default.red(`[code: ${code}]`)}` : '';
205
+ console.error(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${chalk_1.default.red('Failed')}${errorCode}${durationStr}`);
206
+ if (error) {
207
+ console.error(`${prefix} ${chalk_1.default.red(error.message)}`);
208
+ }
209
+ }
210
+ printEnd(result, minimalOutput) {
211
+ this.stopSpinner(result.command);
212
+ const prefix = this.formatPrefix(result.command); // Corrected to formatPrefix
213
+ let durationDisplay = '';
214
+ if (result.duration !== null) {
215
+ // Ensure result.duration is treated as a number here
216
+ durationDisplay = `(${(0, utils_1.formatDuration)(result.duration)})`;
217
+ }
218
+ const duration = durationDisplay;
219
+ if (minimalOutput) {
220
+ if (!result.success) {
221
+ const status = result.code !== null ? `failed (code ${result.code})` : 'failed';
222
+ this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${result.command} ${status} ${duration}`, 'error');
223
+ }
110
224
  }
111
225
  else {
112
- this.printError(result);
226
+ if (result.success) {
227
+ this.printLine(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} Command "${result.command}" finished successfully ${duration}`, 'info');
228
+ }
229
+ else {
230
+ const errorCode = result.code !== null ? ` (code ${result.code})` : '';
231
+ const errorMessage = result.error ? `: ${result.error.message}` : '';
232
+ this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} Command "${result.command}" failed${errorCode}${errorMessage} ${duration}`, 'error');
233
+ }
113
234
  }
114
235
  }
115
- printBulkOutput(outputs) {
116
- if (outputs.length > 0) {
117
- outputs
118
- .map(output => {
119
- const color = this.commandColors.get(output.command) || chalk_1.default.white;
120
- const prefix = this.formatPrefix(output.command);
121
- return {
122
- ...output,
123
- prefix,
124
- color
125
- };
126
- })
127
- .forEach(({ command, type, data, prefix, color }) => {
128
- const lines = data.split('\n').filter(line => line.trim() !== '');
129
- if (lines.length > 0) {
130
- const formatted = lines.map(line => `${prefix}${color(line)}`).join('\n');
131
- if (type === 'stderr') {
132
- console.error(formatted);
133
- }
134
- else {
135
- console.log(formatted);
136
- }
137
- }
236
+ printSummary(results) {
237
+ // Stop any remaining spinners
238
+ this.stopAllSpinners();
239
+ const successful = results.filter(r => r.success).length;
240
+ const failed = results.length - successful;
241
+ const totalDuration = results.reduce((sum, result) => sum + (result.duration || 0), 0);
242
+ const totalSeconds = (totalDuration / 1000).toFixed(2);
243
+ console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Execution Summary ') + '\n');
244
+ console.log(`${chalk_1.default.green(`${figures_1.default.tick} ${successful} succeeded`)}, ${chalk_1.default.red(`${figures_1.default.cross} ${failed} failed`)}`);
245
+ console.log(`${chalk_1.default.blue(figures_1.default.info)} ${chalk_1.default.dim(`Total execution time: ${totalSeconds}s`)}`);
246
+ if (successful > 0) {
247
+ console.log('\n' + chalk_1.default.green.bold('Successful commands:'));
248
+ results
249
+ .filter(r => r.success)
250
+ .forEach(result => {
251
+ const color = this.commandColors.get(result.command) || chalk_1.default.white;
252
+ const duration = result.duration
253
+ ? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
254
+ : '';
255
+ console.log(` ${chalk_1.default.green(figures_1.default.tick)} ${color(result.command)}${duration}`);
256
+ });
257
+ }
258
+ if (failed > 0) {
259
+ console.log('\n' + chalk_1.default.red.bold('Failed commands:'));
260
+ results
261
+ .filter(r => !r.success)
262
+ .forEach(result => {
263
+ const color = this.commandColors.get(result.command) || chalk_1.default.white;
264
+ const duration = result.duration
265
+ ? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
266
+ : '';
267
+ const code = result.code !== null ? chalk_1.default.red(` [code: ${result.code}]`) : '';
268
+ console.log(` ${chalk_1.default.red(figures_1.default.cross)} ${color(result.command)}${code}${duration}`);
138
269
  });
139
270
  }
140
271
  }
@@ -27,9 +27,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Runner = void 0;
30
+ // src/runner.ts - Updated version
30
31
  const child_process_1 = require("child_process");
31
32
  const fsPromises = __importStar(require("fs/promises"));
32
33
  const path = __importStar(require("path"));
34
+ const chalk_1 = __importDefault(require("chalk"));
33
35
  const logger_1 = __importDefault(require("./logger"));
34
36
  const p_map_1 = __importDefault(require("p-map"));
35
37
  const npm_run_path_1 = __importDefault(require("npm-run-path"));
@@ -54,6 +56,7 @@ class Runner {
54
56
  }
55
57
  }
56
58
  catch (error) {
59
+ logger_1.default.printLine(`Could not read or parse package.json: ${error.message}`, 'warn');
57
60
  packageJson = { scripts: {} };
58
61
  }
59
62
  for (const command of commands) {
@@ -62,12 +65,13 @@ class Runner {
62
65
  let foundMatch = false;
63
66
  for (const scriptName in packageJson.scripts) {
64
67
  if (pattern.test(scriptName)) {
65
- expandedCommands.push(scriptName);
68
+ expandedCommands.push(scriptName); // Or packageJson.scripts[scriptName] if you want the script value
66
69
  foundMatch = true;
67
70
  }
68
71
  }
69
72
  if (!foundMatch) {
70
- expandedCommands.push(command);
73
+ logger_1.default.printLine(`No scripts found in package.json matching wildcard: ${command}`, 'warn');
74
+ expandedCommands.push(command); // Add original command if no match
71
75
  }
72
76
  }
73
77
  else {
@@ -91,18 +95,21 @@ class Runner {
91
95
  return { executableCommand: commandToExecute, executionCwd: targetCwd };
92
96
  }
93
97
  else {
98
+ // It's a script from package.json, but no 'cd ... && ...' pattern
94
99
  return { executableCommand: scriptValue, executionCwd: baseDir };
95
100
  }
96
101
  }
97
102
  }
98
103
  catch (error) {
99
- // Errors will treat as direct command
104
+ // Errors like package.json not found, or script not in package.json
105
+ // Will treat as direct command
100
106
  }
101
107
  return { executableCommand: scriptNameOrCommand, executionCwd: undefined };
102
108
  }
103
109
  detectServerInfo(command, data) {
104
110
  if (!this.options.isServerMode)
105
111
  return;
112
+ // Get or create server info
106
113
  let serverInfo = this.serverInfo.get(command);
107
114
  if (!serverInfo) {
108
115
  serverInfo = {
@@ -111,16 +118,25 @@ class Runner {
111
118
  };
112
119
  this.serverInfo.set(command, serverInfo);
113
120
  }
121
+ // Try to detect port from output
114
122
  const portMatch = data.match(this.portRegex);
115
123
  if (portMatch && portMatch[1]) {
116
124
  serverInfo.port = parseInt(portMatch[1], 10);
117
125
  serverInfo.status = 'running';
126
+ // Only log if we just discovered the port
127
+ if (!serverInfo.url) {
128
+ logger_1.default.printLine(`Server ${command} running on port ${serverInfo.port}`, 'info');
129
+ }
118
130
  }
131
+ // Try to detect full URL from output
119
132
  const urlMatch = data.match(this.urlRegex);
120
133
  if (urlMatch && urlMatch[1]) {
121
134
  serverInfo.url = urlMatch[1];
122
135
  serverInfo.status = 'running';
136
+ // Log the full URL once we detect it
137
+ logger_1.default.printLine(`Server ${command} available at ${chalk_1.default.cyan(serverInfo.url)}`, 'info');
123
138
  }
139
+ // Update server info
124
140
  this.serverInfo.set(command, serverInfo);
125
141
  }
126
142
  async runCommand(originalCommand, currentRetry = 0) {
@@ -132,7 +148,6 @@ class Runner {
132
148
  code: null,
133
149
  startTime,
134
150
  endTime: null,
135
- duration: 0,
136
151
  output: [],
137
152
  stderr: []
138
153
  };
@@ -203,11 +218,13 @@ class Runner {
203
218
  serverInfo.status = 'error';
204
219
  this.serverInfo.set(originalCommand, serverInfo);
205
220
  }
221
+ logger_1.default.printLine(`Command "${originalCommand}" failed to start: ${err.message}`, 'error');
206
222
  }
207
223
  logger_1.default.printBuffer(originalCommand);
208
224
  if (this.options.printOutput)
209
225
  logger_1.default.printEnd(result, this.options.minimalOutput);
210
226
  if (this.options.retry && this.options.retry > 0 && currentRetry < this.options.retry) {
227
+ logger_1.default.printLine(`Command "${originalCommand}" failed with error. Retrying (${currentRetry + 1}/${this.options.retry})...`, 'warn');
211
228
  if (this.options.retryDelay && this.options.retryDelay > 0) {
212
229
  await new Promise(res => setTimeout(res, this.options.retryDelay));
213
230
  }
@@ -223,17 +240,22 @@ class Runner {
223
240
  result.success = code === 0;
224
241
  result.endTime = new Date();
225
242
  result.duration = result.endTime.getTime() - startTime.getTime();
243
+ this.activeProcesses.delete(originalCommand);
226
244
  if (this.options.isServerMode) {
227
245
  const serverInfo = this.serverInfo.get(originalCommand);
228
246
  if (serverInfo) {
229
247
  serverInfo.status = code === 0 ? 'stopped' : 'error';
230
248
  this.serverInfo.set(originalCommand, serverInfo);
231
249
  }
250
+ if (code !== 0) {
251
+ logger_1.default.printLine(`Server "${originalCommand}" exited with code ${code}`, 'error');
252
+ }
232
253
  }
233
254
  logger_1.default.printBuffer(originalCommand);
234
255
  if (this.options.printOutput)
235
256
  logger_1.default.printEnd(result, this.options.minimalOutput);
236
257
  if (!result.success && this.options.retry && this.options.retry > 0 && currentRetry < this.options.retry) {
258
+ logger_1.default.printLine(`Command "${originalCommand}" failed with code ${code}. Retrying (${currentRetry + 1}/${this.options.retry})...`, 'warn');
237
259
  if (this.options.retryDelay && this.options.retryDelay > 0) {
238
260
  await new Promise(res => setTimeout(res, this.options.retryDelay));
239
261
  }
@@ -269,39 +291,65 @@ class Runner {
269
291
  });
270
292
  }
271
293
  catch (error) {
294
+ if (this.options.isServerMode) {
295
+ logger_1.default.printLine('One or more servers failed to start. Stopping all servers.', 'error');
296
+ }
272
297
  return [];
273
298
  }
274
299
  }
275
300
  async run(initialCommands) {
276
301
  const commands = await this.expandWildcardCommands(initialCommands);
277
302
  if (commands.length === 0) {
303
+ logger_1.default.printLine('No commands to run after wildcard expansion.', 'warn');
278
304
  return [];
279
305
  }
306
+ // Initialize logger with the final list of commands
280
307
  logger_1.default.setCommands(commands);
308
+ // Run in parallel or sequential mode
281
309
  if (this.options.parallel) {
310
+ if (this.options.isServerMode) {
311
+ logger_1.default.printLine('Starting servers in parallel mode', 'info');
312
+ }
282
313
  return this.runParallel(commands);
283
314
  }
284
315
  else {
316
+ if (this.options.isServerMode) {
317
+ logger_1.default.printLine('Starting servers in sequential mode', 'info');
318
+ }
285
319
  return this.runSequential(commands);
286
320
  }
287
321
  }
288
322
  cleanup(signal = 'SIGTERM') {
323
+ logger_1.default.printLine('Cleaning up child processes...', 'warn');
289
324
  this.activeProcesses.forEach((proc, command) => {
290
325
  if (proc.pid && !proc.killed) {
291
326
  try {
327
+ // Kill process group
292
328
  process.kill(-proc.pid, signal);
329
+ logger_1.default.printLine(`Sent ${signal} to process group ${proc.pid} (${command})`, 'info');
293
330
  }
294
331
  catch (e) {
332
+ // Fallback if killing group failed
295
333
  try {
296
334
  proc.kill(signal);
335
+ logger_1.default.printLine(`Sent ${signal} to process ${proc.pid} (${command})`, 'info');
297
336
  }
298
337
  catch (errInner) {
299
- // Failed to kill
338
+ logger_1.default.printLine(`Failed to kill process ${proc.pid} (${command}): ${errInner.message}`, 'error');
300
339
  }
301
340
  }
302
341
  }
303
342
  });
304
343
  this.activeProcesses.clear();
344
+ // Print server status summary if in server mode
345
+ if (this.options.isServerMode && this.serverInfo.size > 0) {
346
+ logger_1.default.printLine('Server shutdown summary:', 'info');
347
+ this.serverInfo.forEach((info, command) => {
348
+ const statusColor = info.status === 'running' ? chalk_1.default.green :
349
+ info.status === 'error' ? chalk_1.default.red : chalk_1.default.yellow;
350
+ logger_1.default.printLine(` ${command}: ${statusColor(info.status)}`, 'info');
351
+ });
352
+ }
305
353
  }
306
354
  }
307
355
  exports.Runner = Runner;
@@ -22,12 +22,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.FileWatcher = void 0;
27
30
  // src/watcher.ts - File watcher for development (nodemon functionality)
28
31
  const fs = __importStar(require("fs"));
29
32
  const path = __importStar(require("path"));
30
33
  const events_1 = require("events");
34
+ const chalk_1 = __importDefault(require("chalk"));
35
+ const logger_1 = __importDefault(require("./logger"));
31
36
  class FileWatcher extends events_1.EventEmitter {
32
37
  constructor(options) {
33
38
  super();
@@ -116,7 +121,7 @@ class FileWatcher extends events_1.EventEmitter {
116
121
  try {
117
122
  const absolutePath = path.resolve(dirPath);
118
123
  if (this.options.verbose) {
119
- // logger.printLine(`Watching directory: ${chalk.cyan(absolutePath)}`, 'info');
124
+ logger_1.default.printLine(`Watching directory: ${chalk_1.default.cyan(absolutePath)}`, 'info');
120
125
  }
121
126
  const watcher = fs.watch(absolutePath, { recursive: true }, async (eventType, filename) => {
122
127
  if (!filename)
@@ -134,7 +139,7 @@ class FileWatcher extends events_1.EventEmitter {
134
139
  }
135
140
  catch (error) {
136
141
  if (this.options.verbose) {
137
- // logger.printLine(`Failed to watch directory ${dirPath}: ${(error as Error).message}`, 'warn');
142
+ logger_1.default.printLine(`Failed to watch directory ${dirPath}: ${error.message}`, 'warn');
138
143
  }
139
144
  }
140
145
  }
@@ -148,7 +153,7 @@ class FileWatcher extends events_1.EventEmitter {
148
153
  return;
149
154
  }
150
155
  if (this.options.verbose) {
151
- // logger.printLine(`Watching file: ${chalk.cyan(absolutePath)}`, 'info');
156
+ logger_1.default.printLine(`Watching file: ${chalk_1.default.cyan(absolutePath)}`, 'info');
152
157
  }
153
158
  const watcher = fs.watch(absolutePath, (eventType) => {
154
159
  this.handleFileChange(absolutePath, eventType);
@@ -158,13 +163,13 @@ class FileWatcher extends events_1.EventEmitter {
158
163
  }
159
164
  catch (error) {
160
165
  if (this.options.verbose) {
161
- // logger.printLine(`Failed to watch file ${filePath}: ${(error as Error).message}`, 'warn');
166
+ logger_1.default.printLine(`Failed to watch file ${filePath}: ${error.message}`, 'warn');
162
167
  }
163
168
  }
164
169
  }
165
170
  handleFileChange(filePath, eventType) {
166
171
  if (this.options.verbose) {
167
- // logger.printLine(`File ${eventType}: ${chalk.yellow(path.relative(process.cwd(), filePath))}`, 'info');
172
+ logger_1.default.printLine(`File ${eventType}: ${chalk_1.default.yellow(path.relative(process.cwd(), filePath))}`, 'info');
168
173
  }
169
174
  // Debounce file changes
170
175
  if (this.debounceTimer) {
@@ -183,6 +188,7 @@ class FileWatcher extends events_1.EventEmitter {
183
188
  return;
184
189
  }
185
190
  this.isWatching = true;
191
+ logger_1.default.printLine('Starting file watcher...', 'info');
186
192
  for (const watchPath of this.options.watch) {
187
193
  const absolutePath = path.resolve(watchPath);
188
194
  try {
@@ -195,17 +201,20 @@ class FileWatcher extends events_1.EventEmitter {
195
201
  }
196
202
  }
197
203
  catch (error) {
198
- // logger.printLine(`Cannot watch ${watchPath}: ${(error as Error).message}`, 'warn');
204
+ logger_1.default.printLine(`Cannot watch ${watchPath}: ${error.message}`, 'warn');
199
205
  }
200
206
  }
201
207
  const watchedCount = this.watchers.length;
208
+ logger_1.default.printLine(`File watcher started. Monitoring ${chalk_1.default.green(watchedCount)} locations`, 'info');
202
209
  if (this.options.ext && this.options.ext.length > 0) {
210
+ logger_1.default.printLine(`Watching extensions: ${chalk_1.default.cyan(this.options.ext.join(', '))}`, 'info');
203
211
  }
204
212
  }
205
213
  stop() {
206
214
  if (!this.isWatching) {
207
215
  return;
208
216
  }
217
+ logger_1.default.printLine('Stopping file watcher...', 'info');
209
218
  this.watchers.forEach(watcher => {
210
219
  try {
211
220
  watcher.close();
@@ -221,6 +230,7 @@ class FileWatcher extends events_1.EventEmitter {
221
230
  clearTimeout(this.debounceTimer);
222
231
  this.debounceTimer = null;
223
232
  }
233
+ logger_1.default.printLine('File watcher stopped', 'info');
224
234
  }
225
235
  isActive() {
226
236
  return this.isWatching;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.40",
3
+ "version": "0.6.41",
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",