neex 0.6.20 → 0.6.22

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.6.4
9
+ # Neex v0.6.21
10
10
 
11
11
  ### 🚀 Neex: The Modern Build System for Polyrepo-in-Monorepo Architecture
12
12
 
@@ -110,7 +110,7 @@ function addBuildCommands(program) {
110
110
  .option('-d, --delay <ms>', 'Delay before rebuild in milliseconds', parseInt)
111
111
  .option('--clean', 'Clean output directory before build')
112
112
  .option('--verbose', 'Verbose output')
113
- .option('--info', 'Show detailed information during build')
113
+ .option('--info', 'Show detailed build information')
114
114
  .option('--source-map', 'Generate source maps')
115
115
  .option('--target <target>', 'Target ECMAScript version', 'es2020')
116
116
  .option('--module <module>', 'Module system', 'commonjs')
@@ -129,14 +129,32 @@ function addBuildCommands(program) {
129
129
  process.exit(1);
130
130
  }
131
131
  // Get build configuration
132
- let buildConfig;
133
- try {
134
- buildConfig = await getBuildConfig(file, options.output, showInfo);
135
- }
136
- catch (error) {
137
- console.error(chalk_1.default.red(`${figures_1.default.cross} neex build: ${error instanceof Error ? error.message : 'Unknown error occurred'}`));
138
- process.exit(1);
139
- }
132
+ const buildConfig = await getBuildConfig(file, options.output, showInfo);
133
+ // Create BuildManager instance with all required options
134
+ const buildManager = new build_manager_js_1.BuildManager({
135
+ runnerName: 'neex build',
136
+ inputFile: file,
137
+ outputDir: options.output,
138
+ buildType: buildConfig.buildType,
139
+ buildCommand: buildConfig.buildCommand,
140
+ showInfo: showInfo,
141
+ color: !options.color,
142
+ showTiming: !options.timing,
143
+ watch: options.watch,
144
+ delay: parseInt(options.delay),
145
+ verbose: options.verbose,
146
+ clean: options.clean,
147
+ sourceMap: options.sourceMap,
148
+ target: options.target,
149
+ module: options.module,
150
+ parallel: false,
151
+ groupOutput: false,
152
+ isServerMode: false,
153
+ printOutput: true,
154
+ prefix: true,
155
+ stopOnError: false,
156
+ minimalOutput: false
157
+ });
140
158
  // Setup ignore patterns for watch mode
141
159
  const ignorePatterns = options.ignore || [
142
160
  'node_modules/**',
@@ -168,34 +186,26 @@ function addBuildCommands(program) {
168
186
  console.log(chalk_1.default.green(`${figures_1.default.tick} neex build: Starting build process...`));
169
187
  console.log(chalk_1.default.gray(`${'='.repeat(60)}`));
170
188
  }
171
- // Create BuildManager instance
172
- buildManager = new build_manager_js_1.BuildManager({
173
- runnerName: 'neex build',
174
- inputFile: file,
175
- outputDir: options.output,
176
- buildType: buildConfig.buildType,
177
- buildCommand: buildConfig.buildCommand,
178
- color: options.color,
179
- showTiming: options.timing,
180
- prefix: options.prefix,
181
- stopOnError: options.stopOnError,
182
- printOutput: true,
183
- minimalOutput: options.minimal,
184
- watch: options.watch,
185
- ignore: ignorePatterns,
186
- delay: options.delay || 1000,
187
- verbose: options.verbose,
188
- showInfo: showInfo,
189
- clean: options.clean,
190
- sourceMap: options.sourceMap,
191
- target: options.target,
192
- module: options.module,
193
- parallel: false,
194
- groupOutput: false,
195
- isServerMode: false
196
- });
197
189
  // Start the build process
198
- await buildManager.start();
190
+ try {
191
+ if (showInfo) {
192
+ console.log(chalk_1.default.blue(`${figures_1.default.info} neex build: Analyzing ${chalk_1.default.cyan(path.basename(file))}`));
193
+ }
194
+ await buildManager.start();
195
+ if (showInfo) {
196
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Build completed successfully`));
197
+ }
198
+ else {
199
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Build completed`));
200
+ }
201
+ }
202
+ catch (error) {
203
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Build failed`));
204
+ if (error instanceof Error) {
205
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
206
+ }
207
+ process.exit(1);
208
+ }
199
209
  }
200
210
  catch (error) {
201
211
  console.error(chalk_1.default.red(`${figures_1.default.cross} neex build: Fatal error occurred`));
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.addStartCommands = void 0;
30
+ const start_manager_js_1 = require("../start-manager.js");
30
31
  const chalk_1 = __importDefault(require("chalk"));
31
32
  const figures_1 = __importDefault(require("figures"));
32
33
  const path = __importStar(require("path"));
@@ -99,74 +100,75 @@ function addStartCommands(program) {
99
100
  program
100
101
  .command('start <file>')
101
102
  .alias('s')
102
- .description('Start JavaScript/TypeScript applications in production mode')
103
+ .description('Start a production application')
103
104
  .option('-c, --no-color', 'Disable colored output')
104
105
  .option('-t, --no-timing', 'Hide timing information')
105
- .option('-p, --no-prefix', 'Hide command prefix')
106
- .option('-o, --no-output', 'Hide command output')
107
- .option('-m, --minimal', 'Use minimal output format')
108
- .option('-w, --watch', 'Watch for changes and restart (production hot-reload)')
109
- .option('-i, --ignore <patterns...>', 'Patterns to ignore when watching')
110
- .option('-e, --ext <extensions...>', 'File extensions to watch (default: js,mjs,json)')
111
- .option('-d, --delay <ms>', 'Delay before restart in milliseconds', parseInt)
112
- .option('--max-restarts <number>', 'Maximum number of restarts', parseInt)
113
- .option('--restart-delay <ms>', 'Delay between restarts in milliseconds', parseInt)
114
- .option('--verbose', 'Verbose output')
115
- .option('--info', 'Show detailed information during startup')
116
- .option('--signal <signal>', 'Signal to send to processes on restart', 'SIGTERM')
117
- .option('--env <env>', 'Environment mode', 'production')
118
- .option('--cluster', 'Enable cluster mode (spawn multiple processes)')
119
- .option('--cluster-instances <number>', 'Number of cluster instances', parseInt)
120
- .option('--memory-limit <mb>', 'Memory limit in MB', parseInt)
121
- .option('--cpu-limit <percent>', 'CPU limit percentage', parseInt)
106
+ .option('-v, --verbose', 'Show detailed logs')
107
+ .option('-w, --watch', 'Watch for file changes')
108
+ .option('--delay <ms>', 'Delay between file changes (ms)', '1000')
109
+ .option('--environment <env>', 'Application environment', 'production')
110
+ .option('--cluster', 'Enable cluster mode')
111
+ .option('--instances <num>', 'Number of cluster instances', '1')
112
+ .option('--memory <mb>', 'Memory limit (MB)', '0')
113
+ .option('--cpu <percent>', 'CPU limit (%)', '0')
114
+ .option('--info', 'Show detailed start information')
122
115
  .action(async (file, options) => {
123
116
  try {
124
- const showInfo = options.info || false;
125
- if (showInfo) {
126
- console.log(chalk_1.default.blue(`${figures_1.default.info} neex start: Starting production application server...`));
127
- console.log(chalk_1.default.blue(`${figures_1.default.info} neex start: Target file: ${chalk_1.default.cyan(file)}`));
128
- }
129
- // Validate file parameter
130
- if (!file || file.trim() === '') {
131
- console.error(chalk_1.default.red(`${figures_1.default.cross} neex start: Error - No file specified!`));
132
- console.error(chalk_1.default.yellow(`${figures_1.default.pointer} Usage: neex start <file>`));
133
- console.error(chalk_1.default.yellow(`${figures_1.default.pointer} Example: neex start dist/server.js`));
134
- process.exit(1);
135
- }
136
- // Get the start command configuration
137
- let startConfig;
117
+ // Get start command configuration
118
+ const startConfig = await getStartCommand(file, options.info);
119
+ // Create StartManager instance
120
+ const startManager = new start_manager_js_1.StartManager({
121
+ runnerName: 'neex start',
122
+ targetFile: file,
123
+ command: startConfig.command,
124
+ processName: startConfig.processName,
125
+ runtime: startConfig.runtime,
126
+ environment: options.environment,
127
+ showInfo: options.info,
128
+ color: !options.color,
129
+ showTiming: !options.timing,
130
+ watch: options.watch,
131
+ delay: parseInt(options.delay),
132
+ verbose: options.verbose,
133
+ cluster: options.cluster,
134
+ clusterInstances: parseInt(options.instances),
135
+ memoryLimit: parseInt(options.memory),
136
+ cpuLimit: parseInt(options.cpu),
137
+ parallel: false,
138
+ printOutput: true,
139
+ prefix: true,
140
+ stopOnError: false,
141
+ minimalOutput: false,
142
+ groupOutput: false,
143
+ isServerMode: true
144
+ });
145
+ // Start the application
138
146
  try {
139
- startConfig = await getStartCommand(file, showInfo);
147
+ if (options.info) {
148
+ console.log(chalk_1.default.blue(`${figures_1.default.info} neex start: Analyzing ${chalk_1.default.cyan(path.basename(file))}`));
149
+ console.log(chalk_1.default.green(`${figures_1.default.tick} neex start: ${startConfig.runtime} detected, using ${startConfig.runtime} runtime`));
150
+ }
151
+ await startManager.start();
152
+ if (options.info) {
153
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Application started successfully`));
154
+ }
155
+ else {
156
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Application started`));
157
+ }
140
158
  }
141
159
  catch (error) {
142
- console.error(chalk_1.default.red(`${figures_1.default.cross} neex start: ${error instanceof Error ? error.message : 'Unknown error occurred'}`));
160
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Failed to start application`));
161
+ if (error instanceof Error) {
162
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
163
+ }
143
164
  process.exit(1);
144
165
  }
145
- // Setup watch configuration if enabled
146
- const watchPaths = options.watch ? [path.dirname(file)] : [];
147
- const ignorePatterns = options.ignore || [
148
- 'node_modules/**',
149
- '.git/**',
150
- '*.log',
151
- 'src/**',
152
- 'test/**',
153
- 'tests/**',
154
- 'coverage/**',
155
- '.nyc_output/**',
156
- '*.tmp',
157
- '*.temp',
158
- '.DS_Store',
159
- 'Thumbs.db'
160
- ];
161
- const extensions = options.ext || ['js', 'mjs', 'json'];
162
- // Log configuration only if --info flag is set
163
- if (showInfo) {
164
- console.log(chalk_1.default.blue(`${figures_1.default.info} neex start: Configuration:`));
165
- console;
166
- }
167
166
  }
168
167
  catch (error) {
169
- console.error(chalk_1.default.red(`${figures_1.default.cross} neex start: ${error instanceof Error ? error.message : 'Unknown error occurred'}`));
168
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Failed to start application`));
169
+ if (error instanceof Error) {
170
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
171
+ }
170
172
  process.exit(1);
171
173
  }
172
174
  });
@@ -45,6 +45,9 @@ class StartManager {
45
45
  cpuUsage: 0,
46
46
  uptime: 0
47
47
  };
48
+ if (!options.targetFile || !options.command || !options.processName || !options.runtime) {
49
+ throw new Error('Missing required options: targetFile, command, processName, and runtime are required');
50
+ }
48
51
  const defaultOptions = {
49
52
  parallel: false,
50
53
  printOutput: true,
@@ -81,10 +84,31 @@ class StartManager {
81
84
  cluster: false,
82
85
  clusterInstances: os.cpus().length
83
86
  };
87
+ // Validate numeric values
88
+ const validateNumber = (value, name) => {
89
+ const num = parseInt(value);
90
+ if (isNaN(num) || num < 0) {
91
+ throw new Error(`Invalid ${name}: must be a non-negative number`);
92
+ }
93
+ return num;
94
+ };
84
95
  this.options = {
85
96
  ...defaultOptions,
86
- ...options
97
+ ...options,
98
+ delay: validateNumber(options.delay || defaultOptions.delay, 'delay'),
99
+ clusterInstances: validateNumber(options.clusterInstances || defaultOptions.clusterInstances, 'clusterInstances'),
100
+ memoryLimit: options.memoryLimit ? validateNumber(options.memoryLimit, 'memoryLimit') : undefined,
101
+ cpuLimit: options.cpuLimit ? validateNumber(options.cpuLimit, 'cpuLimit') : undefined,
102
+ maxRestarts: options.maxRestarts ? validateNumber(options.maxRestarts, 'maxRestarts') : undefined,
103
+ restartDelay: validateNumber(options.restartDelay || defaultOptions.restartDelay, 'restartDelay')
87
104
  };
105
+ // Validate environment
106
+ if (this.options.environment) {
107
+ this.options.environment = this.options.environment.toLowerCase();
108
+ if (!['production', 'development', 'staging', 'test'].includes(this.options.environment)) {
109
+ throw new Error(`Invalid environment: ${this.options.environment}. Must be one of: production, development, staging, test`);
110
+ }
111
+ }
88
112
  }
89
113
  setupFileWatcher() {
90
114
  var _a;
@@ -171,20 +195,59 @@ class StartManager {
171
195
  logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.cross} Application crashed`)}`, 'error');
172
196
  logger_1.default.printLine(`${prefix} ${chalk_1.default.yellow(`${figures_1.default.warning} Attempting restart in ${this.options.restartDelay}ms...`)}`, 'info');
173
197
  }
198
+ // Check if we should restart
199
+ if (!this.shouldRestart()) {
200
+ if (this.options.showInfo) {
201
+ logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.cross} Max restarts reached (${this.options.maxRestarts}). Stopping application.`)}`, 'error');
202
+ }
203
+ await this.stop();
204
+ return;
205
+ }
174
206
  // Wait before restarting
175
207
  await new Promise(resolve => setTimeout(resolve, this.options.restartDelay));
176
- await this.gracefulRestart();
208
+ try {
209
+ await this.gracefulRestart();
210
+ }
211
+ catch (error) {
212
+ logger_1.default.printLine(`Failed to restart application: ${error.message}`, 'error');
213
+ if (this.options.stopOnError) {
214
+ await this.stop();
215
+ }
216
+ }
177
217
  }
178
218
  collectProcessStats() {
219
+ var _a;
179
220
  if (!this.options.showInfo) {
180
221
  return;
181
222
  }
182
- const memUsage = process.memoryUsage();
183
- this.processStats.memoryUsage = Math.round(memUsage.heapUsed / 1024 / 1024);
184
- this.processStats.uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
185
- // Simple CPU usage approximation
186
- const cpuUsage = process.cpuUsage();
187
- this.processStats.cpuUsage = Math.round(((cpuUsage.user + cpuUsage.system) / 1000000) * 100) / 100;
223
+ try {
224
+ const memUsage = process.memoryUsage();
225
+ this.processStats.memoryUsage = Math.round(memUsage.heapUsed / 1024 / 1024);
226
+ this.processStats.uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
227
+ // More accurate CPU usage calculation
228
+ const cpuUsage = process.cpuUsage();
229
+ const totalCpuTime = cpuUsage.user + cpuUsage.system;
230
+ const timeDiff = Date.now() - (((_a = this.lastRestartTime) === null || _a === void 0 ? void 0 : _a.getTime()) || this.startTime.getTime());
231
+ this.processStats.cpuUsage = Math.round((totalCpuTime / timeDiff) * 100);
232
+ // Check resource limits
233
+ if (this.options.memoryLimit && this.processStats.memoryUsage > this.options.memoryLimit) {
234
+ const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
235
+ logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.warning} Memory limit exceeded: ${this.processStats.memoryUsage}MB > ${this.options.memoryLimit}MB`)}`, 'warn');
236
+ if (this.options.stopOnError) {
237
+ this.stop();
238
+ }
239
+ }
240
+ if (this.options.cpuLimit && this.processStats.cpuUsage > this.options.cpuLimit) {
241
+ const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
242
+ logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.warning} CPU limit exceeded: ${this.processStats.cpuUsage}% > ${this.options.cpuLimit}%`)}`, 'warn');
243
+ if (this.options.stopOnError) {
244
+ this.stop();
245
+ }
246
+ }
247
+ }
248
+ catch (error) {
249
+ logger_1.default.printLine(`Failed to collect process stats: ${error.message}`, 'error');
250
+ }
188
251
  }
189
252
  printStartBanner() {
190
253
  var _a, _b;
@@ -319,6 +382,35 @@ class StartManager {
319
382
  logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} restarted successfully`)}`, 'info');
320
383
  }
321
384
  }
385
+ setupGracefulShutdown() {
386
+ const handleSignal = async (signal) => {
387
+ if (!this.isRunning) {
388
+ return;
389
+ }
390
+ try {
391
+ if (this.options.showInfo) {
392
+ console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Gracefully shutting down ${this.options.processName}...`)}`);
393
+ }
394
+ await this.stop();
395
+ process.exit(0);
396
+ }
397
+ catch (error) {
398
+ logger_1.default.printLine(`Failed to handle ${signal}: ${error.message}`, 'error');
399
+ process.exit(1);
400
+ }
401
+ };
402
+ process.on('SIGINT', () => handleSignal('SIGINT'));
403
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
404
+ process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
405
+ process.on('uncaughtException', (error) => {
406
+ logger_1.default.printLine(`Uncaught Exception: ${error.message}`, 'error');
407
+ handleSignal('SIGTERM');
408
+ });
409
+ process.on('unhandledRejection', (reason) => {
410
+ logger_1.default.printLine(`Unhandled Rejection: ${reason}`, 'error');
411
+ handleSignal('SIGTERM');
412
+ });
413
+ }
322
414
  async stop() {
323
415
  const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
324
416
  if (!this.isRunning) {
@@ -328,34 +420,29 @@ class StartManager {
328
420
  logger_1.default.printLine(`${prefix} ${chalk_1.default.yellow(`${figures_1.default.warning} Stopping ${this.options.processName}...`)}`, 'info');
329
421
  }
330
422
  this.isRunning = false;
331
- // Stop file watcher
332
- if (this.fileWatcher) {
333
- this.fileWatcher.stop();
334
- }
335
- // Stop application gracefully
336
- if (this.runner) {
337
- this.runner.cleanup(this.options.signal);
338
- }
339
- // Calculate final stats
340
- const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
341
- const uptimeStr = this.formatUptime(uptime);
342
- if (this.options.showInfo) {
343
- logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} stopped successfully`)}`, 'info');
344
- logger_1.default.printLine(`${prefix} Final stats: ${uptimeStr} uptime, ${this.restartCount} restarts, ${this.crashCount} crashes`, 'info');
345
- }
346
- }
347
- setupGracefulShutdown() {
348
- const handleSignal = (signal) => {
423
+ try {
424
+ // Stop file watcher
425
+ if (this.fileWatcher) {
426
+ await this.fileWatcher.stop();
427
+ }
428
+ // Stop application gracefully
429
+ if (this.runner) {
430
+ await this.runner.cleanup(this.options.signal);
431
+ }
432
+ // Calculate final stats
433
+ const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
434
+ const uptimeStr = this.formatUptime(uptime);
349
435
  if (this.options.showInfo) {
350
- console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Gracefully shutting down ${this.options.processName}...`)}`);
436
+ logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} stopped successfully`)}`, 'info');
437
+ logger_1.default.printLine(`${prefix} Final stats: ${uptimeStr} uptime, ${this.restartCount} restarts, ${this.crashCount} crashes`, 'info');
351
438
  }
352
- this.stop().then(() => {
353
- process.exit(0);
354
- });
355
- };
356
- process.on('SIGINT', () => handleSignal('SIGINT'));
357
- process.on('SIGTERM', () => handleSignal('SIGTERM'));
358
- process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
439
+ }
440
+ catch (error) {
441
+ logger_1.default.printLine(`Failed to stop application: ${error.message}`, 'error');
442
+ if (this.options.stopOnError) {
443
+ process.exit(1);
444
+ }
445
+ }
359
446
  }
360
447
  isActive() {
361
448
  return this.isRunning;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.20",
3
+ "version": "0.6.22",
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",