neex 0.1.3 → 0.1.5

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.
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DevRunner = void 0;
7
+ // src/dev-runner.ts - Development runner with file watching (nodemon functionality)
8
+ const watcher_1 = require("./watcher");
9
+ const runner_1 = require("./runner");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const figures_1 = __importDefault(require("figures"));
12
+ const logger_1 = __importDefault(require("./logger"));
13
+ class DevRunner {
14
+ constructor(options) {
15
+ this.commands = [];
16
+ this.isRunning = false;
17
+ this.restartCount = 0;
18
+ this.startTime = new Date();
19
+ const defaultOptions = {
20
+ parallel: false,
21
+ printOutput: true,
22
+ color: true,
23
+ showTiming: true,
24
+ prefix: true,
25
+ stopOnError: false,
26
+ minimalOutput: false,
27
+ groupOutput: false,
28
+ isServerMode: false,
29
+ restartOnChange: true,
30
+ clearConsole: false,
31
+ signal: 'SIGTERM',
32
+ watch: ['./'],
33
+ ignore: [
34
+ 'node_modules/**',
35
+ '.git/**',
36
+ '*.log',
37
+ 'dist/**',
38
+ 'build/**',
39
+ 'coverage/**',
40
+ '.nyc_output/**',
41
+ '*.tmp',
42
+ '*.temp'
43
+ ],
44
+ ext: ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'],
45
+ delay: 1000,
46
+ verbose: false,
47
+ };
48
+ this.options = {
49
+ ...defaultOptions,
50
+ ...options
51
+ };
52
+ }
53
+ setupFileWatcher() {
54
+ const watchOptions = {
55
+ watch: this.options.watch || ['./'],
56
+ ignore: this.options.ignore,
57
+ ext: this.options.ext,
58
+ delay: this.options.delay,
59
+ verbose: this.options.verbose
60
+ };
61
+ this.fileWatcher = new watcher_1.FileWatcher(watchOptions);
62
+ this.fileWatcher.on('change', (event) => {
63
+ if (this.options.restartOnChange && this.isRunning) {
64
+ this.handleFileChange(event);
65
+ }
66
+ });
67
+ }
68
+ async handleFileChange(event) {
69
+ logger_1.default.printLine(`File changed: ${chalk_1.default.yellow(event.relativePath)}`, 'info');
70
+ if (this.options.clearConsole) {
71
+ console.clear();
72
+ }
73
+ await this.restart();
74
+ }
75
+ async runCommands() {
76
+ if (this.commands.length === 0) {
77
+ return [];
78
+ }
79
+ this.runner = new runner_1.Runner(this.options);
80
+ try {
81
+ const results = await this.runner.run(this.commands);
82
+ return results;
83
+ }
84
+ catch (error) {
85
+ logger_1.default.printLine(`Execution failed: ${error.message}`, 'error');
86
+ return [];
87
+ }
88
+ }
89
+ printDevBanner() {
90
+ var _a;
91
+ const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
92
+ const uptimeStr = this.formatUptime(uptime);
93
+ console.log('\n' + chalk_1.default.bgGreen.black(' DEV MODE ') + '\n');
94
+ if (this.restartCount > 0) {
95
+ console.log(chalk_1.default.green(`${figures_1.default.arrowUp} Restarted ${this.restartCount} times`));
96
+ }
97
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Uptime: ${uptimeStr}`));
98
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Watching: ${((_a = this.options.watch) === null || _a === void 0 ? void 0 : _a.join(', ')) || 'current directory'}`));
99
+ if (this.options.ext && this.options.ext.length > 0) {
100
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Extensions: ${this.options.ext.join(', ')}`));
101
+ }
102
+ console.log('');
103
+ }
104
+ formatUptime(seconds) {
105
+ if (seconds < 60) {
106
+ return `${seconds}s`;
107
+ }
108
+ else if (seconds < 3600) {
109
+ const minutes = Math.floor(seconds / 60);
110
+ const remainingSeconds = seconds % 60;
111
+ return `${minutes}m ${remainingSeconds}s`;
112
+ }
113
+ else {
114
+ const hours = Math.floor(seconds / 3600);
115
+ const minutes = Math.floor((seconds % 3600) / 60);
116
+ return `${hours}h ${minutes}m`;
117
+ }
118
+ }
119
+ async start(commands) {
120
+ this.commands = commands;
121
+ this.isRunning = true;
122
+ this.startTime = new Date();
123
+ // Setup file watcher
124
+ this.setupFileWatcher();
125
+ // Print development banner
126
+ this.printDevBanner();
127
+ // Start file watcher
128
+ if (this.fileWatcher) {
129
+ await this.fileWatcher.start();
130
+ }
131
+ // Run initial commands
132
+ logger_1.default.printLine('Starting development server...', 'info');
133
+ await this.runCommands();
134
+ // Set up graceful shutdown
135
+ this.setupGracefulShutdown();
136
+ logger_1.default.printLine('Development server started. Watching for changes...', 'info');
137
+ logger_1.default.printLine(`Press ${chalk_1.default.cyan('Ctrl+C')} to stop`, 'info');
138
+ }
139
+ async restart() {
140
+ if (!this.isRunning) {
141
+ return;
142
+ }
143
+ logger_1.default.printLine('Restarting due to file changes...', 'info');
144
+ this.restartCount++;
145
+ // Stop current processes
146
+ if (this.runner) {
147
+ this.runner.cleanup(this.options.signal);
148
+ }
149
+ // Wait a moment before restarting
150
+ await new Promise(resolve => setTimeout(resolve, 500));
151
+ // Print restart banner
152
+ this.printDevBanner();
153
+ // Run commands again
154
+ await this.runCommands();
155
+ logger_1.default.printLine('Restart completed. Watching for changes...', 'info');
156
+ }
157
+ async stop() {
158
+ if (!this.isRunning) {
159
+ return;
160
+ }
161
+ logger_1.default.printLine('Stopping development server...', 'info');
162
+ this.isRunning = false;
163
+ // Stop file watcher
164
+ if (this.fileWatcher) {
165
+ this.fileWatcher.stop();
166
+ }
167
+ // Stop current processes
168
+ if (this.runner) {
169
+ this.runner.cleanup(this.options.signal);
170
+ }
171
+ const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
172
+ const uptimeStr = this.formatUptime(uptime);
173
+ logger_1.default.printLine(`Development server stopped after ${uptimeStr}`, 'info');
174
+ if (this.restartCount > 0) {
175
+ logger_1.default.printLine(`Total restarts: ${this.restartCount}`, 'info');
176
+ }
177
+ }
178
+ setupGracefulShutdown() {
179
+ const handleSignal = (signal) => {
180
+ console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Shutting down development server...`)}`);
181
+ this.stop().then(() => {
182
+ process.exit(0);
183
+ });
184
+ };
185
+ process.on('SIGINT', () => handleSignal('SIGINT'));
186
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
187
+ process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
188
+ }
189
+ isActive() {
190
+ return this.isRunning;
191
+ }
192
+ getUptime() {
193
+ return Math.floor((Date.now() - this.startTime.getTime()) / 1000);
194
+ }
195
+ getRestartCount() {
196
+ return this.restartCount;
197
+ }
198
+ getWatchedFiles() {
199
+ var _a;
200
+ return ((_a = this.fileWatcher) === null || _a === void 0 ? void 0 : _a.getWatchedFiles()) || [];
201
+ }
202
+ }
203
+ exports.DevRunner = DevRunner;
package/dist/src/index.js CHANGED
@@ -23,7 +23,9 @@ async function run(commands, options) {
23
23
  stopOnError: (_f = options === null || options === void 0 ? void 0 : options.stopOnError) !== null && _f !== void 0 ? _f : false,
24
24
  minimalOutput: (_g = options === null || options === void 0 ? void 0 : options.minimalOutput) !== null && _g !== void 0 ? _g : false,
25
25
  groupOutput: (_h = options === null || options === void 0 ? void 0 : options.groupOutput) !== null && _h !== void 0 ? _h : false,
26
- isServerMode: (_j = options === null || options === void 0 ? void 0 : options.isServerMode) !== null && _j !== void 0 ? _j : false
26
+ isServerMode: (_j = options === null || options === void 0 ? void 0 : options.isServerMode) !== null && _j !== void 0 ? _j : false,
27
+ retry: options === null || options === void 0 ? void 0 : options.retry,
28
+ retryDelay: options === null || options === void 0 ? void 0 : options.retryDelay
27
29
  };
28
30
  const runner = new runner_1.Runner(runOptions);
29
31
  if (options === null || options === void 0 ? void 0 : options.registerCleanup) {
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const figures_1 = __importDefault(require("figures"));
9
9
  const string_width_1 = __importDefault(require("string-width"));
10
+ const utils_1 = require("./utils");
10
11
  class Logger {
11
12
  constructor() {
12
13
  this.prefixLength = 0;
@@ -120,6 +121,9 @@ class Logger {
120
121
  // Clear buffer after printing
121
122
  this.outputBuffer.set(command, []);
122
123
  }
124
+ clearBuffer(command) {
125
+ this.outputBuffer.set(command, []);
126
+ }
123
127
  printLine(message, level = 'info') {
124
128
  if (level === 'error') {
125
129
  console.error(chalk_1.default.red(`${figures_1.default.cross} ${message}`));
@@ -136,6 +140,12 @@ class Logger {
136
140
  this.startTimes.set(command, new Date());
137
141
  const prefix = this.formatPrefix(command);
138
142
  const color = this.commandColors.get(command) || chalk_1.default.white;
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
+ }
139
149
  console.log(`${prefix} ${color('Starting...')}`);
140
150
  // Start spinner for this command
141
151
  this.startSpinner(command);
@@ -197,6 +207,32 @@ class Logger {
197
207
  console.error(`${prefix} ${chalk_1.default.red(error.message)}`);
198
208
  }
199
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
+ }
224
+ }
225
+ else {
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
+ }
234
+ }
235
+ }
200
236
  printSummary(results) {
201
237
  // Stop any remaining spinners
202
238
  this.stopAllSpinners();
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.ProcessManager = void 0;
30
+ // src/process-manager.ts - PM2 alternative process manager
31
+ const child_process_1 = require("child_process");
32
+ const fs = __importStar(require("fs/promises"));
33
+ const path = __importStar(require("path"));
34
+ const events_1 = require("events");
35
+ const logger_1 = __importDefault(require("./logger"));
36
+ class ProcessManager extends events_1.EventEmitter {
37
+ constructor(configPath) {
38
+ super();
39
+ this.processes = new Map();
40
+ this.isMonitoring = false;
41
+ this.configPath = configPath || path.join(process.cwd(), '.neex', 'processes.json');
42
+ this.ensureConfigDir();
43
+ this.startMonitoring();
44
+ }
45
+ async ensureConfigDir() {
46
+ const dir = path.dirname(this.configPath);
47
+ try {
48
+ await fs.mkdir(dir, { recursive: true });
49
+ }
50
+ catch (error) {
51
+ // Directory might already exist
52
+ }
53
+ }
54
+ async saveConfig() {
55
+ try {
56
+ const config = Array.from(this.processes.values()).map(proc => ({
57
+ ...proc.config,
58
+ status: proc.status,
59
+ created_at: proc.created_at,
60
+ restarts: proc.restarts
61
+ }));
62
+ await fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
63
+ }
64
+ catch (error) {
65
+ logger_1.default.printLine(`Failed to save config: ${error.message}`, 'error');
66
+ }
67
+ }
68
+ async loadConfig() {
69
+ try {
70
+ const data = await fs.readFile(this.configPath, 'utf-8');
71
+ const configs = JSON.parse(data);
72
+ for (const config of configs) {
73
+ if (config.id && !this.processes.has(config.id)) {
74
+ const processInfo = {
75
+ id: config.id,
76
+ name: config.name,
77
+ status: 'stopped',
78
+ uptime: 0,
79
+ restarts: 0,
80
+ memory: 0,
81
+ cpu: 0,
82
+ created_at: new Date(),
83
+ config
84
+ };
85
+ this.processes.set(config.id, processInfo);
86
+ }
87
+ }
88
+ }
89
+ catch (error) {
90
+ // Config file might not exist yet
91
+ }
92
+ }
93
+ generateId(name) {
94
+ let id = name.toLowerCase().replace(/[^a-z0-9]/g, '-');
95
+ let counter = 0;
96
+ while (this.processes.has(id)) {
97
+ counter++;
98
+ id = `${name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${counter}`;
99
+ }
100
+ return id;
101
+ }
102
+ async startProcess(processInfo) {
103
+ const { config } = processInfo;
104
+ try {
105
+ processInfo.status = 'launching';
106
+ this.emit('process:launching', processInfo);
107
+ const args = config.args || [];
108
+ const env = {
109
+ ...process.env,
110
+ ...config.env,
111
+ NEEX_PROCESS_ID: processInfo.id,
112
+ NEEX_PROCESS_NAME: processInfo.name
113
+ };
114
+ const childProcess = (0, child_process_1.spawn)(config.script, args, {
115
+ cwd: config.cwd || process.cwd(),
116
+ env,
117
+ stdio: ['ignore', 'pipe', 'pipe'],
118
+ detached: true
119
+ });
120
+ processInfo.process = childProcess;
121
+ processInfo.pid = childProcess.pid;
122
+ processInfo.status = 'online';
123
+ processInfo.created_at = new Date();
124
+ this.emit('process:start', processInfo);
125
+ // Handle stdout
126
+ if (childProcess.stdout) {
127
+ childProcess.stdout.on('data', (data) => {
128
+ this.emit('process:log', {
129
+ id: processInfo.id,
130
+ type: 'stdout',
131
+ data: data.toString()
132
+ });
133
+ });
134
+ }
135
+ // Handle stderr
136
+ if (childProcess.stderr) {
137
+ childProcess.stderr.on('data', (data) => {
138
+ this.emit('process:log', {
139
+ id: processInfo.id,
140
+ type: 'stderr',
141
+ data: data.toString()
142
+ });
143
+ });
144
+ }
145
+ // Handle process exit
146
+ childProcess.on('exit', (code, signal) => {
147
+ processInfo.status = code === 0 ? 'stopped' : 'errored';
148
+ processInfo.process = undefined;
149
+ processInfo.pid = undefined;
150
+ this.emit('process:exit', { processInfo, code, signal });
151
+ // Auto-restart if enabled
152
+ if (config.autorestart && processInfo.restarts < (config.max_restarts || 10)) {
153
+ const delay = config.restart_delay || 1000;
154
+ setTimeout(() => {
155
+ if (processInfo.status !== 'stopped') {
156
+ this.restart(processInfo.id);
157
+ }
158
+ }, delay);
159
+ }
160
+ });
161
+ // Handle process error
162
+ childProcess.on('error', (error) => {
163
+ processInfo.status = 'errored';
164
+ this.emit('process:error', { processInfo, error });
165
+ });
166
+ }
167
+ catch (error) {
168
+ processInfo.status = 'errored';
169
+ this.emit('process:error', { processInfo, error });
170
+ throw error;
171
+ }
172
+ }
173
+ startMonitoring() {
174
+ if (this.isMonitoring)
175
+ return;
176
+ this.isMonitoring = true;
177
+ this.monitoringInterval = setInterval(() => {
178
+ this.updateProcessStats();
179
+ }, 5000); // Update every 5 seconds
180
+ }
181
+ stopMonitoring() {
182
+ if (this.monitoringInterval) {
183
+ clearInterval(this.monitoringInterval);
184
+ this.monitoringInterval = undefined;
185
+ }
186
+ this.isMonitoring = false;
187
+ }
188
+ async updateProcessStats() {
189
+ for (const processInfo of this.processes.values()) {
190
+ if (processInfo.status === 'online' && processInfo.pid) {
191
+ try {
192
+ // Update uptime
193
+ const now = Date.now();
194
+ processInfo.uptime = Math.floor((now - processInfo.created_at.getTime()) / 1000);
195
+ // TODO: Implement memory and CPU monitoring
196
+ // This would require platform-specific code or external libraries
197
+ // For now, we'll set placeholder values
198
+ processInfo.memory = 0;
199
+ processInfo.cpu = 0;
200
+ }
201
+ catch (error) {
202
+ // Process might have died
203
+ if (processInfo.status === 'online') {
204
+ processInfo.status = 'stopped';
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ async start(config) {
211
+ const id = this.generateId(config.name);
212
+ const processInfo = {
213
+ id,
214
+ name: config.name,
215
+ status: 'stopped',
216
+ uptime: 0,
217
+ restarts: 0,
218
+ memory: 0,
219
+ cpu: 0,
220
+ created_at: new Date(),
221
+ config: { ...config, id }
222
+ };
223
+ this.processes.set(id, processInfo);
224
+ await this.startProcess(processInfo);
225
+ await this.saveConfig();
226
+ return id;
227
+ }
228
+ async stop(id) {
229
+ const processInfo = this.processes.get(id);
230
+ if (!processInfo) {
231
+ throw new Error(`Process ${id} not found`);
232
+ }
233
+ if (processInfo.status === 'stopped') {
234
+ return;
235
+ }
236
+ processInfo.status = 'stopping';
237
+ if (processInfo.process && processInfo.pid) {
238
+ try {
239
+ // Try graceful shutdown first
240
+ processInfo.process.kill('SIGTERM');
241
+ // Force kill after timeout
242
+ setTimeout(() => {
243
+ if (processInfo.process && !processInfo.process.killed) {
244
+ processInfo.process.kill('SIGKILL');
245
+ }
246
+ }, 5000);
247
+ }
248
+ catch (error) {
249
+ // Process might already be dead
250
+ }
251
+ }
252
+ processInfo.status = 'stopped';
253
+ processInfo.process = undefined;
254
+ processInfo.pid = undefined;
255
+ await this.saveConfig();
256
+ this.emit('process:stop', processInfo);
257
+ }
258
+ async restart(id) {
259
+ const processInfo = this.processes.get(id);
260
+ if (!processInfo) {
261
+ throw new Error(`Process ${id} not found`);
262
+ }
263
+ if (processInfo.status === 'online') {
264
+ await this.stop(id);
265
+ // Wait a bit before restarting
266
+ await new Promise(resolve => setTimeout(resolve, 1000));
267
+ }
268
+ processInfo.restarts++;
269
+ processInfo.last_restart = new Date();
270
+ await this.startProcess(processInfo);
271
+ await this.saveConfig();
272
+ }
273
+ async delete(id) {
274
+ const processInfo = this.processes.get(id);
275
+ if (!processInfo) {
276
+ throw new Error(`Process ${id} not found`);
277
+ }
278
+ if (processInfo.status === 'online') {
279
+ await this.stop(id);
280
+ }
281
+ this.processes.delete(id);
282
+ await this.saveConfig();
283
+ this.emit('process:delete', processInfo);
284
+ }
285
+ async list() {
286
+ return Array.from(this.processes.values());
287
+ }
288
+ async status(id) {
289
+ if (id) {
290
+ const processInfo = this.processes.get(id);
291
+ if (!processInfo) {
292
+ throw new Error(`Process ${id} not found`);
293
+ }
294
+ return processInfo;
295
+ }
296
+ return this.list();
297
+ }
298
+ async logs(id, lines = 100) {
299
+ // TODO: Implement log file reading
300
+ // For now, return empty array
301
+ return [];
302
+ }
303
+ async save() {
304
+ await this.saveConfig();
305
+ }
306
+ async load() {
307
+ await this.loadConfig();
308
+ }
309
+ async stopAll() {
310
+ const promises = Array.from(this.processes.keys()).map(id => this.stop(id));
311
+ await Promise.all(promises);
312
+ }
313
+ async restartAll() {
314
+ const promises = Array.from(this.processes.keys()).map(id => this.restart(id));
315
+ await Promise.all(promises);
316
+ }
317
+ async deleteAll() {
318
+ await this.stopAll();
319
+ this.processes.clear();
320
+ await this.saveConfig();
321
+ }
322
+ getProcess(id) {
323
+ return this.processes.get(id);
324
+ }
325
+ dispose() {
326
+ this.stopMonitoring();
327
+ this.stopAll();
328
+ }
329
+ }
330
+ exports.ProcessManager = ProcessManager;