neex 0.1.6 → 0.1.7
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 +1 -1
- package/dist/src/cli.js +18 -18
- package/dist/src/process-manager.js +109 -13
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/src/cli.js
CHANGED
|
@@ -197,11 +197,11 @@ function cli() {
|
|
|
197
197
|
process.exit(1);
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
|
-
//
|
|
200
|
+
// Dev command (Nodemon functionality - formerly watch)
|
|
201
201
|
program
|
|
202
|
-
.command('
|
|
203
|
-
.alias('
|
|
204
|
-
.description('Run commands with file watching (nodemon functionality)')
|
|
202
|
+
.command('dev [commands...]') // Made commands optional
|
|
203
|
+
.alias('d')
|
|
204
|
+
.description('Run commands with file watching and auto-restart (nodemon functionality)')
|
|
205
205
|
.option('-c, --no-color', 'Disable colored output')
|
|
206
206
|
.option('-t, --no-timing', 'Hide timing information')
|
|
207
207
|
.option('-p, --no-prefix', 'Hide command prefix')
|
|
@@ -222,11 +222,11 @@ function cli() {
|
|
|
222
222
|
const foundCommand = await findDefaultCommand();
|
|
223
223
|
if (foundCommand) {
|
|
224
224
|
effectiveCommands = [foundCommand];
|
|
225
|
-
console.log(chalk_1.default.blue(`${figures_1.default.info} No command specified for 'neex
|
|
225
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} No command specified for 'neex dev', using default: "${foundCommand}"`));
|
|
226
226
|
}
|
|
227
227
|
else {
|
|
228
|
-
console.error(chalk_1.default.red(`${figures_1.default.cross} No command specified for 'neex
|
|
229
|
-
console.error(chalk_1.default.yellow(`${figures_1.default.pointer} Please specify a command to run (e.g., neex
|
|
228
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} No command specified for 'neex dev' and no default script (dev, start) or main file found in package.json.`));
|
|
229
|
+
console.error(chalk_1.default.yellow(`${figures_1.default.pointer} Please specify a command to run (e.g., neex dev "npm run dev") or define a "dev" or "start" script in your package.json.`));
|
|
230
230
|
process.exit(1);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
@@ -268,7 +268,7 @@ function cli() {
|
|
|
268
268
|
// If isLikelyCommandOrScript is true, or if the file auto-detection didn't apply,
|
|
269
269
|
// effectiveCommands remains as the user provided it (joined later by DevRunner).
|
|
270
270
|
}
|
|
271
|
-
console.log(chalk_1.default.blue(`${figures_1.default.info} Starting development server with file watching (neex
|
|
271
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} Starting development server with file watching (neex dev) for command(s): ${effectiveCommands.map(cmd => `"${cmd}"`).join(' && ')}...`));
|
|
272
272
|
const watchPaths = options.watch || ['./'];
|
|
273
273
|
const ignorePatterns = options.ignore || [
|
|
274
274
|
'node_modules/**', '.git/**', '*.log', 'dist/**', 'build/**',
|
|
@@ -276,7 +276,7 @@ function cli() {
|
|
|
276
276
|
];
|
|
277
277
|
const extensions = options.ext || ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'];
|
|
278
278
|
devRunner = new dev_runner_js_1.DevRunner({
|
|
279
|
-
runnerName: 'neex
|
|
279
|
+
runnerName: 'neex dev',
|
|
280
280
|
parallel: false,
|
|
281
281
|
color: options.color,
|
|
282
282
|
showTiming: options.timing,
|
|
@@ -299,10 +299,10 @@ function cli() {
|
|
|
299
299
|
}
|
|
300
300
|
catch (error) {
|
|
301
301
|
if (error instanceof Error) {
|
|
302
|
-
console.error(chalk_1.default.red(`${figures_1.default.cross}
|
|
302
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Dev Error: ${error.message}`));
|
|
303
303
|
}
|
|
304
304
|
else {
|
|
305
|
-
console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown
|
|
305
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown dev error occurred`));
|
|
306
306
|
}
|
|
307
307
|
process.exit(1);
|
|
308
308
|
}
|
|
@@ -492,7 +492,7 @@ function cli() {
|
|
|
492
492
|
.command('logs [id]')
|
|
493
493
|
.description('Show process logs')
|
|
494
494
|
.option('-f, --follow', 'Follow log output')
|
|
495
|
-
.option('-n, --lines <number>', 'Number of lines to show', parseInt)
|
|
495
|
+
.option('-n, --lines <number>', 'Number of lines to show', (value) => parseInt(value, 10), 15)
|
|
496
496
|
.action(async (id, options) => {
|
|
497
497
|
try {
|
|
498
498
|
if (!processManager) {
|
|
@@ -500,7 +500,7 @@ function cli() {
|
|
|
500
500
|
await processManager.load();
|
|
501
501
|
}
|
|
502
502
|
if (id) {
|
|
503
|
-
const logs = await processManager.logs(id, options.lines
|
|
503
|
+
const logs = await processManager.logs(id, options.lines);
|
|
504
504
|
if (logs.length === 0) {
|
|
505
505
|
console.log(chalk_1.default.yellow(`${figures_1.default.info} No logs found for process ${id}`));
|
|
506
506
|
}
|
|
@@ -680,7 +680,7 @@ WantedBy=multi-user.target
|
|
|
680
680
|
program.help();
|
|
681
681
|
}
|
|
682
682
|
// Graceful shutdown handling
|
|
683
|
-
const handleSignal = (signal) => {
|
|
683
|
+
const handleSignal = async (signal) => {
|
|
684
684
|
console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Cleaning up...`)}`);
|
|
685
685
|
if (devRunner && devRunner.isActive()) {
|
|
686
686
|
devRunner.stop();
|
|
@@ -689,13 +689,13 @@ WantedBy=multi-user.target
|
|
|
689
689
|
cleanupRunner();
|
|
690
690
|
}
|
|
691
691
|
if (processManager) {
|
|
692
|
-
processManager.dispose();
|
|
692
|
+
await processManager.dispose();
|
|
693
693
|
}
|
|
694
694
|
setTimeout(() => process.exit(0), 500);
|
|
695
695
|
};
|
|
696
|
-
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
697
|
-
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
698
|
-
process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
|
|
696
|
+
process.on('SIGINT', () => handleSignal('SIGINT').catch(err => console.error('SIGINT handler error:', err)));
|
|
697
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM').catch(err => console.error('SIGTERM handler error:', err)));
|
|
698
|
+
process.on('SIGQUIT', () => handleSignal('SIGQUIT').catch(err => console.error('SIGQUIT handler error:', err)));
|
|
699
699
|
}
|
|
700
700
|
exports.default = cli;
|
|
701
701
|
// Helper function to format uptime
|
|
@@ -29,26 +29,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.ProcessManager = void 0;
|
|
30
30
|
// src/process-manager.ts - PM2 alternative process manager
|
|
31
31
|
const child_process_1 = require("child_process");
|
|
32
|
-
const
|
|
32
|
+
const fsFull = __importStar(require("fs")); // Renamed to fsFull to avoid conflicts
|
|
33
|
+
const fs_1 = require("fs");
|
|
33
34
|
const path = __importStar(require("path"));
|
|
34
35
|
const events_1 = require("events");
|
|
35
36
|
const logger_1 = __importDefault(require("./logger"));
|
|
36
37
|
class ProcessManager extends events_1.EventEmitter {
|
|
37
38
|
constructor(configPath) {
|
|
38
39
|
super();
|
|
40
|
+
this.logStreams = new Map();
|
|
39
41
|
this.processes = new Map();
|
|
40
42
|
this.isMonitoring = false;
|
|
41
43
|
this.configPath = configPath || path.join(process.cwd(), '.neex', 'processes.json');
|
|
42
|
-
this.
|
|
44
|
+
this.logDir = path.join(path.dirname(this.configPath), 'logs');
|
|
45
|
+
this.ensureConfigDirAndLogDir(); // Renamed and updated
|
|
46
|
+
// loadConfig will be called by CLI commands as needed before operations like list, stop, etc.
|
|
43
47
|
this.startMonitoring();
|
|
44
48
|
}
|
|
45
|
-
async
|
|
49
|
+
async ensureConfigDirAndLogDir() {
|
|
46
50
|
const dir = path.dirname(this.configPath);
|
|
47
51
|
try {
|
|
48
|
-
await
|
|
52
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
53
|
+
await fs_1.promises.mkdir(this.logDir, { recursive: true });
|
|
49
54
|
}
|
|
50
55
|
catch (error) {
|
|
51
|
-
|
|
56
|
+
logger_1.default.printLine(`Failed to create config/log directories: ${error.message}`, 'error');
|
|
57
|
+
// Directory might already exist or other error
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
async saveConfig() {
|
|
@@ -59,7 +65,7 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
59
65
|
created_at: proc.created_at,
|
|
60
66
|
restarts: proc.restarts
|
|
61
67
|
}));
|
|
62
|
-
await
|
|
68
|
+
await fs_1.promises.writeFile(this.configPath, JSON.stringify(config, null, 2));
|
|
63
69
|
}
|
|
64
70
|
catch (error) {
|
|
65
71
|
logger_1.default.printLine(`Failed to save config: ${error.message}`, 'error');
|
|
@@ -67,7 +73,7 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
67
73
|
}
|
|
68
74
|
async loadConfig() {
|
|
69
75
|
try {
|
|
70
|
-
const data = await
|
|
76
|
+
const data = await fs_1.promises.readFile(this.configPath, 'utf-8');
|
|
71
77
|
const configs = JSON.parse(data);
|
|
72
78
|
for (const config of configs) {
|
|
73
79
|
if (config.id && !this.processes.has(config.id)) {
|
|
@@ -99,8 +105,39 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
99
105
|
}
|
|
100
106
|
return id;
|
|
101
107
|
}
|
|
108
|
+
closeLogStreams(id) {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
const streams = this.logStreams.get(id);
|
|
111
|
+
if (streams) {
|
|
112
|
+
(_a = streams.out) === null || _a === void 0 ? void 0 : _a.end();
|
|
113
|
+
(_b = streams.err) === null || _b === void 0 ? void 0 : _b.end();
|
|
114
|
+
this.logStreams.delete(id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
102
117
|
async startProcess(processInfo) {
|
|
103
118
|
const { config } = processInfo;
|
|
119
|
+
// Setup log files
|
|
120
|
+
const defaultLogPath = (type) => path.join(this.logDir, `${processInfo.id}.${type}.log`);
|
|
121
|
+
config.out_file = config.out_file || defaultLogPath('out');
|
|
122
|
+
config.error_file = config.error_file || defaultLogPath('err');
|
|
123
|
+
this.closeLogStreams(processInfo.id); // Ensure any previous streams for this ID are closed (e.g., on restart)
|
|
124
|
+
let outStream;
|
|
125
|
+
let errStream;
|
|
126
|
+
try {
|
|
127
|
+
if (config.out_file) {
|
|
128
|
+
await fs_1.promises.mkdir(path.dirname(config.out_file), { recursive: true }); // Ensure directory for custom log file exists
|
|
129
|
+
outStream = fsFull.createWriteStream(config.out_file, { flags: 'a' });
|
|
130
|
+
this.logStreams.set(processInfo.id, { ...this.logStreams.get(processInfo.id), out: outStream });
|
|
131
|
+
}
|
|
132
|
+
if (config.error_file) {
|
|
133
|
+
await fs_1.promises.mkdir(path.dirname(config.error_file), { recursive: true }); // Ensure directory for custom log file exists
|
|
134
|
+
errStream = fsFull.createWriteStream(config.error_file, { flags: 'a' });
|
|
135
|
+
this.logStreams.set(processInfo.id, { ...this.logStreams.get(processInfo.id), err: errStream });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (streamError) {
|
|
139
|
+
logger_1.default.printLine(`Failed to open log streams for ${processInfo.id}: ${streamError.message}`, 'error');
|
|
140
|
+
}
|
|
104
141
|
try {
|
|
105
142
|
processInfo.status = 'launching';
|
|
106
143
|
this.emit('process:launching', processInfo);
|
|
@@ -121,7 +158,18 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
121
158
|
processInfo.pid = childProcess.pid;
|
|
122
159
|
processInfo.status = 'online';
|
|
123
160
|
processInfo.created_at = new Date();
|
|
161
|
+
// Allow parent to exit independently of the child
|
|
162
|
+
if (childProcess.pid) { // unref only if process spawned successfully
|
|
163
|
+
childProcess.unref();
|
|
164
|
+
}
|
|
124
165
|
this.emit('process:start', processInfo);
|
|
166
|
+
// Pipe to log files if streams are available
|
|
167
|
+
if (outStream && childProcess.stdout) {
|
|
168
|
+
childProcess.stdout.pipe(outStream);
|
|
169
|
+
}
|
|
170
|
+
if (errStream && childProcess.stderr) {
|
|
171
|
+
childProcess.stderr.pipe(errStream);
|
|
172
|
+
}
|
|
125
173
|
// Handle stdout
|
|
126
174
|
if (childProcess.stdout) {
|
|
127
175
|
childProcess.stdout.on('data', (data) => {
|
|
@@ -147,6 +195,8 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
147
195
|
processInfo.status = code === 0 ? 'stopped' : 'errored';
|
|
148
196
|
processInfo.process = undefined;
|
|
149
197
|
processInfo.pid = undefined;
|
|
198
|
+
// Note: Streams are not closed here on 'exit' to allow logs to be read after a crash.
|
|
199
|
+
// They are closed on explicit stop/delete or neex shutdown.
|
|
150
200
|
this.emit('process:exit', { processInfo, code, signal });
|
|
151
201
|
// Auto-restart if enabled
|
|
152
202
|
if (config.autorestart && processInfo.restarts < (config.max_restarts || 10)) {
|
|
@@ -252,6 +302,7 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
252
302
|
processInfo.status = 'stopped';
|
|
253
303
|
processInfo.process = undefined;
|
|
254
304
|
processInfo.pid = undefined;
|
|
305
|
+
this.closeLogStreams(id);
|
|
255
306
|
await this.saveConfig();
|
|
256
307
|
this.emit('process:stop', processInfo);
|
|
257
308
|
}
|
|
@@ -278,9 +329,22 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
278
329
|
if (processInfo.status === 'online') {
|
|
279
330
|
await this.stop(id);
|
|
280
331
|
}
|
|
332
|
+
const currentProcessInfo = this.processes.get(id);
|
|
333
|
+
if (currentProcessInfo) {
|
|
334
|
+
if (currentProcessInfo.status === 'online' || currentProcessInfo.status === 'launching' || currentProcessInfo.status === 'stopping') {
|
|
335
|
+
// stop() will call closeLogStreams
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this.closeLogStreams(id); // Ensure closed for other states before deleting
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Optional: Delete actual log files if `delete` implies full data removal
|
|
342
|
+
// if (config.out_file) await fsPromises.unlink(config.out_file).catch(() => {});
|
|
343
|
+
// if (config.error_file) await fsPromises.unlink(config.error_file).catch(() => {});
|
|
281
344
|
this.processes.delete(id);
|
|
282
345
|
await this.saveConfig();
|
|
283
|
-
|
|
346
|
+
if (currentProcessInfo)
|
|
347
|
+
this.emit('process:delete', currentProcessInfo);
|
|
284
348
|
}
|
|
285
349
|
async list() {
|
|
286
350
|
return Array.from(this.processes.values());
|
|
@@ -295,10 +359,42 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
295
359
|
}
|
|
296
360
|
return this.list();
|
|
297
361
|
}
|
|
298
|
-
async logs(id, lines =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
362
|
+
async logs(id, lines = 15) {
|
|
363
|
+
let processInfo = this.processes.get(id);
|
|
364
|
+
if (!processInfo) {
|
|
365
|
+
await this.loadConfig(); // Attempt to load if not in memory
|
|
366
|
+
processInfo = this.processes.get(id);
|
|
367
|
+
if (!processInfo) {
|
|
368
|
+
throw new Error(`Process ${id} not found`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return this.readLogFiles(processInfo.config, lines);
|
|
372
|
+
}
|
|
373
|
+
async readLogFiles(config, linesToRead) {
|
|
374
|
+
const finalMessages = [];
|
|
375
|
+
const { out_file, error_file } = config;
|
|
376
|
+
const readFileLastLines = async (filePath) => {
|
|
377
|
+
if (!filePath)
|
|
378
|
+
return [];
|
|
379
|
+
try {
|
|
380
|
+
await fs_1.promises.access(filePath);
|
|
381
|
+
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
382
|
+
const fileLines = content.split('\n').filter((line) => line.trim() !== '');
|
|
383
|
+
return fileLines.slice(-linesToRead);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// Log file might not exist or not be readable, return empty
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
const outLogs = await readFileLastLines(out_file);
|
|
391
|
+
const errLogs = await readFileLastLines(error_file);
|
|
392
|
+
outLogs.forEach(log => finalMessages.push(log));
|
|
393
|
+
if (errLogs.length > 0 && outLogs.length > 0) {
|
|
394
|
+
finalMessages.push('--- STDERR ---'); // Separator
|
|
395
|
+
}
|
|
396
|
+
errLogs.forEach(log => finalMessages.push(log));
|
|
397
|
+
return finalMessages;
|
|
302
398
|
}
|
|
303
399
|
async save() {
|
|
304
400
|
await this.saveConfig();
|
|
@@ -322,7 +418,7 @@ class ProcessManager extends events_1.EventEmitter {
|
|
|
322
418
|
getProcess(id) {
|
|
323
419
|
return this.processes.get(id);
|
|
324
420
|
}
|
|
325
|
-
dispose() {
|
|
421
|
+
async dispose() {
|
|
326
422
|
this.stopMonitoring();
|
|
327
423
|
this.stopAll();
|
|
328
424
|
}
|