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 CHANGED
@@ -6,7 +6,7 @@
6
6
  </picture>
7
7
  </a>
8
8
 
9
- # Neex v0.1.6
9
+ # Neex v0.1.7
10
10
 
11
11
  ### 🚀 Neex: The Modern Build System for Polyrepo-in-Monorepo Architecture
12
12
 
package/dist/src/cli.js CHANGED
@@ -197,11 +197,11 @@ function cli() {
197
197
  process.exit(1);
198
198
  }
199
199
  });
200
- // Watch command (Nodemon functionality)
200
+ // Dev command (Nodemon functionality - formerly watch)
201
201
  program
202
- .command('watch [commands...]') // Made commands optional
203
- .alias('w')
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 w', using default: "${foundCommand}"`));
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 w' 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 w "npm run dev") or define a "dev" or "start" script in your package.json.`));
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 w) for command(s): ${effectiveCommands.map(cmd => `"${cmd}"`).join(' && ')}...`));
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 w',
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} Watch Error: ${error.message}`));
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 watch error occurred`));
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 || 100);
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 fs = __importStar(require("fs/promises"));
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.ensureConfigDir();
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 ensureConfigDir() {
49
+ async ensureConfigDirAndLogDir() {
46
50
  const dir = path.dirname(this.configPath);
47
51
  try {
48
- await fs.mkdir(dir, { recursive: true });
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
- // Directory might already exist
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 fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
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 fs.readFile(this.configPath, 'utf-8');
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
- this.emit('process:delete', processInfo);
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 = 100) {
299
- // TODO: Implement log file reading
300
- // For now, return empty array
301
- return [];
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",