@nomicfoundation/hardhat-utils 4.0.5 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/src/debug.d.ts +40 -8
- package/dist/src/debug.d.ts.map +1 -1
- package/dist/src/debug.js +54 -18
- package/dist/src/debug.js.map +1 -1
- package/dist/src/fast-semver.d.ts +49 -0
- package/dist/src/fast-semver.d.ts.map +1 -0
- package/dist/src/fast-semver.js +73 -0
- package/dist/src/fast-semver.js.map +1 -0
- package/dist/src/fs.d.ts +47 -0
- package/dist/src/fs.d.ts.map +1 -1
- package/dist/src/fs.js +183 -13
- package/dist/src/fs.js.map +1 -1
- package/dist/src/internal/debug.d.ts +35 -0
- package/dist/src/internal/debug.d.ts.map +1 -0
- package/dist/src/internal/debug.js +91 -0
- package/dist/src/internal/debug.js.map +1 -0
- package/dist/src/subprocess.d.ts +2 -0
- package/dist/src/subprocess.d.ts.map +1 -1
- package/dist/src/subprocess.js +1 -0
- package/dist/src/subprocess.js.map +1 -1
- package/dist/src/synchronization.d.ts +97 -0
- package/dist/src/synchronization.d.ts.map +1 -1
- package/dist/src/synchronization.js +171 -2
- package/dist/src/synchronization.js.map +1 -1
- package/package.json +3 -4
- package/src/debug.ts +73 -28
- package/src/fast-semver.ts +95 -0
- package/src/fs.ts +249 -23
- package/src/internal/debug.ts +119 -0
- package/src/subprocess.ts +2 -0
- package/src/synchronization.ts +211 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @nomicfoundation/hardhat-utils
|
|
2
2
|
|
|
3
|
+
## 4.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#8205](https://github.com/NomicFoundation/hardhat/pull/8205) [`99a4556`](https://github.com/NomicFoundation/hardhat/commit/99a4556698e1d0776951fb160641f5e2529b68ee) Thanks [@alcuadrado](https://github.com/alcuadrado)! - Introduce the SharedPromiseCache synchronization primitive
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#8219](https://github.com/NomicFoundation/hardhat/pull/8219) [`2cad309`](https://github.com/NomicFoundation/hardhat/commit/2cad309eb8d35fcc5d2aba5d75a5af4d63d50508) Thanks [@schaable](https://github.com/schaable)! - Improved performance by replacing the semver dependency with a lightweight in-tree implementation.
|
|
12
|
+
|
|
13
|
+
- [#8196](https://github.com/NomicFoundation/hardhat/pull/8196) [`73436aa`](https://github.com/NomicFoundation/hardhat/commit/73436aaa1ddac805f8d855627b5b40ad69cf7d2e) Thanks [@alcuadrado](https://github.com/alcuadrado)! - Export missing error types
|
|
14
|
+
|
|
15
|
+
- [#8207](https://github.com/NomicFoundation/hardhat/pull/8207) [`d594209`](https://github.com/NomicFoundation/hardhat/commit/d59420968bffca83e1ad2712c6881d19cc7e1a99) Thanks [@alcuadrado](https://github.com/alcuadrado)! - Improved performance by replacing the debug logging library with a lightweight in-tree implementation.
|
|
16
|
+
|
|
17
|
+
- [#8189](https://github.com/NomicFoundation/hardhat/pull/8189) [`b5ca1ae`](https://github.com/NomicFoundation/hardhat/commit/b5ca1aee1eb2571ec23f54ac8a9b2c39a90361c6) Thanks [@alcuadrado](https://github.com/alcuadrado)! - Introduce a TrueCasePathResolver class to optimize repeated filesystem casing resolution
|
|
18
|
+
|
|
3
19
|
## 4.0.5
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/src/debug.d.ts
CHANGED
|
@@ -1,16 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A
|
|
2
|
+
* A logger function returned by {@link createDebug}.
|
|
3
|
+
* The `enabled` property is `true` when the logger will produce output.
|
|
4
|
+
*/
|
|
5
|
+
export interface DebugLogger {
|
|
6
|
+
(format: unknown, ...args: unknown[]): void;
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a namespaced logger controlled by the `DEBUG` env var.
|
|
3
11
|
*
|
|
4
|
-
*
|
|
12
|
+
* If `namespace` matches `DEBUG`, the logger writes to `process.stderr`;
|
|
13
|
+
* otherwise, it is a no-op. Additionally, `logger.enabled` is `true` when
|
|
14
|
+
* the namespace matches `DEBUG`, allowing you to conditionally run expensive
|
|
15
|
+
* diagnostics.
|
|
5
16
|
*
|
|
6
|
-
*
|
|
17
|
+
* `DEBUG` should be a comma- or whitespace-separated list of patterns.
|
|
18
|
+
* `*` is a wildcard and a leading `-` negates a pattern (e.g.
|
|
19
|
+
* `hardhat:*,-hardhat:noisy`).
|
|
7
20
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
21
|
+
* Messages are formatted using `node:util.format`, allowing you to use format
|
|
22
|
+
* specifiers like `%O`, `%o`, `%s`, `%d`, `%j`. Extra arguments without a
|
|
23
|
+
* matching specifier are inspected automatically.
|
|
24
|
+
*
|
|
25
|
+
* Output is colorized per namespace when `stderr` is a TTY. Set
|
|
26
|
+
* `DEBUG_COLORS=no` or `false` to disable colors.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const log = createDebug("hardhat:utils:foo");
|
|
31
|
+
* log("Starting up");
|
|
32
|
+
* log("Received %O", payload);
|
|
33
|
+
* log("Saved data", id, filePath);
|
|
34
|
+
*
|
|
35
|
+
* if (log.enabled) {
|
|
36
|
+
* // expensive diagnostics that should only run while debugging
|
|
12
37
|
* }
|
|
13
38
|
* ```
|
|
39
|
+
*
|
|
40
|
+
* ```sh
|
|
41
|
+
* DEBUG="hardhat:*,-hardhat:noisy" DEBUG_COLORS=no pnpm hardhat run script.js
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param namespace Namespace used for filtering and as the log prefix.
|
|
45
|
+
* @returns Logger function, or a shared no-op if disabled.
|
|
14
46
|
*/
|
|
15
|
-
export declare function
|
|
47
|
+
export declare function createDebug(namespace: string): DebugLogger;
|
|
16
48
|
//# sourceMappingURL=debug.d.ts.map
|
package/dist/src/debug.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CA+B1D"}
|
package/dist/src/debug.js
CHANGED
|
@@ -1,28 +1,64 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { formatWithOptions } from "node:util";
|
|
2
|
+
import { NOOP, isEnabled, selectColor, useColors } from "./internal/debug.js";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Creates a namespaced logger controlled by the `DEBUG` env var.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
+
* If `namespace` matches `DEBUG`, the logger writes to `process.stderr`;
|
|
7
|
+
* otherwise, it is a no-op. Additionally, `logger.enabled` is `true` when
|
|
8
|
+
* the namespace matches `DEBUG`, allowing you to conditionally run expensive
|
|
9
|
+
* diagnostics.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
11
|
+
* `DEBUG` should be a comma- or whitespace-separated list of patterns.
|
|
12
|
+
* `*` is a wildcard and a leading `-` negates a pattern (e.g.
|
|
13
|
+
* `hardhat:*,-hardhat:noisy`).
|
|
8
14
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* Messages are formatted using `node:util.format`, allowing you to use format
|
|
16
|
+
* specifiers like `%O`, `%o`, `%s`, `%d`, `%j`. Extra arguments without a
|
|
17
|
+
* matching specifier are inspected automatically.
|
|
18
|
+
*
|
|
19
|
+
* Output is colorized per namespace when `stderr` is a TTY. Set
|
|
20
|
+
* `DEBUG_COLORS=no` or `false` to disable colors.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const log = createDebug("hardhat:utils:foo");
|
|
25
|
+
* log("Starting up");
|
|
26
|
+
* log("Received %O", payload);
|
|
27
|
+
* log("Saved data", id, filePath);
|
|
28
|
+
*
|
|
29
|
+
* if (log.enabled) {
|
|
30
|
+
* // expensive diagnostics that should only run while debugging
|
|
13
31
|
* }
|
|
14
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* ```sh
|
|
35
|
+
* DEBUG="hardhat:*,-hardhat:noisy" DEBUG_COLORS=no pnpm hardhat run script.js
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param namespace Namespace used for filtering and as the log prefix.
|
|
39
|
+
* @returns Logger function, or a shared no-op if disabled.
|
|
15
40
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
41
|
+
export function createDebug(namespace) {
|
|
42
|
+
if (!isEnabled(namespace, process.env.DEBUG ?? "")) {
|
|
43
|
+
return NOOP;
|
|
44
|
+
}
|
|
45
|
+
const colors = useColors();
|
|
46
|
+
const color = colors ? selectColor(namespace) : undefined;
|
|
47
|
+
const prefix = color !== undefined
|
|
48
|
+
? `\x1b[38;5;${color};1m ${namespace}\x1b[0m`
|
|
49
|
+
: ` ${namespace}`;
|
|
50
|
+
const suffixOpen = color !== undefined ? `\x1b[38;5;${color}m` : "";
|
|
51
|
+
const suffixClose = color !== undefined ? `\x1b[0m` : "";
|
|
52
|
+
let prev = 0;
|
|
53
|
+
const logger = (format, ...args) => {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const diff = prev === 0 ? 0 : now - prev;
|
|
56
|
+
prev = now;
|
|
57
|
+
const body = args.length === 0 && typeof format === "string"
|
|
58
|
+
? format
|
|
59
|
+
: formatWithOptions({ colors }, format, ...args);
|
|
60
|
+
process.stderr.write(`${prefix} ${body} ${suffixOpen}+${diff}ms${suffixClose}\n`);
|
|
26
61
|
};
|
|
62
|
+
return Object.assign(logger, { enabled: true });
|
|
27
63
|
}
|
|
28
64
|
//# sourceMappingURL=debug.js.map
|
package/dist/src/debug.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAW9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1D,MAAM,MAAM,GACV,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,aAAa,KAAK,QAAQ,SAAS,SAAS;QAC9C,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,MAAM,WAAW,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,MAAM,MAAM,GAAG,CAAC,MAAe,EAAE,GAAG,IAAe,EAAQ,EAAE;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;QACzC,IAAI,GAAG,GAAG,CAAC;QAEX,MAAM,IAAI,GACR,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAC7C,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;QAErD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,MAAM,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,KAAK,WAAW,IAAI,CAC5D,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, fast subset of semver: strict `MAJOR.MINOR.PATCH` parsing and
|
|
3
|
+
* triple-wise comparison helpers.
|
|
4
|
+
*
|
|
5
|
+
* This module exists because the full `semver` package is slow to load and is
|
|
6
|
+
* overkill for the many call sites in Hardhat that compare against hard-coded
|
|
7
|
+
* `x.y.z` literals. For range grammar (caret/tilde, disjunctions, prerelease
|
|
8
|
+
* subset, etc.) keep using `semver`.
|
|
9
|
+
*/
|
|
10
|
+
export type SemverVersion = [major: number, minor: number, patch: number];
|
|
11
|
+
/**
|
|
12
|
+
* Parses a strict `MAJOR.MINOR.PATCH` version string into a `SemverVersion`
|
|
13
|
+
* tuple.
|
|
14
|
+
*
|
|
15
|
+
* An optional `+build` suffix is accepted and stripped silently. A
|
|
16
|
+
* `-prerelease` suffix is rejected.
|
|
17
|
+
*
|
|
18
|
+
* @param version The version string to parse.
|
|
19
|
+
* @returns The parsed `SemverVersion`, or `undefined` if the input does not
|
|
20
|
+
* match the strict `\d+\.\d+\.\d+` shape.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseVersion(version: string): SemverVersion | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* `Array#sort`-style comparator for `SemverVersion` tuples: returns a negative
|
|
25
|
+
* number, zero, or a positive number depending on whether `a` is lower than,
|
|
26
|
+
* equal to, or greater than `b`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compare(a: SemverVersion, b: SemverVersion): number;
|
|
29
|
+
/**
|
|
30
|
+
* Returns `true` if `a` and `b` represent the same `MAJOR.MINOR.PATCH`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function equals(a: SemverVersion, b: SemverVersion): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Returns `true` if `compared` is strictly lower than `comparator`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function lowerThan(compared: SemverVersion, comparator: SemverVersion): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Returns `true` if `compared` is lower than or equal to `comparator`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function lowerThanOrEqual(compared: SemverVersion, comparator: SemverVersion): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Returns `true` if `compared` is strictly greater than `comparator`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function greaterThan(compared: SemverVersion, comparator: SemverVersion): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Returns `true` if `compared` is greater than or equal to `comparator`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function greaterThanOrEqual(compared: SemverVersion, comparator: SemverVersion): boolean;
|
|
49
|
+
//# sourceMappingURL=fast-semver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast-semver.d.ts","sourceRoot":"","sources":["../../src/fast-semver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAI1E;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAOvE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,MAAM,CAQlE;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAElE;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,aAAa,GACxB,OAAO,CAET;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,aAAa,GACxB,OAAO,CAET;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,aAAa,GACxB,OAAO,CAET;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,aAAa,GACxB,OAAO,CAET"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, fast subset of semver: strict `MAJOR.MINOR.PATCH` parsing and
|
|
3
|
+
* triple-wise comparison helpers.
|
|
4
|
+
*
|
|
5
|
+
* This module exists because the full `semver` package is slow to load and is
|
|
6
|
+
* overkill for the many call sites in Hardhat that compare against hard-coded
|
|
7
|
+
* `x.y.z` literals. For range grammar (caret/tilde, disjunctions, prerelease
|
|
8
|
+
* subset, etc.) keep using `semver`.
|
|
9
|
+
*/
|
|
10
|
+
const VERSION_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/;
|
|
11
|
+
/**
|
|
12
|
+
* Parses a strict `MAJOR.MINOR.PATCH` version string into a `SemverVersion`
|
|
13
|
+
* tuple.
|
|
14
|
+
*
|
|
15
|
+
* An optional `+build` suffix is accepted and stripped silently. A
|
|
16
|
+
* `-prerelease` suffix is rejected.
|
|
17
|
+
*
|
|
18
|
+
* @param version The version string to parse.
|
|
19
|
+
* @returns The parsed `SemverVersion`, or `undefined` if the input does not
|
|
20
|
+
* match the strict `\d+\.\d+\.\d+` shape.
|
|
21
|
+
*/
|
|
22
|
+
export function parseVersion(version) {
|
|
23
|
+
const match = VERSION_REGEX.exec(version);
|
|
24
|
+
if (match === null) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `Array#sort`-style comparator for `SemverVersion` tuples: returns a negative
|
|
31
|
+
* number, zero, or a positive number depending on whether `a` is lower than,
|
|
32
|
+
* equal to, or greater than `b`.
|
|
33
|
+
*/
|
|
34
|
+
export function compare(a, b) {
|
|
35
|
+
if (a[0] !== b[0]) {
|
|
36
|
+
return a[0] - b[0];
|
|
37
|
+
}
|
|
38
|
+
if (a[1] !== b[1]) {
|
|
39
|
+
return a[1] - b[1];
|
|
40
|
+
}
|
|
41
|
+
return a[2] - b[2];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns `true` if `a` and `b` represent the same `MAJOR.MINOR.PATCH`.
|
|
45
|
+
*/
|
|
46
|
+
export function equals(a, b) {
|
|
47
|
+
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns `true` if `compared` is strictly lower than `comparator`.
|
|
51
|
+
*/
|
|
52
|
+
export function lowerThan(compared, comparator) {
|
|
53
|
+
return compare(compared, comparator) < 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Returns `true` if `compared` is lower than or equal to `comparator`.
|
|
57
|
+
*/
|
|
58
|
+
export function lowerThanOrEqual(compared, comparator) {
|
|
59
|
+
return compare(compared, comparator) <= 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns `true` if `compared` is strictly greater than `comparator`.
|
|
63
|
+
*/
|
|
64
|
+
export function greaterThan(compared, comparator) {
|
|
65
|
+
return compare(compared, comparator) > 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns `true` if `compared` is greater than or equal to `comparator`.
|
|
69
|
+
*/
|
|
70
|
+
export function greaterThanOrEqual(compared, comparator) {
|
|
71
|
+
return compare(compared, comparator) >= 0;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=fast-semver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast-semver.js","sourceRoot":"","sources":["../../src/fast-semver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,aAAa,GAAG,4CAA4C,CAAC;AAEnE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,CAAgB,EAAE,CAAgB;IACxD,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,CAAgB,EAAE,CAAgB;IACvD,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,QAAuB,EACvB,UAAyB;IAEzB,OAAO,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuB,EACvB,UAAyB;IAEzB,OAAO,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,QAAuB,EACvB,UAAyB;IAEzB,OAAO,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAuB,EACvB,UAAyB;IAEzB,OAAO,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC"}
|
package/dist/src/fs.d.ts
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves paths to their true (on-disk) casing, caching directory listings
|
|
3
|
+
* and resolutions so repeated lookups against the same directories don't re-hit
|
|
4
|
+
* the filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Intended to be used in hot paths where the same `from` directories are seen
|
|
7
|
+
* over and over.
|
|
8
|
+
*
|
|
9
|
+
* Does not resolve symbolic links.
|
|
10
|
+
*
|
|
11
|
+
* This class caches successful resolutions internally, and may do some
|
|
12
|
+
* duplicate work when multiple concurrent lookups for the same path are made
|
|
13
|
+
* before the first one finishes. It does not cache failed resolutions as
|
|
14
|
+
* negative result entries, but it does cache directory listings, so filesystem
|
|
15
|
+
* changes may not be observed until `clear()` is called. After `clear()`,
|
|
16
|
+
* previously cached directory and resolution data is discarded. If profiling
|
|
17
|
+
* shows that this work duplication is a problem, we can either cache in-flight
|
|
18
|
+
* operations, or add a mutex.
|
|
19
|
+
*/
|
|
20
|
+
export declare class TrueCasePathResolver {
|
|
21
|
+
#private;
|
|
22
|
+
/**
|
|
23
|
+
* Determines the true-case path of a given relative path from a specified
|
|
24
|
+
* directory, without resolving symbolic links.
|
|
25
|
+
*
|
|
26
|
+
* Note that the casing of the `from` path is not checked against the
|
|
27
|
+
* filesystem, and is trusted as-is. This avoids unnecessary directory
|
|
28
|
+
* listings for every ancestor of `from`, which can result in permission
|
|
29
|
+
* errors for directories that are otherwise accessible.
|
|
30
|
+
*
|
|
31
|
+
* @param from The absolute path of the directory to start the search from.
|
|
32
|
+
* @param relativePath The relative path to get the true case of.
|
|
33
|
+
* @returns The true case of the relative path. Returns an empty string if
|
|
34
|
+
* relativePath points to from.
|
|
35
|
+
* @throws FileNotFoundError if the starting directory or the relative path
|
|
36
|
+
* doesn't exist or is ambiguous.
|
|
37
|
+
* @throws NotADirectoryError if the starting directory, or an intermediate
|
|
38
|
+
* segment, is not a directory.
|
|
39
|
+
* @throws FileSystemAccessError for any other error.
|
|
40
|
+
*/
|
|
41
|
+
getFileTrueCase(from: string, relativePath: string): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Clears all cached directory listings and resolutions.
|
|
44
|
+
*/
|
|
45
|
+
clear(): void;
|
|
46
|
+
}
|
|
1
47
|
/**
|
|
2
48
|
* Determines the canonical pathname for a given path, resolving any symbolic
|
|
3
49
|
* links, and returns it.
|
|
@@ -46,6 +92,7 @@ export declare function getAllDirectoriesMatching(dirFrom: string, matches?: (ab
|
|
|
46
92
|
* @throws FileNotFoundError if the starting directory or the relative path doesn't exist.
|
|
47
93
|
* @throws NotADirectoryError if the starting directory is not a directory.
|
|
48
94
|
* @throws FileSystemAccessError for any other error.
|
|
95
|
+
* @deprecated Use {@link TrueCasePathResolver} instead.
|
|
49
96
|
*/
|
|
50
97
|
export declare function getFileTrueCase(from: string, relativePath: string): Promise<string>;
|
|
51
98
|
/**
|
package/dist/src/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/fs.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,oBAAoB;;IAoB/B;;;;;;;;;;;;;;;;;;OAkBG;IACU,eAAe,CAC1B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC;IAyBlB;;OAEG;IACI,KAAK,IAAI,IAAI;CAsIrB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,EACpE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAC1E,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAClE,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAQ5E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,CAAC,CAAC,CA0DZ;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,CAAC,GACR,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,CAAC,GACR,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,kBAAkB,EAAE,MAAM,EAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,UAAU,CAAC,CAiBrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAe1E;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB;AAED;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO/D;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,OAAO,KAAa,CAAC;AAE7C;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS7D;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvE;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYvE;AAED;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;;;;;;;;GASG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB7E;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhE;AAED;;;;;;;GAOG;AACH,wBAAsB,KAAK,CACzB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBlE;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC1B,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB7B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,SAAO,GAClB,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC"}
|
package/dist/src/fs.js
CHANGED
|
@@ -7,6 +7,187 @@ import { JsonStreamStringify } from "json-stream-stringify";
|
|
|
7
7
|
import { ensureError, ensureNodeErrnoExceptionError } from "./error.js";
|
|
8
8
|
import { FileNotFoundError, FileSystemAccessError, InvalidFileFormatError, JsonSerializationError, FileAlreadyExistsError, NotADirectoryError, IsDirectoryError, DirectoryNotEmptyError, } from "./errors/fs.js";
|
|
9
9
|
import { isDirectoryDirentAware, readdirWithFileTypesOrEmpty, } from "./internal/fs.js";
|
|
10
|
+
const AMBIGUOUS_CASING_DIR_ENTRY = Symbol("ambiguous");
|
|
11
|
+
/**
|
|
12
|
+
* Resolves paths to their true (on-disk) casing, caching directory listings
|
|
13
|
+
* and resolutions so repeated lookups against the same directories don't re-hit
|
|
14
|
+
* the filesystem.
|
|
15
|
+
*
|
|
16
|
+
* Intended to be used in hot paths where the same `from` directories are seen
|
|
17
|
+
* over and over.
|
|
18
|
+
*
|
|
19
|
+
* Does not resolve symbolic links.
|
|
20
|
+
*
|
|
21
|
+
* This class caches successful resolutions internally, and may do some
|
|
22
|
+
* duplicate work when multiple concurrent lookups for the same path are made
|
|
23
|
+
* before the first one finishes. It does not cache failed resolutions as
|
|
24
|
+
* negative result entries, but it does cache directory listings, so filesystem
|
|
25
|
+
* changes may not be observed until `clear()` is called. After `clear()`,
|
|
26
|
+
* previously cached directory and resolution data is discarded. If profiling
|
|
27
|
+
* shows that this work duplication is a problem, we can either cache in-flight
|
|
28
|
+
* operations, or add a mutex.
|
|
29
|
+
*/
|
|
30
|
+
export class TrueCasePathResolver {
|
|
31
|
+
/**
|
|
32
|
+
* A cache of DirEntries for the directories we've seen, keyed by their
|
|
33
|
+
* normalized absolute path as read. For example, if the same physical
|
|
34
|
+
* directory is read as `/a/foo` and `/a/Foo`, each path gets its own entry.
|
|
35
|
+
*/
|
|
36
|
+
#dirCache = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* A cache of successful resolutions, grouped by their `from` trusted starting
|
|
39
|
+
* directory.
|
|
40
|
+
*
|
|
41
|
+
* The outer key is the normalized absolute `from` path, and the inner key is
|
|
42
|
+
* the normalized `relativePath`.
|
|
43
|
+
*
|
|
44
|
+
* This keeps paths like `/a/B` + `foo.ts` distinct from `/a` + `B/foo.ts`,
|
|
45
|
+
* even if they point to the same location.
|
|
46
|
+
*/
|
|
47
|
+
#resultCache = new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Determines the true-case path of a given relative path from a specified
|
|
50
|
+
* directory, without resolving symbolic links.
|
|
51
|
+
*
|
|
52
|
+
* Note that the casing of the `from` path is not checked against the
|
|
53
|
+
* filesystem, and is trusted as-is. This avoids unnecessary directory
|
|
54
|
+
* listings for every ancestor of `from`, which can result in permission
|
|
55
|
+
* errors for directories that are otherwise accessible.
|
|
56
|
+
*
|
|
57
|
+
* @param from The absolute path of the directory to start the search from.
|
|
58
|
+
* @param relativePath The relative path to get the true case of.
|
|
59
|
+
* @returns The true case of the relative path. Returns an empty string if
|
|
60
|
+
* relativePath points to from.
|
|
61
|
+
* @throws FileNotFoundError if the starting directory or the relative path
|
|
62
|
+
* doesn't exist or is ambiguous.
|
|
63
|
+
* @throws NotADirectoryError if the starting directory, or an intermediate
|
|
64
|
+
* segment, is not a directory.
|
|
65
|
+
* @throws FileSystemAccessError for any other error.
|
|
66
|
+
*/
|
|
67
|
+
async getFileTrueCase(from, relativePath) {
|
|
68
|
+
const absoluteFrom = path.resolve(from);
|
|
69
|
+
if (path.normalize(relativePath) === ".") {
|
|
70
|
+
// There's no casing to resolve, but we still read `from` so that callers
|
|
71
|
+
// get the documented FileNotFoundError / NotADirectoryError if it
|
|
72
|
+
// doesn't exist or isn't a directory.
|
|
73
|
+
await this.#getDirEntries(absoluteFrom);
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
const resolved = await this.#resolveFrom(absoluteFrom, relativePath);
|
|
77
|
+
const resolvedRelativePath = path.relative(absoluteFrom, resolved);
|
|
78
|
+
if (resolvedRelativePath === ".." ||
|
|
79
|
+
resolvedRelativePath.startsWith(`..${path.sep}`) ||
|
|
80
|
+
path.isAbsolute(resolvedRelativePath)) {
|
|
81
|
+
throw new FileNotFoundError(path.resolve(absoluteFrom, relativePath));
|
|
82
|
+
}
|
|
83
|
+
return resolvedRelativePath;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Clears all cached directory listings and resolutions.
|
|
87
|
+
*/
|
|
88
|
+
clear() {
|
|
89
|
+
this.#dirCache.clear();
|
|
90
|
+
this.#resultCache.clear();
|
|
91
|
+
}
|
|
92
|
+
async #resolveFrom(from, relativePath) {
|
|
93
|
+
const fromCacheKey = this.#getResultFromCacheKey(from);
|
|
94
|
+
const relativePathCacheKey = this.#getResultRelativePathCacheKey(relativePath);
|
|
95
|
+
const cached = this.#resultCache
|
|
96
|
+
.get(fromCacheKey)
|
|
97
|
+
?.get(relativePathCacheKey);
|
|
98
|
+
if (cached !== undefined) {
|
|
99
|
+
return cached;
|
|
100
|
+
}
|
|
101
|
+
const resolved = await this.#doResolveFrom(from, relativePath);
|
|
102
|
+
let resultsFromCache = this.#resultCache.get(fromCacheKey);
|
|
103
|
+
if (resultsFromCache === undefined) {
|
|
104
|
+
resultsFromCache = new Map();
|
|
105
|
+
this.#resultCache.set(fromCacheKey, resultsFromCache);
|
|
106
|
+
}
|
|
107
|
+
resultsFromCache.set(relativePathCacheKey, resolved);
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
async #doResolveFrom(from, relativePath) {
|
|
111
|
+
let currentPath = from;
|
|
112
|
+
const segments = path
|
|
113
|
+
.normalize(relativePath)
|
|
114
|
+
.split(path.sep)
|
|
115
|
+
.filter((s) => s.length > 0 || s === ".");
|
|
116
|
+
for (const requestedName of segments) {
|
|
117
|
+
if (requestedName === "..") {
|
|
118
|
+
currentPath = path.join(currentPath, requestedName);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const entries = await this.#getDirEntries(currentPath);
|
|
122
|
+
const actualName = this.#lookupChild(entries, requestedName);
|
|
123
|
+
if (actualName === undefined) {
|
|
124
|
+
throw new FileNotFoundError(path.resolve(from, relativePath));
|
|
125
|
+
}
|
|
126
|
+
currentPath = path.join(currentPath, actualName);
|
|
127
|
+
}
|
|
128
|
+
return currentPath;
|
|
129
|
+
}
|
|
130
|
+
async #getDirEntries(dirPath) {
|
|
131
|
+
const cacheKey = this.#getDirCacheKey(dirPath);
|
|
132
|
+
const cached = this.#dirCache.get(cacheKey);
|
|
133
|
+
if (cached !== undefined) {
|
|
134
|
+
return cached;
|
|
135
|
+
}
|
|
136
|
+
const entries = await this.#readDirEntries(dirPath);
|
|
137
|
+
this.#dirCache.set(cacheKey, entries);
|
|
138
|
+
return entries;
|
|
139
|
+
}
|
|
140
|
+
async #readDirEntries(dirPath) {
|
|
141
|
+
const names = await readdir(dirPath);
|
|
142
|
+
const exactNames = new Set();
|
|
143
|
+
const caseFoldedNames = new Map();
|
|
144
|
+
for (const name of names) {
|
|
145
|
+
exactNames.add(name);
|
|
146
|
+
const folded = this.#caseFold(name);
|
|
147
|
+
const previous = caseFoldedNames.get(folded);
|
|
148
|
+
if (previous === undefined) {
|
|
149
|
+
caseFoldedNames.set(folded, name);
|
|
150
|
+
}
|
|
151
|
+
else if (previous !== name) {
|
|
152
|
+
caseFoldedNames.set(folded, AMBIGUOUS_CASING_DIR_ENTRY);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { exactNames, caseFoldedNames };
|
|
156
|
+
}
|
|
157
|
+
#lookupChild(entries, requestedName) {
|
|
158
|
+
if (entries.exactNames.has(requestedName)) {
|
|
159
|
+
return requestedName;
|
|
160
|
+
}
|
|
161
|
+
const candidate = entries.caseFoldedNames.get(this.#caseFold(requestedName));
|
|
162
|
+
if (candidate === undefined || candidate === AMBIGUOUS_CASING_DIR_ENTRY) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return candidate;
|
|
166
|
+
}
|
|
167
|
+
#getResultFromCacheKey(from) {
|
|
168
|
+
return path.normalize(from);
|
|
169
|
+
}
|
|
170
|
+
#getResultRelativePathCacheKey(relativePath) {
|
|
171
|
+
return path.normalize(relativePath);
|
|
172
|
+
}
|
|
173
|
+
#getDirCacheKey(dirPath) {
|
|
174
|
+
return path.normalize(dirPath);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Returns a case-folded version of the given name, which can be thought of as
|
|
178
|
+
* a "normalized uppercase" form. This is used to implement case-insensitive
|
|
179
|
+
* comparisons.
|
|
180
|
+
*
|
|
181
|
+
* This is not an exact match with what every filesystem would do, but a good
|
|
182
|
+
* enough approximation for our purposes.
|
|
183
|
+
*
|
|
184
|
+
* @param name The name to fold.
|
|
185
|
+
* @returns The case-folded version of the name.
|
|
186
|
+
*/
|
|
187
|
+
#caseFold(name) {
|
|
188
|
+
return name.normalize("NFC").toUpperCase();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
10
191
|
/**
|
|
11
192
|
* Determines the canonical pathname for a given path, resolving any symbolic
|
|
12
193
|
* links, and returns it.
|
|
@@ -98,21 +279,10 @@ export async function getAllDirectoriesMatching(dirFrom, matches) {
|
|
|
98
279
|
* @throws FileNotFoundError if the starting directory or the relative path doesn't exist.
|
|
99
280
|
* @throws NotADirectoryError if the starting directory is not a directory.
|
|
100
281
|
* @throws FileSystemAccessError for any other error.
|
|
282
|
+
* @deprecated Use {@link TrueCasePathResolver} instead.
|
|
101
283
|
*/
|
|
102
284
|
export async function getFileTrueCase(from, relativePath) {
|
|
103
|
-
|
|
104
|
-
const segments = relativePath.split(path.sep);
|
|
105
|
-
const nextDir = segments[0];
|
|
106
|
-
const nextDirLowerCase = nextDir.toLowerCase();
|
|
107
|
-
for (const dirEntry of dirEntries) {
|
|
108
|
-
if (dirEntry.toLowerCase() === nextDirLowerCase) {
|
|
109
|
-
if (segments.length === 1) {
|
|
110
|
-
return dirEntry;
|
|
111
|
-
}
|
|
112
|
-
return path.join(dirEntry, await getFileTrueCase(path.join(from, dirEntry), path.relative(nextDir, relativePath)));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
throw new FileNotFoundError(path.join(from, relativePath));
|
|
285
|
+
return await new TrueCasePathResolver().getFileTrueCase(from, relativePath);
|
|
116
286
|
}
|
|
117
287
|
/**
|
|
118
288
|
* Checks if a given path is a directory.
|