108 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			108 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Tobias Koppers @sokra
 | 
						|
*/
 | 
						|
"use strict";
 | 
						|
 | 
						|
const fs = require("fs");
 | 
						|
const path = require("path");
 | 
						|
 | 
						|
// macOS, Linux, and Windows all rely on these errors
 | 
						|
const EXPECTED_ERRORS = new Set(["EINVAL", "ENOENT"]);
 | 
						|
 | 
						|
// On Windows there is also this error in some cases
 | 
						|
if (process.platform === "win32") EXPECTED_ERRORS.add("UNKNOWN");
 | 
						|
 | 
						|
class LinkResolver {
 | 
						|
	constructor() {
 | 
						|
		this.cache = new Map();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {string} file path to file or directory
 | 
						|
	 * @returns {string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file)
 | 
						|
	 */
 | 
						|
	resolve(file) {
 | 
						|
		const cacheEntry = this.cache.get(file);
 | 
						|
		if (cacheEntry !== undefined) {
 | 
						|
			return cacheEntry;
 | 
						|
		}
 | 
						|
		const parent = path.dirname(file);
 | 
						|
		if (parent === file) {
 | 
						|
			// At root of filesystem there can't be a link
 | 
						|
			const result = Object.freeze([file]);
 | 
						|
			this.cache.set(file, result);
 | 
						|
			return result;
 | 
						|
		}
 | 
						|
		// resolve the parent directory to find links there and get the real path
 | 
						|
		const parentResolved = this.resolve(parent);
 | 
						|
		let realFile = file;
 | 
						|
 | 
						|
		// is the parent directory really somewhere else?
 | 
						|
		if (parentResolved[0] !== parent) {
 | 
						|
			// get the real location of file
 | 
						|
			const basename = path.basename(file);
 | 
						|
			realFile = path.resolve(parentResolved[0], basename);
 | 
						|
		}
 | 
						|
		// try to read the link content
 | 
						|
		try {
 | 
						|
			const linkContent = fs.readlinkSync(realFile);
 | 
						|
 | 
						|
			// resolve the link content relative to the parent directory
 | 
						|
			const resolvedLink = path.resolve(parentResolved[0], linkContent);
 | 
						|
 | 
						|
			// recursive resolve the link content for more links in the structure
 | 
						|
			const linkResolved = this.resolve(resolvedLink);
 | 
						|
 | 
						|
			// merge parent and link resolve results
 | 
						|
			let result;
 | 
						|
			if (linkResolved.length > 1 && parentResolved.length > 1) {
 | 
						|
				// when both contain links we need to duplicate them with a Set
 | 
						|
				const resultSet = new Set(linkResolved);
 | 
						|
				// add the link
 | 
						|
				resultSet.add(realFile);
 | 
						|
				// add all symlinks of the parent
 | 
						|
				for (let i = 1; i < parentResolved.length; i++) {
 | 
						|
					resultSet.add(parentResolved[i]);
 | 
						|
				}
 | 
						|
				result = Object.freeze(Array.from(resultSet));
 | 
						|
			} else if (parentResolved.length > 1) {
 | 
						|
				// we have links in the parent but not for the link content location
 | 
						|
				result = parentResolved.slice();
 | 
						|
				result[0] = linkResolved[0];
 | 
						|
				// add the link
 | 
						|
				result.push(realFile);
 | 
						|
				Object.freeze(result);
 | 
						|
			} else if (linkResolved.length > 1) {
 | 
						|
				// we can return the link content location result
 | 
						|
				result = linkResolved.slice();
 | 
						|
				// add the link
 | 
						|
				result.push(realFile);
 | 
						|
				Object.freeze(result);
 | 
						|
			} else {
 | 
						|
				// neither link content location nor parent have links
 | 
						|
				// this link is the only link here
 | 
						|
				result = Object.freeze([
 | 
						|
					// the resolve real location
 | 
						|
					linkResolved[0],
 | 
						|
					// add the link
 | 
						|
					realFile
 | 
						|
				]);
 | 
						|
			}
 | 
						|
			this.cache.set(file, result);
 | 
						|
			return result;
 | 
						|
		} catch (e) {
 | 
						|
			if (!EXPECTED_ERRORS.has(e.code)) {
 | 
						|
				throw e;
 | 
						|
			}
 | 
						|
			// no link
 | 
						|
			const result = parentResolved.slice();
 | 
						|
			result[0] = realFile;
 | 
						|
			Object.freeze(result);
 | 
						|
			this.cache.set(file, result);
 | 
						|
			return result;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
module.exports = LinkResolver;
 |