'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var node_module = require('node:module'); var node_process = require('node:process'); var oxcResolver = require('oxc-resolver'); var path = require('node:path'); var crypto = require('node:crypto'); var fastGlob = require('fast-glob'); var fs = require('node:fs'); var yaml = require('js-yaml'); var stableHash = require('stable-hash'); 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"; const pathToPackagesCache = new Map(); function getPackagesCache(root) { if (pathToPackagesCache.has(root)) { return pathToPackagesCache.get(root); } return null; } function setPackagesCache(root, packagePaths) { pathToPackagesCache.set(root, packagePaths); } const configFilesCache = new Map(); function getConfigFilesCache(root) { if (configFilesCache.has(root)) { return configFilesCache.get(root); } return null; } function setConfigFilesCache(root, configFiles) { configFilesCache.set(root, configFiles); } const yamlCache = new Map(); function getYamlCache(root) { if (yamlCache.has(root)) { return yamlCache.get(root); } return null; } function setYamlCache(root, yaml) { yamlCache.set(root, yaml); } let relativeResolver = null; function getRelativeResolver(options) { if (!relativeResolver) { relativeResolver = new oxcResolver.ResolverFactory(options); } return relativeResolver; } const MAX_RESOLVER_CACHE_SIZE = 4; const resolverCache = new Map(); function getResolver(hashKey, options) { if (resolverCache.has(hashKey)) { return resolverCache.get(hashKey); } if (resolverCache.size >= MAX_RESOLVER_CACHE_SIZE) { const firstKey = resolverCache.keys().next().value; const oldResolver = resolverCache.get(firstKey); oldResolver.clearCache(); resolverCache.delete(firstKey); } const resolver = new oxcResolver.ResolverFactory(options); resolverCache.set(hashKey, resolver); return resolver; } /** * 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; } /** * Get the depth of a path. * * @param p {string} - the path * @returns {number} - the depth of the path */ function getPathDepth(p) { if (!p) return 0; return p.split(path.sep).filter(Boolean).length; } /** * 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) { 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); }); } /** * Read a yaml file. * * @param filePath {string} - the file path to read * @returns {T | null} - the parsed yaml file */ function readYamlFile(filePath) { const cache = getYamlCache(filePath); if (cache) return cache; if (!fs.existsSync(filePath)) { return null; } let doc; try { doc = yaml.load(fs.readFileSync(filePath, "utf8")); } catch { doc = null; } setYamlCache(filePath, doc); return doc; } /** * Normalize package glob options. * * @param opts {PackageOptions | string[]} - the package options * @param root {string} - the root path * @returns {PackageGlobOptions} - the normalized package glob options */ 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, }; } /** * * Copy from https://github.com/pnpm/pnpm/blob/19d5b51558716025b9c32af16abcaa009421f835/fs/find-packages/src/index.ts * * @param root * @param opts * @returns */ function findAllPackages(root, packageOpts) { const cache = getPackagesCache(root); if (cache) return cache; const opts = normalizePackageGlobOptions(packageOpts, root); 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, }); const packagePaths = unique(paths.map((manifestPath) => path.join(root, path.dirname(manifestPath)))); setPackagesCache(root, packagePaths); return packagePaths; } /** * Find the closest package from the source file. * * @param sourceFile {string} - the source file * @param paths {string[]} - the paths to search * @returns {string | undefined} - the closest package root */ function findClosestPackageRoot(sourceFile, paths) { return sortPaths(paths).find((p) => sourceFile.startsWith(p)); } function findClosestConfigFile(sourceFile, configFiles, tsconfigFilename) { return configFiles .sort((a, b) => { const aDepth = getPathDepth(a); const bDepth = getPathDepth(b); if (aDepth !== bDepth) { return bDepth - aDepth; } if (tsconfigFilename) { if (a.endsWith(tsconfigFilename)) return -1; if (b.endsWith(tsconfigFilename)) return 1; } return b.localeCompare(a); }) .find((p) => sourceFile.startsWith(path.dirname(p))); } function getConfigFilename(config, defaultFilename) { if (!config) return undefined; if (typeof config === "string") { return path.basename(config); } if (typeof config === "object" && config?.configFile) { return path.basename(config.configFile); } return defaultFilename; } function normalizeConfigFileOptions(configs, packageDir, sourceFile) { const { jsconfig, tsconfig } = configs; if (!tsconfig && !jsconfig) return undefined; for (const c of [tsconfig, jsconfig]) { if (typeof c === "object" && c.configFile && path.isAbsolute(c.configFile)) { return { ...defaultConfigFileOptions, ...c }; } } const jsconfigFilename = getConfigFilename(jsconfig, JSCONFIG_FILENAME); const tsconfigFilename = getConfigFilename(tsconfig, TSCONFIG_FILENAME); let configFiles = getConfigFilesCache(packageDir); if (!configFiles) { const paths = fastGlob.globSync([tsconfigFilename, jsconfigFilename] .filter(Boolean) .map((f) => `**/${f}`), { cwd: packageDir, ignore: ["**/node_modules/**"], }); configFiles = paths.map((p) => path.join(packageDir, p)); setConfigFilesCache(packageDir, configFiles); } if (!configFiles.length) { return undefined; } const closestConfigPath = findClosestConfigFile(sourceFile, configFiles, tsconfigFilename); if (closestConfigPath) { return { ...defaultConfigFileOptions, configFile: closestConfigPath }; } return undefined; } function normalizeAlias(alias, parent) { if (!alias) return undefined; const normalizedAlias = 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; }, {}); return normalizedAlias; } /** * Get the hash of an object. * * @param obj {unknown} - the object to hash * @returns - the hash of the object */ function hashObject(obj) { return crypto.createHash("sha256").update(stableHash(obj)).digest("hex"); } /** * Find all workspace packages. * * @param roots {string[]} - the roots to search * @param packages {string[] | PackageOptions} - the package options * @returns {string[]} - the workspace packages */ function findWorkspacePackages(roots, packages) { if (packages && typeof packages === "object") { return roots.flatMap((r) => findAllPackages(r, packages)); } return [...roots]; } /** * 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) { const sourceFileDir = path.dirname(sourceFile); const relativeResolver = getRelativeResolver(options); const result = relativeResolver.sync(sourceFileDir, modulePath); if (result.path) { return { found: true, path: result.path }; } return { found: false }; } /** * Resolves a module path * * @param modulePath - The module path to resolve * @param sourceFile - The file that imports the module * @param options - The resolver options * @returns */ function resolveModulePath(sourceFile, modulePath, options) { // hash the options to cache the resolver // other options are not needed as they are not usually changed const hashKey = hashObject({ alias: options.alias, tsconfig: options.tsconfig, roots: options.roots, }); const resolver = getResolver(hashKey, options); const result = resolver.sync(path.dirname(sourceFile), modulePath); if (result.path) { return { found: true, path: result.path }; } return { found: false }; } 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); } const resolveRoots = roots?.length ? roots : [node_process.cwd()]; const workspacePackages = findWorkspacePackages(resolveRoots, packages); const packageDir = findClosestPackageRoot(sourceFile, workspacePackages); // file not find in any package if (!packageDir) { return { found: false }; } const packageRoots = unique([packageDir, ...resolveRoots]); const resolveAlias = normalizeAlias(alias, packageDir); const configFileOptions = normalizeConfigFileOptions({ tsconfig, jsconfig }, packageDir, sourceFile); return resolveModulePath(sourceFile, modulePath, { alias: resolveAlias, tsconfig: configFileOptions, roots: packageRoots, ...restOptions, }); } function createNextImportResolver(config) { const { roots, alias, packages, jsconfig, tsconfig, ...restOptions } = { ...defaultOptions, ...config, }; const resolveRoots = roots?.length ? roots : [node_process.cwd()]; const workspacePackages = findWorkspacePackages(resolveRoots, packages); return { interfaceVersion: 3, name: "eslint-import-resolver-next", resolve: (modulePath, sourceFile) => { 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 packageDir = findClosestPackageRoot(sourceFile, workspacePackages); // file not find in any package if (!packageDir) { return { found: false }; } const packageRoots = unique([packageDir, ...resolveRoots]); const resolveAlias = normalizeAlias(alias, packageDir); const configFileOptions = normalizeConfigFileOptions({ tsconfig, jsconfig }, packageDir, sourceFile); return resolveModulePath(sourceFile, modulePath, { alias: resolveAlias, tsconfig: configFileOptions, roots: packageRoots, ...restOptions, }); }, }; } const interfaceVersion = 2; var index = { interfaceVersion, resolve, }; exports.createNextImportResolver = createNextImportResolver; exports.default = index; exports.interfaceVersion = interfaceVersion; exports.resolve = resolve;