@rushstack/node-core-library
Version:
Core libraries that every NodeJS toolchain project should use
151 lines • 7.54 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RealNodeModulePathResolver = void 0;
const nodeFs = __importStar(require("fs"));
const nodePath = __importStar(require("path"));
/**
* This class encapsulates a caching resolver for symlinks in node_modules directories.
* It assumes that the only symlinks that exist in input paths are those that correspond to
* npm packages.
*
* @remarks
* In a repository with a symlinked node_modules installation, some symbolic links need to be mapped for
* node module resolution to produce correct results. However, calling `fs.realpathSync.native` on every path,
* as is commonly done by most resolvers, involves an enormous number of file system operations (for reference,
* each invocation of `fs.realpathSync.native` involves a series of `fs.readlinkSync` calls, up to one for each
* path segment in the input).
*
* @public
*/
class RealNodeModulePathResolver {
constructor(options = {}) {
const { fs: { lstatSync = nodeFs.lstatSync, readlinkSync = nodeFs.readlinkSync } = nodeFs, path: { isAbsolute = nodePath.isAbsolute, join = nodePath.join, resolve = nodePath.resolve, sep = nodePath.sep } = nodePath } = options;
const cache = (this._cache = new Map());
this._fs = {
lstatSync,
readlinkSync
};
this._path = {
isAbsolute,
join,
resolve,
sep
};
const nodeModulesToken = `${sep}node_modules${sep}`;
const self = this;
function realNodeModulePathInternal(input) {
// Find the last node_modules path segment
const nodeModulesIndex = input.lastIndexOf(nodeModulesToken);
if (nodeModulesIndex < 0) {
// No node_modules in path, so we assume it is already the real path
return input;
}
// First assume that the next path segment after node_modules is a symlink
let linkStart = nodeModulesIndex + nodeModulesToken.length - 1;
let linkEnd = input.indexOf(sep, linkStart + 1);
// If the path segment starts with a '@', then it is a scoped package
const isScoped = input.charAt(linkStart + 1) === '@';
if (isScoped) {
// For a scoped package, the scope is an ordinary directory, so we need to find the next path segment
if (linkEnd < 0) {
// Symlink missing, so see if anything before the last node_modules needs resolving,
// and preserve the rest of the path
return join(realNodeModulePathInternal(input.slice(0, nodeModulesIndex)), input.slice(nodeModulesIndex + 1),
// Joining to `.` will clean up any extraneous trailing slashes
'.');
}
linkStart = linkEnd;
linkEnd = input.indexOf(sep, linkStart + 1);
}
// No trailing separator, so the link is the last path segment
if (linkEnd < 0) {
linkEnd = input.length;
}
const linkCandidate = input.slice(0, linkEnd);
// Check if the link is a symlink
const linkTarget = self._tryReadLink(linkCandidate);
if (linkTarget && isAbsolute(linkTarget)) {
// Absolute path, combine the link target with any remaining path segments
// Cache the resolution to avoid the readlink call in subsequent calls
cache.set(linkCandidate, linkTarget);
cache.set(linkTarget, linkTarget);
// Joining to `.` will clean up any extraneous trailing slashes
return join(linkTarget, input.slice(linkEnd + 1), '.');
}
// Relative path or does not exist
// Either way, the path before the last node_modules could itself be in a node_modules folder
// So resolve the base path to find out what paths are relative to
const realpathBeforeNodeModules = realNodeModulePathInternal(input.slice(0, nodeModulesIndex));
if (linkTarget) {
// Relative path in symbolic link. Should be resolved relative to real path of base path.
const resolvedTarget = resolve(realpathBeforeNodeModules, input.slice(nodeModulesIndex + 1, linkStart), linkTarget);
// Cache the result of the combined resolution to avoid the readlink call in subsequent calls
cache.set(linkCandidate, resolvedTarget);
cache.set(resolvedTarget, resolvedTarget);
// Joining to `.` will clean up any extraneous trailing slashes
return join(resolvedTarget, input.slice(linkEnd + 1), '.');
}
// No symlink, so just return the real path before the last node_modules combined with the
// subsequent path segments
// Joining to `.` will clean up any extraneous trailing slashes
return join(realpathBeforeNodeModules, input.slice(nodeModulesIndex + 1), '.');
}
this.realNodeModulePath = (input) => {
return realNodeModulePathInternal(resolve(input));
};
}
/**
* Clears the cache of resolved symlinks.
* @public
*/
clearCache() {
this._cache.clear();
}
/**
* Tries to read a symbolic link at the specified path.
* If the input is not a symbolic link, returns undefined.
* @param link - The link to try to read
* @returns The target of the symbolic link, or undefined if the input is not a symbolic link
*/
_tryReadLink(link) {
const cached = this._cache.get(link);
if (cached) {
return cached;
}
// On Windows, calling `readlink` on a directory throws an EUNKOWN, not EINVAL, so just pay the cost
// of an lstat call.
const stat = this._fs.lstatSync(link);
if (stat.isSymbolicLink()) {
// path.join(x, '.') will trim trailing slashes, if applicable
const result = this._path.join(this._fs.readlinkSync(link, 'utf8'), '.');
return result;
}
}
}
exports.RealNodeModulePathResolver = RealNodeModulePathResolver;
//# sourceMappingURL=RealNodeModulePath.js.map