From 22564fd289a3dd61e1fa70283fd5603626b15498 Mon Sep 17 00:00:00 2001 From: Denys Kuchma Date: Fri, 9 Jan 2026 18:51:52 +0200 Subject: [PATCH] resolve TypeScript path aliases from tsconfig.json --- lib/utils/typescript.js | 139 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 128 insertions(+), 11 deletions(-) diff --git a/lib/utils/typescript.js b/lib/utils/typescript.js index caec8ced5..5f9d0d417 100644 --- a/lib/utils/typescript.js +++ b/lib/utils/typescript.js @@ -1,5 +1,60 @@ import fs from 'fs' import path from 'path' +import { pathToFileURL } from 'url' + +/** + * Load tsconfig.json if it exists + * @param {string} tsConfigPath - Path to tsconfig.json + * @returns {object|null} - Parsed tsconfig or null + */ +function loadTsConfig(tsConfigPath) { + if (!fs.existsSync(tsConfigPath)) { + return null + } + + try { + const tsConfigContent = fs.readFileSync(tsConfigPath, 'utf8') + return JSON.parse(tsConfigContent) + } catch (err) { + return null + } +} + +/** + * Resolve TypeScript path alias to actual file path + * @param {string} importPath - Import path with alias (e.g., '#config/urls') + * @param {object} tsConfig - Parsed tsconfig.json + * @param {string} configDir - Directory containing tsconfig.json + * @returns {string|null} - Resolved file path or null if not an alias + */ +function resolveTsPathAlias(importPath, tsConfig, configDir) { + if (!tsConfig || !tsConfig.compilerOptions || !tsConfig.compilerOptions.paths) { + return null + } + + const paths = tsConfig.compilerOptions.paths + + for (const [pattern, targets] of Object.entries(paths)) { + if (!targets || targets.length === 0) { + continue + } + + const patternRegex = new RegExp( + '^' + pattern.replace(/\*/g, '(.*)') + '$' + ) + const match = importPath.match(patternRegex) + + if (match) { + const wildcard = match[1] || '' + const target = targets[0] + const resolvedTarget = target.replace(/\*/g, wildcard) + + return path.resolve(configDir, resolvedTarget) + } + } + + return null +} /** * Transpile TypeScript files to ES modules with CommonJS shim support @@ -108,6 +163,22 @@ const __dirname = __dirname_fn(__filename); const transpiledFiles = new Map() const baseDir = path.dirname(mainFilePath) + // Try to find tsconfig.json by walking up the directory tree + let tsConfigPath = path.join(baseDir, 'tsconfig.json') + let configDir = baseDir + let searchDir = baseDir + + while (!fs.existsSync(tsConfigPath) && searchDir !== path.dirname(searchDir)) { + searchDir = path.dirname(searchDir) + tsConfigPath = path.join(searchDir, 'tsconfig.json') + if (fs.existsSync(tsConfigPath)) { + configDir = searchDir + break + } + } + + const tsConfig = loadTsConfig(tsConfigPath) + // Recursive function to transpile a file and all its TypeScript dependencies const transpileFileAndDeps = (filePath) => { // Already transpiled, skip @@ -118,9 +189,9 @@ const __dirname = __dirname_fn(__filename); // Transpile this file let jsContent = transpileTS(filePath) - // Find all relative TypeScript imports in this file (both ESM imports and require() calls) - const importRegex = /from\s+['"](\.[^'"]+?)(?:\.ts)?['"]/g - const requireRegex = /require\s*\(\s*['"](\.[^'"]+?)(?:\.ts)?['"]\s*\)/g + // Find all TypeScript imports in this file (both ESM imports and require() calls) + const importRegex = /from\s+['"]([^'"]+?)['"]/g + const requireRegex = /require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g let match const imports = [] @@ -136,8 +207,18 @@ const __dirname = __dirname_fn(__filename); const fileBaseDir = path.dirname(filePath) // Recursively transpile each imported TypeScript file - for (const { path: relativeImport } of imports) { - let importedPath = path.resolve(fileBaseDir, relativeImport) + for (const { path: importPath } of imports) { + let importedPath = importPath + + // Check if this is a path alias + const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir) + if (resolvedAlias) { + importedPath = resolvedAlias + } else if (importPath.startsWith('.')) { + importedPath = path.resolve(fileBaseDir, importPath) + } else { + continue + } // Handle .js extensions that might actually be .ts files if (importedPath.endsWith('.js')) { @@ -181,11 +262,34 @@ const __dirname = __dirname_fn(__filename); // After all dependencies are transpiled, rewrite imports in this file jsContent = jsContent.replace( - /from\s+['"](\.[^'"]+?)(?:\.ts)?['"]/g, + /from\s+['"]([^'"]+?)['"]/g, (match, importPath) => { - let resolvedPath = path.resolve(fileBaseDir, importPath) + let resolvedPath = importPath const originalExt = path.extname(importPath) + // Check if this is a path alias + const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir) + if (resolvedAlias) { + resolvedPath = resolvedAlias + } else if (importPath.startsWith('.')) { + resolvedPath = path.resolve(fileBaseDir, importPath) + } else { + return match + } + + // If resolved path is a directory, try index.ts + if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) { + const indexPath = path.join(resolvedPath, 'index.ts') + if (fs.existsSync(indexPath) && transpiledFiles.has(indexPath)) { + const tempFile = transpiledFiles.get(indexPath) + const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/') + if (!relPath.startsWith('.')) { + return `from './${relPath}'` + } + return `from '${relPath}'` + } + } + // Handle .js extension that might be .ts if (resolvedPath.endsWith('.js')) { const tsVersion = resolvedPath.replace(/\.js$/, '.ts') @@ -238,9 +342,19 @@ const __dirname = __dirname_fn(__filename); // Also rewrite require() calls to point to transpiled TypeScript files jsContent = jsContent.replace( - /require\s*\(\s*['"](\.[^'"]+?)(?:\.ts)?['"]\s*\)/g, + /require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g, (match, requirePath) => { - let resolvedPath = path.resolve(fileBaseDir, requirePath) + let resolvedPath = requirePath + + // Check if this is a path alias + const resolvedAlias = resolveTsPathAlias(requirePath, tsConfig, configDir) + if (resolvedAlias) { + resolvedPath = resolvedAlias + } else if (requirePath.startsWith('.')) { + resolvedPath = path.resolve(fileBaseDir, requirePath) + } else { + return match + } // Handle .js extension that might be .ts if (resolvedPath.endsWith('.js')) { @@ -282,10 +396,13 @@ const __dirname = __dirname_fn(__filename); // Get the main transpiled file const tempJsFile = transpiledFiles.get(mainFilePath) - // Store all temp files for cleanup + // Convert to file:// URL for dynamic import() (required on Windows) + const tempFileUrl = pathToFileURL(tempJsFile).href + + // Store all temp files for cleanup (keep as paths, not URLs) const allTempFiles = Array.from(transpiledFiles.values()) - return { tempFile: tempJsFile, allTempFiles, fileMapping: transpiledFiles } + return { tempFile: tempFileUrl, allTempFiles, fileMapping: transpiledFiles } } /**