马宇豪
2024-07-16 f591c27b57e2418c9495bc02ae8cfff84d35bc18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
    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;