neex 0.6.88 → 0.6.90

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.
@@ -7,20 +7,18 @@ exports.addDevCommands = void 0;
7
7
  const dev_manager_js_1 = require("../dev-manager.js");
8
8
  const logger_manager_js_1 = require("../logger-manager.js");
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
- const path_1 = __importDefault(require("path"));
11
- const fs_1 = __importDefault(require("fs"));
12
10
  const figures_1 = __importDefault(require("figures"));
13
11
  function addDevCommands(program) {
14
12
  let devManager = null;
15
- let isShuttingDown = false;
16
- // Ultra-fast Express dev command optimized for speed
13
+ // Enhanced dev command with built-in TypeScript compilation
17
14
  program
18
15
  .command('dev [file]')
19
16
  .description('Start TypeScript development server with hot reloading (default: src/index.ts)')
20
17
  .option('-w, --watch <patterns>', 'Watch additional patterns (comma-separated)', 'src/**/*')
21
18
  .option('-i, --ignore <patterns>', 'Ignore patterns (comma-separated)', 'node_modules,dist,build,.git,coverage')
22
19
  .option('-e, --ext <extensions>', 'File extensions to watch (comma-separated)', 'ts,tsx,js,jsx,json')
23
- .option('-d, --delay <ms>', 'Delay before restart (ms)', parseInt, 800)
20
+ .option('-d, --delay <ms>', 'Delay before restart (ms)', parseInt, 500)
21
+ .option('--fast', 'Enable fast hot reload (shorter delays)')
24
22
  .option('-c, --no-color', 'Disable colored output')
25
23
  .option('-q, --quiet', 'Reduce output verbosity')
26
24
  .option('-v, --verbose', 'Verbose output')
@@ -33,13 +31,11 @@ function addDevCommands(program) {
33
31
  .option('--no-source-maps', 'Disable source maps')
34
32
  .option('--transpile-only', 'Skip type checking for faster compilation')
35
33
  .option('--node-args <args>', 'Additional Node.js arguments (comma-separated)', '')
36
- .option('--port <port>', 'Port to use if available', parseInt)
37
- .option('--host <host>', 'Host to bind to')
38
- .option('--hot-reload', 'Enable hot reloading for supported frameworks (e.g., Express)')
39
- .option('--middleware', 'Inject middleware for features like hot reloading')
40
34
  .action(async (file, options) => {
41
35
  try {
42
36
  const targetFile = file || 'src/index.ts';
37
+ // تنظیمات fast mode
38
+ const delay = options.fast ? 200 : options.delay;
43
39
  if (!options.quiet) {
44
40
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting ${chalk_1.default.cyan('neex dev')} for ${chalk_1.default.cyan(targetFile)}`, 'info');
45
41
  if (options.verbose) {
@@ -56,7 +52,7 @@ function addDevCommands(program) {
56
52
  watch: options.watch.split(',').map((p) => p.trim()),
57
53
  ignore: options.ignore.split(',').map((p) => p.trim()),
58
54
  extensions: options.ext.split(',').map((e) => e.trim()),
59
- delay: options.delay,
55
+ delay: delay,
60
56
  color: options.color,
61
57
  quiet: options.quiet,
62
58
  verbose: options.verbose,
@@ -68,11 +64,7 @@ function addDevCommands(program) {
68
64
  tsConfig: options.tsconfig,
69
65
  sourceMaps: options.sourceMaps,
70
66
  transpileOnly: options.transpileOnly,
71
- nodeArgs: options.nodeArgs ? options.nodeArgs.split(',').map((arg) => arg.trim()) : [],
72
- port: options.port,
73
- host: options.host,
74
- hotReload: options.hotReload,
75
- middleware: options.middleware
67
+ nodeArgs: options.nodeArgs ? options.nodeArgs.split(',').map((arg) => arg.trim()) : []
76
68
  });
77
69
  await devManager.start();
78
70
  }
@@ -91,9 +83,11 @@ function addDevCommands(program) {
91
83
  .command('dev:clean')
92
84
  .description('Clean development server cache and temporary files')
93
85
  .action(() => {
94
- const tempDir = path_1.default.join(process.cwd(), '.neex');
95
- if (fs_1.default.existsSync(tempDir)) {
96
- fs_1.default.rmSync(tempDir, { recursive: true, force: true });
86
+ const path = require('path');
87
+ const fs = require('fs');
88
+ const tempDir = path.join(process.cwd(), '.neex-temp');
89
+ if (fs.existsSync(tempDir)) {
90
+ fs.rmSync(tempDir, { recursive: true, force: true });
97
91
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Cleaned development cache`, 'info');
98
92
  }
99
93
  else {
@@ -105,13 +99,15 @@ function addDevCommands(program) {
105
99
  .description('Check TypeScript configuration and dependencies')
106
100
  .option('--tsconfig <path>', 'Path to TypeScript configuration file')
107
101
  .action((options) => {
102
+ const path = require('path');
103
+ const fs = require('fs');
108
104
  const configPath = options.tsconfig || 'tsconfig.json';
109
- if (!fs_1.default.existsSync(configPath)) {
105
+ if (!fs.existsSync(configPath)) {
110
106
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} TypeScript config not found: ${configPath}`, 'error');
111
107
  process.exit(1);
112
108
  }
113
109
  try {
114
- const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
110
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
115
111
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} TypeScript config is valid`, 'info');
116
112
  if (config.compilerOptions) {
117
113
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.dim('Compiler Options:')}`, 'info');
@@ -27,17 +27,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.DevManager = void 0;
30
- // src/dev-manager.ts - Development process manager for Node.js applications
30
+ // src/dev-manager.ts - Enhanced TypeScript development server with built-in compilation
31
31
  const child_process_1 = require("child_process");
32
32
  const chokidar_1 = require("chokidar");
33
33
  const logger_manager_js_1 = require("./logger-manager.js");
34
34
  const chalk_1 = __importDefault(require("chalk"));
35
+ const figures_1 = __importDefault(require("figures"));
35
36
  const path_1 = __importDefault(require("path"));
36
37
  const fs_1 = __importDefault(require("fs"));
37
38
  const lodash_1 = require("lodash");
38
39
  const ts = __importStar(require("typescript"));
39
- const crypto_1 = __importDefault(require("crypto"));
40
- const net_1 = __importDefault(require("net"));
41
40
  class DevManager {
42
41
  constructor(options) {
43
42
  this.process = null;
@@ -46,93 +45,10 @@ class DevManager {
46
45
  this.restartCount = 0;
47
46
  this.startTime = null;
48
47
  this.moduleCache = new Map();
49
- this.currentTempFile = null;
50
- this.serverInfo = null;
51
- this.hotReloadClient = null;
52
- this.isExpressApp = false;
53
- this.lastCompileTime = 0;
54
- this.performanceMetrics = {
55
- averageRestartTime: 0,
56
- totalRestarts: 0,
57
- cacheHits: 0,
58
- cacheMisses: 0
59
- };
48
+ this.fileWatcher = new Map();
60
49
  this.options = options;
61
- this.tempDir = path_1.default.join(process.cwd(), '.neex');
62
- this.debouncedRestart = (0, lodash_1.debounce)(this.restart.bind(this), Math.max(options.delay, 100));
50
+ this.debouncedRestart = (0, lodash_1.debounce)(this.restart.bind(this), options.delay);
63
51
  this.tsCompilerOptions = this.loadTsConfig();
64
- this.setupTempDir();
65
- this.detectExpressApp();
66
- this.setupHotReloadClient();
67
- }
68
- setupTempDir() {
69
- if (fs_1.default.existsSync(this.tempDir)) {
70
- try {
71
- fs_1.default.rmSync(this.tempDir, { recursive: true, force: true });
72
- }
73
- catch (error) {
74
- // Ignore cleanup errors
75
- }
76
- }
77
- fs_1.default.mkdirSync(this.tempDir, { recursive: true });
78
- // Create cache directory
79
- const cacheDir = path_1.default.join(this.tempDir, 'cache');
80
- fs_1.default.mkdirSync(cacheDir, { recursive: true });
81
- }
82
- detectExpressApp() {
83
- try {
84
- const fileContent = fs_1.default.readFileSync(this.options.file, 'utf8');
85
- this.isExpressApp = /import.*express|require.*express|app\.listen|server\.listen/.test(fileContent);
86
- if (this.options.verbose) {
87
- logger_manager_js_1.loggerManager.printLine(`Detected ${this.isExpressApp ? 'Express' : 'Node.js'} application`, 'info');
88
- }
89
- }
90
- catch (error) {
91
- this.isExpressApp = false;
92
- }
93
- }
94
- setupHotReloadClient() {
95
- if (!this.options.hotReload || !this.isExpressApp)
96
- return;
97
- this.hotReloadClient = `
98
- // Hot reload client for Express development
99
- (function() {
100
- if (typeof window === 'undefined') return;
101
-
102
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
103
- const wsUrl = protocol + '//' + window.location.host + '/__neex_hot_reload';
104
-
105
- function connect() {
106
- const ws = new WebSocket(wsUrl);
107
-
108
- ws.onopen = function() {
109
- console.log('🔥 Hot reload connected');
110
- };
111
-
112
- ws.onmessage = function(event) {
113
- const data = JSON.parse(event.data);
114
-
115
- if (data.type === 'reload') {
116
- console.log('🔄 Reloading page...');
117
- window.location.reload();
118
- } else if (data.type === 'error') {
119
- console.error('🚨 Build error:', data.message);
120
- }
121
- };
122
-
123
- ws.onclose = function() {
124
- console.log('🔌 Hot reload disconnected, retrying...');
125
- setTimeout(connect, 1000);
126
- };
127
-
128
- ws.onerror = function(error) {
129
- console.error('❌ Hot reload error:', error);
130
- };
131
- }
132
-
133
- connect();
134
- })();
135
- `;
136
52
  }
137
53
  loadTsConfig() {
138
54
  const configPath = this.options.tsConfig || 'tsconfig.json';
@@ -141,6 +57,8 @@ class DevManager {
141
57
  module: ts.ModuleKind.CommonJS,
142
58
  moduleResolution: ts.ModuleResolutionKind.NodeJs,
143
59
  allowJs: true,
60
+ outDir: undefined,
61
+ rootDir: undefined,
144
62
  strict: false,
145
63
  esModuleInterop: true,
146
64
  skipLibCheck: true,
@@ -150,519 +68,399 @@ class DevManager {
150
68
  sourceMap: this.options.sourceMaps,
151
69
  inlineSourceMap: false,
152
70
  inlineSources: false,
153
- removeComments: false,
154
- preserveConstEnums: false,
155
- isolatedModules: true,
156
- incremental: true,
157
- tsBuildInfoFile: path_1.default.join(this.tempDir, 'tsconfig.tsbuildinfo'),
158
71
  };
159
72
  if (fs_1.default.existsSync(configPath)) {
160
73
  try {
161
74
  const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
162
- if (!configFile.error) {
163
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path_1.default.dirname(configPath));
164
- if (parsedConfig.errors.length === 0) {
165
- return { ...defaultOptions, ...parsedConfig.options };
166
- }
75
+ if (configFile.error) {
76
+ logger_manager_js_1.loggerManager.printLine(`Error reading tsconfig.json: ${configFile.error.messageText}`, 'warn');
77
+ return defaultOptions;
167
78
  }
79
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path_1.default.dirname(configPath));
80
+ if (parsedConfig.errors.length > 0) {
81
+ logger_manager_js_1.loggerManager.printLine(`Error parsing tsconfig.json: ${parsedConfig.errors[0].messageText}`, 'warn');
82
+ return defaultOptions;
83
+ }
84
+ // Override some options for development
85
+ const options = { ...parsedConfig.options, ...defaultOptions };
86
+ if (this.options.verbose) {
87
+ logger_manager_js_1.loggerManager.printLine(`Loaded TypeScript config from ${configPath}`, 'info');
88
+ }
89
+ return options;
168
90
  }
169
91
  catch (error) {
170
- // Fall back to defaults
92
+ logger_manager_js_1.loggerManager.printLine(`Failed to load tsconfig.json: ${error.message}`, 'warn');
171
93
  }
172
94
  }
173
95
  return defaultOptions;
174
96
  }
175
97
  loadEnvFile() {
176
- const envFiles = [
177
- this.options.envFile,
178
- '.env.development',
179
- '.env.local',
180
- '.env'
181
- ].filter(Boolean);
182
- for (const envFile of envFiles) {
183
- if (fs_1.default.existsSync(envFile)) {
184
- try {
185
- const envContent = fs_1.default.readFileSync(envFile, 'utf8');
186
- const lines = envContent.split('\n');
187
- for (const line of lines) {
188
- const trimmed = line.trim();
189
- if (trimmed && !trimmed.startsWith('#')) {
190
- const [key, ...values] = trimmed.split('=');
191
- if (key && values.length > 0) {
192
- const value = values.join('=').trim().replace(/^["']|["']$/g, '');
193
- if (!process.env[key.trim()]) {
194
- process.env[key.trim()] = value;
195
- }
196
- }
197
- }
198
- }
199
- if (this.options.verbose) {
200
- logger_manager_js_1.loggerManager.printLine(`Loaded environment from ${envFile}`, 'info');
201
- }
202
- break;
203
- }
204
- catch (error) {
205
- if (this.options.verbose) {
206
- logger_manager_js_1.loggerManager.printLine(`Failed to load ${envFile}: ${error.message}`, 'warn');
207
- }
208
- }
209
- }
210
- }
211
- }
212
- createHash(content) {
213
- return crypto_1.default.createHash('sha256').update(content).digest('hex').substring(0, 16);
214
- }
215
- extractDependencies(sourceCode, filePath) {
216
- const dependencies = [];
217
- const importRegex = /(?:import|require)\s*(?:\([^)]*\)|[^;]+?from\s+)?['"`]([^'"`]+)['"`]/g;
218
- let match;
219
- while ((match = importRegex.exec(sourceCode)) !== null) {
220
- const importPath = match[1];
221
- if (importPath.startsWith('.')) {
222
- let resolvedPath = path_1.default.resolve(path_1.default.dirname(filePath), importPath);
223
- // Try to resolve with extensions
224
- if (!fs_1.default.existsSync(resolvedPath)) {
225
- for (const ext of this.options.extensions.map(e => `.${e}`)) {
226
- const withExt = resolvedPath + ext;
227
- if (fs_1.default.existsSync(withExt)) {
228
- resolvedPath = withExt;
229
- break;
98
+ if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
99
+ try {
100
+ const envContent = fs_1.default.readFileSync(this.options.envFile, 'utf8');
101
+ const lines = envContent.split('\n');
102
+ let loadedCount = 0;
103
+ for (const line of lines) {
104
+ const trimmed = line.trim();
105
+ if (trimmed && !trimmed.startsWith('#')) {
106
+ const [key, ...values] = trimmed.split('=');
107
+ if (key && values.length > 0) {
108
+ process.env[key.trim()] = values.join('=').trim().replace(/^["']|["']$/g, '');
109
+ loadedCount++;
230
110
  }
231
111
  }
232
112
  }
233
- // Try index files
234
- if (!fs_1.default.existsSync(resolvedPath)) {
235
- for (const ext of this.options.extensions.map(e => `.${e}`)) {
236
- const indexPath = path_1.default.join(resolvedPath, 'index' + ext);
237
- if (fs_1.default.existsSync(indexPath)) {
238
- resolvedPath = indexPath;
239
- break;
240
- }
241
- }
113
+ if (!this.options.quiet && loadedCount > 0) {
114
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.dim(figures_1.default.info)} Loaded ${loadedCount} env variable${loadedCount > 1 ? 's' : ''} from ${path_1.default.basename(this.options.envFile)}`, 'info');
242
115
  }
243
- if (fs_1.default.existsSync(resolvedPath)) {
244
- dependencies.push(resolvedPath);
116
+ else if (this.options.verbose) {
117
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.dim(figures_1.default.info)} No env variables found in ${this.options.envFile}`, 'info');
245
118
  }
246
119
  }
120
+ catch (error) {
121
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Failed to load ${this.options.envFile}: ${error.message}`, 'warn');
122
+ }
247
123
  }
248
- return dependencies;
249
- }
250
- extractExports(sourceCode) {
251
- const exports = [];
252
- const exportRegex = /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
253
- let match;
254
- while ((match = exportRegex.exec(sourceCode)) !== null) {
255
- exports.push(match[1]);
256
- }
257
- return exports;
258
124
  }
259
- compileModule(filePath, forceRecompile = false) {
125
+ compileTypeScript(filePath) {
260
126
  const absolutePath = path_1.default.resolve(filePath);
261
- const compileStart = Date.now();
127
+ // Check cache first
128
+ const cached = this.moduleCache.get(absolutePath);
129
+ if (cached && !this.options.transpileOnly) {
130
+ return cached;
131
+ }
262
132
  try {
263
133
  const sourceCode = fs_1.default.readFileSync(absolutePath, 'utf8');
264
- const hash = this.createHash(sourceCode);
265
- const cached = this.moduleCache.get(absolutePath);
266
- // Check if we can use cached version
267
- if (!forceRecompile && cached && cached.hash === hash) {
268
- this.performanceMetrics.cacheHits++;
269
- return cached;
134
+ const dependencies = new Set();
135
+ // Extract import/require dependencies
136
+ const importRegex = /(?:import|require)\s*(?:\(.*?\)|.*?from\s+)['"`]([^'"`]+)['"`]/g;
137
+ let match;
138
+ while ((match = importRegex.exec(sourceCode)) !== null) {
139
+ if (!match[1].startsWith('.'))
140
+ continue;
141
+ const depPath = path_1.default.resolve(path_1.default.dirname(absolutePath), match[1]);
142
+ dependencies.add(depPath);
270
143
  }
271
- this.performanceMetrics.cacheMisses++;
272
- const dependencies = this.extractDependencies(sourceCode, absolutePath);
273
- const exports = this.extractExports(sourceCode);
274
- // Fast transpile without type checking for development
275
- const result = ts.transpileModule(sourceCode, {
276
- compilerOptions: this.tsCompilerOptions,
277
- fileName: absolutePath,
278
- reportDiagnostics: this.options.verbose
279
- });
280
- const moduleInfo = {
144
+ let result;
145
+ if (this.options.transpileOnly) {
146
+ // Fast transpile without type checking
147
+ result = ts.transpileModule(sourceCode, {
148
+ compilerOptions: this.tsCompilerOptions,
149
+ fileName: absolutePath,
150
+ reportDiagnostics: false
151
+ });
152
+ }
153
+ else {
154
+ // Full compilation with type checking
155
+ const program = ts.createProgram([absolutePath], this.tsCompilerOptions);
156
+ const sourceFile = program.getSourceFile(absolutePath);
157
+ if (!sourceFile) {
158
+ throw new Error(`Could not load source file: ${absolutePath}`);
159
+ }
160
+ // Check for errors
161
+ const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
162
+ if (diagnostics.length > 0) {
163
+ const errors = diagnostics.map(diagnostic => {
164
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
165
+ if (diagnostic.file && diagnostic.start !== undefined) {
166
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
167
+ return `${path_1.default.relative(process.cwd(), diagnostic.file.fileName)}:${line + 1}:${character + 1} - ${message}`;
168
+ }
169
+ return message;
170
+ });
171
+ logger_manager_js_1.loggerManager.printLine(`TypeScript compilation errors:\n${errors.join('\n')}`, 'error');
172
+ if (!this.options.quiet) {
173
+ process.exit(1);
174
+ }
175
+ }
176
+ result = ts.transpileModule(sourceCode, {
177
+ compilerOptions: this.tsCompilerOptions,
178
+ fileName: absolutePath,
179
+ reportDiagnostics: true
180
+ });
181
+ }
182
+ const compiled = {
281
183
  code: result.outputText,
282
184
  map: result.sourceMapText,
283
- hash,
284
- timestamp: Date.now(),
285
- dependencies,
286
- exports
185
+ dependencies
287
186
  };
288
- this.moduleCache.set(absolutePath, moduleInfo);
289
- if (this.options.verbose) {
290
- const compileTime = Date.now() - compileStart;
291
- logger_manager_js_1.loggerManager.printLine(`Compiled ${path_1.default.relative(process.cwd(), filePath)} (${compileTime}ms)`, 'info');
292
- }
293
- return moduleInfo;
187
+ this.moduleCache.set(absolutePath, compiled);
188
+ return compiled;
294
189
  }
295
190
  catch (error) {
296
191
  logger_manager_js_1.loggerManager.printLine(`Compilation error in ${filePath}: ${error.message}`, 'error');
297
192
  throw error;
298
193
  }
299
194
  }
300
- invalidateModuleCache(filePath) {
301
- const absolutePath = path_1.default.resolve(filePath);
302
- const toRemove = new Set();
303
- // Remove the file itself
304
- toRemove.add(absolutePath);
305
- // Remove any modules that depend on this file (recursive)
306
- const findDependents = (targetPath) => {
307
- for (const [cachedPath, info] of this.moduleCache.entries()) {
308
- if (info.dependencies.includes(targetPath) && !toRemove.has(cachedPath)) {
309
- toRemove.add(cachedPath);
310
- findDependents(cachedPath);
311
- }
312
- }
313
- };
314
- findDependents(absolutePath);
315
- // Remove all affected modules
316
- for (const pathToRemove of toRemove) {
317
- this.moduleCache.delete(pathToRemove);
318
- }
319
- if (this.options.verbose && toRemove.size > 1) {
320
- logger_manager_js_1.loggerManager.printLine(`Invalidated ${toRemove.size} modules`, 'info');
321
- }
322
- }
323
- async findFreePort(startPort = 3000) {
324
- return new Promise((resolve, reject) => {
325
- const server = net_1.default.createServer();
326
- server.listen(startPort, () => {
327
- const port = server.address().port;
328
- server.close(() => resolve(port));
329
- });
330
- server.on('error', () => {
331
- this.findFreePort(startPort + 1).then(resolve).catch(reject);
332
- });
333
- });
334
- }
335
- createExecutableFile() {
336
- const startCompile = Date.now();
337
- // Always force recompile the main file
338
- const mainModule = this.compileModule(this.options.file, true);
339
- // Create a unique temp file
340
- const timestamp = Date.now();
341
- const random = Math.random().toString(36).substr(2, 9);
342
- const tempFile = path_1.default.join(this.tempDir, `server-${timestamp}-${random}.js`);
343
- let code = mainModule.code;
344
- // Add hot reload middleware for Express apps
345
- if (this.isExpressApp && this.options.hotReload) {
346
- const hotReloadMiddleware = `
347
- // Hot reload middleware injected by neex
348
- const originalListen = require('http').Server.prototype.listen;
349
- require('http').Server.prototype.listen = function(...args) {
350
- const server = originalListen.apply(this, args);
351
-
352
- // Setup WebSocket for hot reload
353
- const WebSocket = require('ws');
354
- const wss = new WebSocket.Server({ server, path: '/__neex_hot_reload' });
355
-
356
- global.__neex_hot_reload = (type, data) => {
357
- wss.clients.forEach(client => {
358
- if (client.readyState === WebSocket.OPEN) {
359
- client.send(JSON.stringify({ type, ...data }));
360
- }
361
- });
362
- };
363
-
364
- return server;
365
- };
366
- `;
367
- code = hotReloadMiddleware + '\n' + code;
195
+ createTempFile(compiled, originalPath) {
196
+ const tempDir = path_1.default.join(process.cwd(), '.neex-temp');
197
+ if (!fs_1.default.existsSync(tempDir)) {
198
+ fs_1.default.mkdirSync(tempDir, { recursive: true });
368
199
  }
369
- // Add source map support
370
- if (mainModule.map && this.options.sourceMaps) {
200
+ const tempFile = path_1.default.join(tempDir, `${path_1.default.basename(originalPath, path_1.default.extname(originalPath))}-${Date.now()}.js`);
201
+ let code = compiled.code;
202
+ // Handle source maps
203
+ if (compiled.map && this.options.sourceMaps) {
371
204
  const mapFile = tempFile + '.map';
372
- fs_1.default.writeFileSync(mapFile, mainModule.map);
205
+ fs_1.default.writeFileSync(mapFile, compiled.map);
373
206
  code += `\n//# sourceMappingURL=${path_1.default.basename(mapFile)}`;
374
207
  }
375
208
  fs_1.default.writeFileSync(tempFile, code);
376
- // Clean up old temp file
377
- if (this.currentTempFile && fs_1.default.existsSync(this.currentTempFile)) {
209
+ return tempFile;
210
+ }
211
+ cleanupTempFiles() {
212
+ const tempDir = path_1.default.join(process.cwd(), '.neex-temp');
213
+ if (fs_1.default.existsSync(tempDir)) {
378
214
  try {
379
- fs_1.default.unlinkSync(this.currentTempFile);
380
- const mapFile = this.currentTempFile + '.map';
381
- if (fs_1.default.existsSync(mapFile)) {
382
- fs_1.default.unlinkSync(mapFile);
383
- }
215
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
384
216
  }
385
217
  catch (error) {
386
218
  // Ignore cleanup errors
387
219
  }
388
220
  }
389
- this.currentTempFile = tempFile;
390
- this.lastCompileTime = Date.now() - startCompile;
391
- return tempFile;
392
221
  }
393
222
  async getExecuteCommand() {
394
223
  if (this.options.execCommand) {
395
224
  const parts = this.options.execCommand.split(' ');
396
- return { command: parts[0], args: parts.slice(1) };
225
+ return { command: parts[0], args: [...parts.slice(1)] };
397
226
  }
398
- const executableFile = this.createExecutableFile();
399
- const args = [...this.options.nodeArgs, executableFile];
400
- // Add Node.js flags
401
- if (this.options.inspect)
227
+ // Compile TypeScript file
228
+ const compiled = this.compileTypeScript(this.options.file);
229
+ const tempFile = this.createTempFile(compiled, this.options.file);
230
+ const args = [...this.options.nodeArgs, tempFile];
231
+ if (this.options.inspect) {
402
232
  args.unshift('--inspect');
403
- if (this.options.inspectBrk)
233
+ }
234
+ if (this.options.inspectBrk) {
404
235
  args.unshift('--inspect-brk');
405
- if (this.options.sourceMaps)
236
+ }
237
+ // Enable source map support
238
+ if (this.options.sourceMaps) {
406
239
  args.unshift('--enable-source-maps');
407
- // Memory optimization
408
- args.unshift('--max-old-space-size=4096');
409
- args.unshift('--optimize-for-size');
240
+ }
410
241
  return { command: 'node', args };
411
242
  }
412
243
  clearConsole() {
413
244
  if (this.options.clearConsole && process.stdout.isTTY) {
414
- process.stdout.write('\x1Bc');
245
+ process.stdout.write('\x1b[2J\x1b[0f');
415
246
  }
416
247
  }
417
248
  async startProcess() {
418
249
  var _a, _b;
419
- if (this.process)
250
+ if (this.process) {
420
251
  return;
421
- const restartStart = Date.now();
252
+ }
422
253
  this.loadEnvFile();
423
254
  try {
424
255
  const { command, args } = await this.getExecuteCommand();
425
- // Find free port for Express apps
426
- if (this.isExpressApp && !process.env.PORT) {
427
- const port = await this.findFreePort(this.options.port || 3000);
428
- process.env.PORT = port.toString();
256
+ if (this.options.verbose) {
257
+ logger_manager_js_1.loggerManager.printLine(`Executing: ${command} ${args.join(' ')}`, 'info');
429
258
  }
430
259
  this.process = (0, child_process_1.spawn)(command, args, {
431
260
  stdio: ['ignore', 'pipe', 'pipe'],
261
+ shell: false,
432
262
  env: {
433
263
  ...process.env,
434
264
  NODE_ENV: process.env.NODE_ENV || 'development',
435
265
  FORCE_COLOR: this.options.color ? '1' : '0',
436
- NEEX_DEV_MODE: '1',
437
- NEEX_HOT_RELOAD: this.options.hotReload ? '1' : '0',
266
+ TS_NODE_DEV: '1',
267
+ NEEX_DEV: '1'
438
268
  },
439
- detached: false
269
+ detached: true
440
270
  });
441
271
  this.startTime = new Date();
442
272
  this.restartCount++;
443
- // Update performance metrics
444
- if (this.restartCount > 1) {
445
- const restartTime = Date.now() - restartStart;
446
- this.performanceMetrics.averageRestartTime =
447
- (this.performanceMetrics.averageRestartTime * (this.restartCount - 2) + restartTime) / (this.restartCount - 1);
448
- this.performanceMetrics.totalRestarts++;
449
- }
450
273
  if (!this.options.quiet) {
451
274
  const timestamp = new Date().toLocaleTimeString();
452
- const fileRelative = path_1.default.relative(process.cwd(), this.options.file);
453
- const port = process.env.PORT ? `:${process.env.PORT}` : '';
454
- const url = this.isExpressApp ? `http://localhost${port}` : '';
455
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green('▶')} ${chalk_1.default.cyan(fileRelative)} ${chalk_1.default.dim(`#${this.restartCount} ${timestamp}`)}`, 'info');
456
- if (url) {
457
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue('🌐')} ${chalk_1.default.underline(url)}`, 'info');
458
- }
459
- if (this.options.verbose) {
460
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.dim('Compile time:')} ${this.lastCompileTime}ms`, 'info');
461
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.dim('Cache hits:')} ${this.performanceMetrics.cacheHits}`, 'info');
462
- }
275
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Started ${chalk_1.default.cyan(path_1.default.relative(process.cwd(), this.options.file))} ${chalk_1.default.dim(`(${timestamp})`)}`, 'info');
463
276
  }
464
- this.serverInfo = {
465
- port: parseInt(process.env.PORT || '3000'),
466
- host: this.options.host || 'localhost',
467
- pid: this.process.pid,
468
- startTime: Date.now(),
469
- restarts: this.restartCount
470
- };
471
- // Handle stdout/stderr with better formatting
472
277
  (_a = this.process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
473
- const output = data.toString();
474
278
  if (!this.options.quiet) {
475
- process.stdout.write(output);
279
+ process.stdout.write(data);
476
280
  }
477
281
  });
478
282
  (_b = this.process.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
479
- const output = data.toString();
480
283
  if (!this.options.quiet) {
481
- process.stderr.write(output);
284
+ process.stderr.write(data);
482
285
  }
483
286
  });
484
287
  this.process.on('error', (error) => {
485
288
  logger_manager_js_1.loggerManager.printLine(`Process error: ${error.message}`, 'error');
486
- if (this.options.hotReload && global.__neex_hot_reload) {
487
- global.__neex_hot_reload('error', { message: error.message });
488
- }
489
289
  });
490
290
  this.process.on('exit', (code, signal) => {
491
291
  if (this.process) {
492
292
  this.process = null;
493
- this.serverInfo = null;
494
- if (!this.isRestarting && code !== 0) {
495
- const duration = this.startTime ? Date.now() - this.startTime.getTime() : 0;
496
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red('✖')} Process exited with code ${code} (${duration}ms)`, 'error');
293
+ if (!this.isRestarting) {
294
+ if (code !== 0) {
295
+ const duration = this.startTime ? Date.now() - this.startTime.getTime() : 0;
296
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Process exited with code ${code} after ${duration}ms`, 'error');
297
+ }
497
298
  }
498
299
  }
499
300
  });
500
301
  }
501
302
  catch (error) {
502
- logger_manager_js_1.loggerManager.printLine(`Failed to start: ${error.message}`, 'error');
303
+ logger_manager_js_1.loggerManager.printLine(`Failed to start process: ${error.message}`, 'error');
503
304
  throw error;
504
305
  }
505
306
  }
506
307
  async stopProcess() {
507
- if (!this.process)
308
+ if (!this.process) {
508
309
  return;
310
+ }
509
311
  return new Promise((resolve) => {
312
+ if (!this.process) {
313
+ resolve();
314
+ return;
315
+ }
510
316
  const proc = this.process;
511
317
  this.process = null;
512
- this.serverInfo = null;
513
318
  const cleanup = () => {
319
+ if (!this.options.quiet) {
320
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.square)} Stopped process`, 'info');
321
+ }
322
+ this.cleanupTempFiles();
514
323
  resolve();
515
324
  };
516
325
  proc.on('exit', cleanup);
517
326
  proc.on('error', cleanup);
518
327
  try {
519
- proc.kill('SIGTERM');
520
- // Force kill after timeout
521
- setTimeout(() => {
522
- if (!proc.killed) {
523
- proc.kill('SIGKILL');
524
- }
525
- cleanup();
526
- }, 2000);
328
+ if (proc.pid) {
329
+ // Kill process group
330
+ process.kill(-proc.pid, 'SIGTERM');
331
+ // Fallback after timeout
332
+ setTimeout(() => {
333
+ if (proc.pid && !proc.killed) {
334
+ try {
335
+ process.kill(-proc.pid, 'SIGKILL');
336
+ }
337
+ catch (e) {
338
+ // Ignore
339
+ }
340
+ }
341
+ }, 3000);
342
+ }
527
343
  }
528
344
  catch (error) {
345
+ // Process might already be dead
529
346
  cleanup();
530
347
  }
531
348
  });
532
349
  }
350
+ invalidateCache(filePath) {
351
+ const absolutePath = path_1.default.resolve(filePath);
352
+ // Remove from cache
353
+ this.moduleCache.delete(absolutePath);
354
+ // Remove dependent modules from cache
355
+ for (const [cachedPath, module] of this.moduleCache.entries()) {
356
+ if (module.dependencies.has(absolutePath)) {
357
+ this.moduleCache.delete(cachedPath);
358
+ }
359
+ }
360
+ }
533
361
  async restart() {
534
- if (this.isRestarting)
362
+ if (this.isRestarting) {
535
363
  return;
364
+ }
536
365
  this.isRestarting = true;
537
- // Clear console for better UX
538
- this.clearConsole();
539
- if (!this.options.quiet) {
540
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow('⟳')} Restarting server...`, 'info');
366
+ if (this.options.clearConsole) {
367
+ this.clearConsole();
541
368
  }
542
- // Notify hot reload clients
543
- if (this.options.hotReload && global.__neex_hot_reload) {
544
- global.__neex_hot_reload('reload', {});
369
+ if (!this.options.quiet) {
370
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.arrowRight)} Restarting due to changes...`, 'info');
545
371
  }
546
- // Stop current process
372
+ // Clear module cache
373
+ this.moduleCache.clear();
547
374
  await this.stopProcess();
548
- // Start new process
375
+ // Small delay to ensure cleanup
376
+ await new Promise(resolve => setTimeout(resolve, 100));
549
377
  await this.startProcess();
550
378
  this.isRestarting = false;
551
379
  }
552
380
  setupWatcher() {
553
381
  const watchPatterns = this.options.watch;
554
- // Optimized ignore patterns for Express projects
555
382
  const ignored = [
556
- 'node_modules/**',
557
- '.git/**',
558
- 'dist/**',
559
- 'build/**',
560
- '.neex/**',
561
- 'coverage/**',
562
- 'logs/**',
383
+ '**/node_modules/**',
384
+ '**/.git/**',
385
+ '**/dist/**',
386
+ '**/build/**',
387
+ '**/.neex-temp/**',
563
388
  '**/*.log',
564
389
  '**/*.d.ts',
565
- '**/*.map',
566
- '**/*.tsbuildinfo',
567
- '**/package-lock.json',
568
- '**/yarn.lock',
569
- ...this.options.ignore
390
+ ...this.options.ignore.map(pattern => `**/${pattern}/**`)
570
391
  ];
571
392
  this.watcher = (0, chokidar_1.watch)(watchPatterns, {
572
393
  ignored,
573
394
  ignoreInitial: true,
574
395
  followSymlinks: false,
575
396
  usePolling: false,
576
- atomic: 100,
397
+ atomic: 200,
577
398
  awaitWriteFinish: {
578
- stabilityThreshold: 150,
579
- pollInterval: 100
399
+ stabilityThreshold: 100,
400
+ pollInterval: 50
580
401
  }
581
402
  });
582
403
  this.watcher.on('change', (filePath) => {
583
- this.invalidateModuleCache(filePath);
404
+ this.invalidateCache(filePath);
584
405
  if (this.options.verbose) {
585
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue('●')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
406
+ logger_manager_js_1.loggerManager.printLine(`File changed: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
586
407
  }
587
408
  this.debouncedRestart();
588
409
  });
589
410
  this.watcher.on('add', (filePath) => {
590
411
  if (this.options.verbose) {
591
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green('+')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
412
+ logger_manager_js_1.loggerManager.printLine(`File added: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
592
413
  }
593
414
  this.debouncedRestart();
594
415
  });
595
416
  this.watcher.on('unlink', (filePath) => {
596
- this.invalidateModuleCache(filePath);
417
+ this.invalidateCache(filePath);
597
418
  if (this.options.verbose) {
598
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red('-')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
419
+ logger_manager_js_1.loggerManager.printLine(`File removed: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
599
420
  }
600
421
  this.debouncedRestart();
601
422
  });
602
423
  this.watcher.on('error', (error) => {
603
424
  logger_manager_js_1.loggerManager.printLine(`Watcher error: ${error.message}`, 'error');
604
425
  });
426
+ if (this.options.verbose) {
427
+ logger_manager_js_1.loggerManager.printLine(`Watching: ${watchPatterns.join(', ')}`, 'info');
428
+ logger_manager_js_1.loggerManager.printLine(`Ignoring: ${ignored.join(', ')}`, 'info');
429
+ }
605
430
  }
606
431
  async start() {
432
+ // Check if target file exists
607
433
  if (!fs_1.default.existsSync(this.options.file)) {
608
434
  throw new Error(`Target file not found: ${this.options.file}`);
609
435
  }
436
+ // Validate TypeScript file
610
437
  const ext = path_1.default.extname(this.options.file);
611
- if (!this.options.extensions.map(e => `.${e}`).includes(ext)) {
438
+ if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
612
439
  throw new Error(`Unsupported file extension: ${ext}`);
613
440
  }
614
- // Clear any existing cache
615
- this.moduleCache.clear();
616
- this.setupTempDir();
617
- if (!this.options.quiet) {
618
- const appType = this.isExpressApp ? 'Express' : 'Node.js';
619
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue('⚡')} Starting ${appType} development server...`, 'info');
441
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting TypeScript development server...`, 'info');
442
+ // Show configuration in verbose mode
443
+ if (this.options.verbose) {
444
+ logger_manager_js_1.loggerManager.printLine(`Target file: ${this.options.file}`, 'info');
445
+ logger_manager_js_1.loggerManager.printLine(`Watch patterns: ${this.options.watch.join(', ')}`, 'info');
446
+ logger_manager_js_1.loggerManager.printLine(`Restart delay: ${this.options.delay}ms`, 'info');
447
+ logger_manager_js_1.loggerManager.printLine(`Transpile only: ${this.options.transpileOnly}`, 'info');
448
+ logger_manager_js_1.loggerManager.printLine(`Source maps: ${this.options.sourceMaps}`, 'info');
620
449
  }
621
450
  this.setupWatcher();
622
451
  await this.startProcess();
623
- if (!this.options.quiet) {
624
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green('✓')} Watching for changes...`, 'info');
625
- if (this.options.hotReload) {
626
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue('🔥')} Hot reload enabled`, 'info');
627
- }
628
- }
452
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} TypeScript development server started. Watching for changes...`, 'info');
629
453
  }
630
454
  async stop() {
631
- if (!this.options.quiet) {
632
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow('⏹')} Stopping development server...`, 'info');
633
- }
455
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Stopping development server...`, 'info');
634
456
  if (this.watcher) {
635
457
  await this.watcher.close();
636
458
  this.watcher = null;
637
459
  }
638
460
  await this.stopProcess();
639
- // Cleanup temp files
640
- if (fs_1.default.existsSync(this.tempDir)) {
641
- try {
642
- fs_1.default.rmSync(this.tempDir, { recursive: true, force: true });
643
- }
644
- catch (error) {
645
- // Ignore cleanup errors
646
- }
461
+ if (this.restartCount > 0) {
462
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Development server stopped after ${this.restartCount} restart(s)`, 'info');
647
463
  }
648
- this.moduleCache.clear();
649
- if (this.options.verbose) {
650
- logger_manager_js_1.loggerManager.printLine('Development server stopped', 'info');
651
- }
652
- }
653
- getServerInfo() {
654
- return this.serverInfo;
655
- }
656
- getPerformanceMetrics() {
657
- return {
658
- ...this.performanceMetrics,
659
- modulesCached: this.moduleCache.size,
660
- lastCompileTime: this.lastCompileTime
661
- };
662
- }
663
- async forceRestart() {
664
- this.moduleCache.clear();
665
- await this.restart();
666
464
  }
667
465
  }
668
466
  exports.DevManager = DevManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.88",
3
+ "version": "0.6.90",
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",