UNPKG

neex

Version:

Neex - Modern Fullstack Framework Built on Express and Next.js. Fast to Start, Easy to Build, Ready to Deploy

464 lines (463 loc) 18.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DevManager = void 0; // src/dev-manager.ts - Ultra-fast TypeScript development server like tsx const child_process_1 = require("child_process"); const chokidar_1 = require("chokidar"); const logger_manager_js_1 = require("./logger-manager.js"); const chalk_1 = __importDefault(require("chalk")); const figures_1 = __importDefault(require("figures")); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const lodash_1 = require("lodash"); const ts = __importStar(require("typescript")); const crypto_1 = __importDefault(require("crypto")); class DevManager { constructor(options) { this.process = null; this.watcher = null; this.isRestarting = false; this.restartCount = 0; this.startTime = null; this.moduleCache = new Map(); this.currentTempFile = null; this.isShuttingDown = false; this.options = options; this.tempDir = path_1.default.join(process.cwd(), '.neex-temp'); this.debouncedRestart = (0, lodash_1.debounce)(this.restart.bind(this), Math.max(options.delay, 100)); this.tsCompilerOptions = this.loadTsConfig(); this.setupTempDir(); } setupTempDir() { if (fs_1.default.existsSync(this.tempDir)) { try { fs_1.default.rmSync(this.tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } } fs_1.default.mkdirSync(this.tempDir, { recursive: true }); } loadTsConfig() { const configPath = this.options.tsConfig || 'tsconfig.json'; const defaultOptions = { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true, strict: false, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: false, sourceMap: this.options.sourceMaps, inlineSourceMap: false, inlineSources: false, removeComments: false, preserveConstEnums: false, isolatedModules: true, // For faster compilation }; if (fs_1.default.existsSync(configPath)) { try { const configFile = ts.readConfigFile(configPath, ts.sys.readFile); if (!configFile.error) { const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path_1.default.dirname(configPath)); if (parsedConfig.errors.length === 0) { return { ...defaultOptions, ...parsedConfig.options }; } } } catch (error) { // Fall back to defaults } } return defaultOptions; } loadEnvFile() { let count = 0; const envFile = this.options.envFile; if (envFile && fs_1.default.existsSync(envFile)) { try { const envContent = fs_1.default.readFileSync(envFile, 'utf8'); const lines = envContent.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...values] = trimmed.split('='); if (key && values.length > 0) { process.env[key.trim()] = values.join('=').trim().replace(/^["']|["']$/g, ''); count++; } } } if (!this.options.quiet) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Loaded ${count} env variables from ${envFile}`, 'info'); } } catch (error) { if (this.options.verbose) { logger_manager_js_1.loggerManager.printLine(`Failed to load ${envFile}: ${error.message}`, 'warn'); } } } } createHash(content) { return crypto_1.default.createHash('md5').update(content).digest('hex'); } extractDependencies(sourceCode, filePath) { const dependencies = []; const importRegex = /(?:import|require)\s*(?:\([^)]*\)|[^;]+?from\s+)?['"`]([^'"`]+)['"`]/g; let match; while ((match = importRegex.exec(sourceCode)) !== null) { const importPath = match[1]; if (importPath.startsWith('.')) { let resolvedPath = path_1.default.resolve(path_1.default.dirname(filePath), importPath); // Try to resolve with extensions if (!fs_1.default.existsSync(resolvedPath)) { for (const ext of ['.ts', '.tsx', '.js', '.jsx']) { const withExt = resolvedPath + ext; if (fs_1.default.existsSync(withExt)) { resolvedPath = withExt; break; } } } // Try index files if (!fs_1.default.existsSync(resolvedPath)) { for (const ext of ['.ts', '.tsx', '.js', '.jsx']) { const indexPath = path_1.default.join(resolvedPath, 'index' + ext); if (fs_1.default.existsSync(indexPath)) { resolvedPath = indexPath; break; } } } if (fs_1.default.existsSync(resolvedPath)) { dependencies.push(resolvedPath); } } } return dependencies; } compileModule(filePath, forceRecompile = false) { const absolutePath = path_1.default.resolve(filePath); try { const sourceCode = fs_1.default.readFileSync(absolutePath, 'utf8'); const hash = this.createHash(sourceCode); const cached = this.moduleCache.get(absolutePath); // Check if we can use cached version if (!forceRecompile && cached && cached.hash === hash) { return cached; } const dependencies = this.extractDependencies(sourceCode, absolutePath); // Fast transpile without type checking for development const result = ts.transpileModule(sourceCode, { compilerOptions: this.tsCompilerOptions, fileName: absolutePath, reportDiagnostics: false // Skip diagnostics for speed }); const moduleInfo = { code: result.outputText, map: result.sourceMapText, hash, timestamp: Date.now(), dependencies }; this.moduleCache.set(absolutePath, moduleInfo); if (this.options.verbose) { logger_manager_js_1.loggerManager.printLine(`Compiled ${path_1.default.relative(process.cwd(), filePath)}`, 'info'); } return moduleInfo; } catch (error) { logger_manager_js_1.loggerManager.printLine(`Compilation error: ${error.message}`, 'error'); throw error; } } invalidateModuleCache(filePath) { const absolutePath = path_1.default.resolve(filePath); // Remove the file itself this.moduleCache.delete(absolutePath); // Remove any modules that depend on this file const toRemove = []; for (const [cachedPath, info] of this.moduleCache.entries()) { if (info.dependencies.includes(absolutePath)) { toRemove.push(cachedPath); } } for (const pathToRemove of toRemove) { this.moduleCache.delete(pathToRemove); } if (this.options.verbose && toRemove.length > 0) { logger_manager_js_1.loggerManager.printLine(`Invalidated ${toRemove.length + 1} modules`, 'info'); } } createExecutableFile() { // Always force recompile the main file const mainModule = this.compileModule(this.options.file, true); // Create a unique temp file const timestamp = Date.now(); const random = Math.random().toString(36).substr(2, 9); const tempFile = path_1.default.join(this.tempDir, `main-${timestamp}-${random}.js`); let code = mainModule.code; // Add source map support if (mainModule.map && this.options.sourceMaps) { const mapFile = tempFile + '.map'; fs_1.default.writeFileSync(mapFile, mainModule.map); code += `\n//# sourceMappingURL=${path_1.default.basename(mapFile)}`; } fs_1.default.writeFileSync(tempFile, code); // Clean up old temp file if (this.currentTempFile && fs_1.default.existsSync(this.currentTempFile)) { try { fs_1.default.unlinkSync(this.currentTempFile); const mapFile = this.currentTempFile + '.map'; if (fs_1.default.existsSync(mapFile)) { fs_1.default.unlinkSync(mapFile); } } catch (error) { // Ignore cleanup errors } } this.currentTempFile = tempFile; return tempFile; } async getExecuteCommand() { if (this.options.execCommand) { const parts = this.options.execCommand.split(' '); return { command: parts[0], args: parts.slice(1) }; } const executableFile = this.createExecutableFile(); const args = [...this.options.nodeArgs, executableFile]; // Add Node.js flags if (this.options.inspect) args.unshift('--inspect'); if (this.options.inspectBrk) args.unshift('--inspect-brk'); if (this.options.sourceMaps) args.unshift('--enable-source-maps'); return { command: 'node', args }; } clearConsole() { if (this.options.clearConsole && process.stdout.isTTY) { process.stdout.write('\x1Bc'); // Clear screen and scrollback } } async startProcess() { var _a, _b; if (this.process) return; this.loadEnvFile(); try { const { command, args } = await this.getExecuteCommand(); this.process = (0, child_process_1.spawn)(command, args, { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, NODE_ENV: process.env.NODE_ENV || 'development', FORCE_COLOR: this.options.color ? '1' : '0', NODE_OPTIONS: '--max-old-space-size=4096', // Prevent memory issues }, detached: false // Keep attached for better cleanup }); this.startTime = new Date(); this.restartCount++; // Handle stdout/stderr (_a = this.process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => { process.stdout.write(data); }); (_b = this.process.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => { process.stderr.write(data); }); this.process.on('error', (error) => { logger_manager_js_1.loggerManager.printLine(`Process error: ${error.message}`, 'error'); }); this.process.on('exit', (code, signal) => { if (this.process) { this.process = null; if (!this.isRestarting && code !== 0) { const duration = this.startTime ? Date.now() - this.startTime.getTime() : 0; logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red('✖')} Process exited with code ${code} (${duration}ms)`, 'error'); } } }); } catch (error) { logger_manager_js_1.loggerManager.printLine(`Failed to start: ${error.message}`, 'error'); throw error; } } async stopProcess() { if (!this.process) return; return new Promise((resolve) => { const proc = this.process; this.process = null; const cleanup = () => { resolve(); }; proc.on('exit', cleanup); proc.on('error', cleanup); try { proc.kill('SIGTERM'); // Force kill after timeout setTimeout(() => { if (!proc.killed) { proc.kill('SIGKILL'); } }, 1000); } catch (error) { cleanup(); } }); } async restart() { if (this.isRestarting) return; this.isRestarting = true; // Clear console immediately for better UX this.clearConsole(); if (!this.options.quiet) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow('⟳')} Restarting...`, 'info'); } // Stop current process await this.stopProcess(); // Start new process await this.startProcess(); this.isRestarting = false; } setupWatcher() { const watchPatterns = this.options.watch; // Optimized ignore patterns const ignored = [ 'node_modules/**', '.git/**', 'dist/**', 'build/**', '.neex-temp/**', '**/*.log', '**/*.d.ts', '**/*.map', '**/*.tsbuildinfo', ...this.options.ignore ]; this.watcher = (0, chokidar_1.watch)(watchPatterns, { ignored, ignoreInitial: true, followSymlinks: false, usePolling: false, atomic: 50, // Very fast detection awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 } }); this.watcher.on('change', (filePath) => { this.invalidateModuleCache(filePath); if (this.options.verbose) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue('●')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info'); } this.debouncedRestart(); }); this.watcher.on('add', (filePath) => { if (this.options.verbose) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green('+')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info'); } this.debouncedRestart(); }); this.watcher.on('unlink', (filePath) => { this.invalidateModuleCache(filePath); if (this.options.verbose) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red('-')} ${path_1.default.relative(process.cwd(), filePath)}`, 'info'); } this.debouncedRestart(); }); this.watcher.on('error', (error) => { logger_manager_js_1.loggerManager.printLine(`Watcher error: ${error.message}`, 'error'); }); } async start() { if (!fs_1.default.existsSync(this.options.file)) { throw new Error(`Target file not found: ${this.options.file}`); } const ext = path_1.default.extname(this.options.file); if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext)) { throw new Error(`Unsupported file extension: ${ext}`); } // Clear any existing cache this.moduleCache.clear(); this.setupTempDir(); if (!this.options.quiet) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Starting TypeScript development server...`, 'info'); } this.setupWatcher(); await this.startProcess(); if (!this.options.quiet) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green('✓')} Watching for changes...`, 'info'); } } async stop() { if (this.isShuttingDown) return; this.isShuttingDown = true; if (!this.options.quiet) { logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow('⏹')} Stopping dev server...`, 'info'); } if (this.watcher) { await this.watcher.close(); this.watcher = null; } await this.stopProcess(); // Cleanup temp files if (fs_1.default.existsSync(this.tempDir)) { try { fs_1.default.rmSync(this.tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } } this.moduleCache.clear(); } } exports.DevManager = DevManager;