'use strict'; var node_module = require('node:module'); var oxcResolver = require('oxc-resolver'); var path = require('node:path'); var node_process = require('node:process'); var fastGlob = require('fast-glob'); var fs = require('node:fs'); var yaml = require('js-yaml'); const defaultPackagesOptions = { patterns: ["."], ignore: ["**/node_modules/**", "**/test/**", "**/tests/**"], includeRoot: false, }; /** * Copy from https://github.com/9romise/eslint-import-resolver-oxc/blob/main/src/normalizeOptions.ts */ const defaultOptions = { aliasFields: [["browser"]], conditionNames: [ "types", "import", // APF: https://angular.io/guide/angular-package-format "esm2020", "es2020", "es2015", "require", "node", "node-addons", "browser", "default", ], extensionAlias: { ".js": [ ".ts", // `.tsx` can also be compiled as `.js` ".tsx", ".d.ts", ".js", ], ".jsx": [".tsx", ".d.ts", ".jsx"], ".cjs": [".cts", ".d.cts", ".cjs"], ".mjs": [".mts", ".d.mts", ".mjs"], }, extensions: [".ts", ".tsx", ".d.ts", ".js", ".jsx", ".json", ".node"], mainFields: [ "types", "typings", // APF: https://angular.io/guide/angular-package-format "fesm2020", "fesm2015", "esm2020", "es2020", "module", "jsnext:main", "main", "browser", ], tsconfig: true, jsconfig: true, }; const defaultConfigFileOptions = { references: "auto", }; const PNPM_WORKSPACE_FILENAME = "pnpm-workspace.yaml"; const JSCONFIG_FILENAME = "jsconfig.json"; const TSCONFIG_FILENAME = "tsconfig.json"; /** * Remove duplicates from an array. * * @param arr - the array to remove duplicates from * @returns */ function unique(arr) { return Array.from(new Set(arr)); } /** * Remove prefix and querystrings from the module path. * When using node: prefix, we should remove it. * Some imports may have querystrings, for example: * * import "foo?bar"; * * @param {string} modulePath the import module path * * @retures {string} cleaned source */ function cleanModulePath(modulePath) { if (modulePath.startsWith("node:")) { return modulePath.slice(5); } const querystringIndex = modulePath.lastIndexOf("?"); if (querystringIndex > -1) { return modulePath.slice(0, querystringIndex); } return modulePath; } /** * Normalize patterns to include all possible package descriptor files. * * @param patterns - the patterns to normalize * @returns the normalized patterns */ function normalizePatterns(patterns) { const normalizedPatterns = []; for (const pattern of patterns) { const convertedPattern = pattern.replace(/\\/g, "/").replace(/\/$/, ""); // We should add separate pattern for each extension // for some reason, fast-glob is buggy with /package.{json,yaml,json5} pattern normalizedPatterns.push(convertedPattern + "/package.json", convertedPattern + "/package.json5", convertedPattern + "/package.yaml"); } return normalizedPatterns; } /** * * Copy from https://github.com/pnpm/pnpm/blob/19d5b51558716025b9c32af16abcaa009421f835/fs/find-packages/src/index.ts * * @param root * @param opts * @returns */ function findPackages(root, opts) { const { patterns, includeRoot, ignore } = { ...defaultPackagesOptions, ...opts, }; const normalizedPatterns = normalizePatterns(patterns ?? defaultPackagesOptions.patterns); if (includeRoot) { normalizedPatterns.push(...normalizePatterns(["."])); } if (!normalizedPatterns.length) { return []; } const paths = fastGlob.globSync(normalizedPatterns, { cwd: root, ignore, }); return unique(paths.map((manifestPath) => path.join(root, path.dirname(manifestPath)))); } /** * Sort paths by the depth of the path. The deeper the path, the higher the priority. * * @param paths - the paths to sort * @returns */ function sortPaths(paths) { const pathDepths = new Map(); const getPathDepth = (p) => { if (!p) return 0; if (pathDepths.has(p)) { return pathDepths.get(p); } const depth = p.split(path.sep).filter(Boolean).length; pathDepths.set(p, depth); return depth; }; return paths.sort((a, b) => { if (a === "/") return 1; if (b === "/") return -1; const aDepth = getPathDepth(a); const bDepth = getPathDepth(b); if (aDepth !== bDepth) { return bDepth - aDepth; } return b.localeCompare(a); }); } function readYamlFile(filePath) { if (!fs.existsSync(filePath)) { return null; } let doc; try { doc = yaml.load(fs.readFileSync(filePath, "utf8")); } catch { doc = null; } return doc; } function normalizePackageGlobOptions(opts, root) { const { pnpmWorkspace, patterns, ...restOptions } = Array.isArray(opts) ? { patterns: opts } : opts; let packagePatterns; if (pnpmWorkspace) { const pnpmWorkspacePath = path.join(root, typeof pnpmWorkspace === "string" ? pnpmWorkspace : PNPM_WORKSPACE_FILENAME); const pnpmWorkspaceRes = readYamlFile(pnpmWorkspacePath); if (pnpmWorkspaceRes?.packages?.length) { packagePatterns = pnpmWorkspaceRes.packages; } } if (patterns?.length) { const mergedPatterns = packagePatterns ? [...packagePatterns, ...patterns] : patterns; packagePatterns = unique(mergedPatterns.filter(Boolean)); } return { patterns: packagePatterns?.length ? packagePatterns : undefined, ...restOptions, }; } function findClosestPackageRoot(sourceFile, paths) { return sortPaths(paths).find((p) => sourceFile.startsWith(p)); } function findClosestConfigFile(sourceFile, configFiles) { return sortPaths(configFiles).find((p) => sourceFile.startsWith(path.dirname(p))); } const configCache = new Map(); function normalizeConfigFileOptions(config, packageDir, sourceFile, defaultFilename = TSCONFIG_FILENAME) { if (!config) return undefined; if (typeof config === "object") { return { ...defaultConfigFileOptions, ...config }; } const filename = typeof config === "string" ? path.basename(config) : defaultFilename; const cacheKey = `${packageDir}:${filename}`; let configFiles; if (configCache.has(cacheKey)) { configFiles = configCache.get(cacheKey); } else { const paths = fastGlob.globSync(`**/${filename}`, { cwd: packageDir, ignore: ["**/node_modules/**"], }); configFiles = paths.map((p) => path.join(packageDir, p)); configCache.set(cacheKey, configFiles); } if (!configFiles.length) { return undefined; } const closestConfigPath = findClosestConfigFile(sourceFile, configFiles); if (closestConfigPath) { return { ...defaultConfigFileOptions, configFile: closestConfigPath }; } return undefined; } function normalizeAlias(alias, parent) { if (!alias) return undefined; return Object.keys(alias).reduce((acc, key) => { const value = Array.isArray(alias[key]) ? alias[key] : [alias[key]]; acc[key] = value.map((item) => { if (path.isAbsolute(item)) { return item; } return path.resolve(parent, item); }); return acc; }, {}); } const root = node_process.cwd(); let resolverCache = null; /** * Resolves relative path imports * * @param sourceFile - The file that imports the module * @param modulePath - The module path to resolve * @param options - The resolver options * @returns */ function resolveRelativePath(sourceFile, modulePath, options) { if (!resolverCache) { resolverCache = new oxcResolver.ResolverFactory(options); } const sourceFileDir = path.dirname(sourceFile); const result = resolverCache.sync(sourceFileDir, modulePath); if (result.path) { return { found: true, path: result.path }; } return { found: false }; } const pathToPackagesMap = new Map(); function resolve(modulePath, sourceFile, config) { const cleanedPath = cleanModulePath(modulePath); if (node_module.builtinModules.includes(cleanedPath)) { return { found: true, path: null }; } // wrong node module path if (modulePath.startsWith("node:")) { return { found: false }; } const { roots, alias, packages, jsconfig, tsconfig, ...restOptions } = { ...defaultOptions, ...config, }; // relative path if (modulePath.startsWith(".")) { return resolveRelativePath(sourceFile, modulePath, restOptions); } let resolveRoots = roots?.length ? roots : [root]; let packageDir; if (packages && typeof packages === "object") { for (const r of resolveRoots) { // find all packages in the root let paths = pathToPackagesMap.get(r); if (!paths) { paths = findPackages(r, normalizePackageGlobOptions(packages, r)); pathToPackagesMap.set(r, paths); } if (!paths.length) continue; const filePackage = findClosestPackageRoot(sourceFile, paths); if (filePackage) { packageDir = filePackage; resolveRoots = unique([packageDir, ...resolveRoots]); break; } } } else { const findPackage = findClosestPackageRoot(sourceFile, resolveRoots); if (findPackage) { packageDir = findPackage; } } // file not find in any package if (!packageDir) { return { found: false }; } const resolveAlias = normalizeAlias(alias, packageDir); let configFileOptions; for (const c of [ { config: tsconfig, filename: TSCONFIG_FILENAME }, { config: jsconfig, filename: JSCONFIG_FILENAME }, ]) { configFileOptions = normalizeConfigFileOptions(c.config, packageDir, sourceFile, c.filename); if (configFileOptions) { break; } } const resolver = new oxcResolver.ResolverFactory({ alias: resolveAlias, tsconfig: configFileOptions, roots: resolveRoots, ...restOptions, }); const result = resolver.sync(path.dirname(sourceFile), modulePath); if (result.path) { return { found: true, path: result.path }; } return { found: false }; } var index = { interfaceVersion: 2, resolve, }; module.exports = index;