@nomicfoundation/hardhat-utils 4.1.0 → 4.1.2
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 +12 -0
- package/dist/src/bytecode.d.ts +4 -0
- package/dist/src/bytecode.d.ts.map +1 -1
- package/dist/src/bytecode.js +12 -7
- package/dist/src/bytecode.js.map +1 -1
- package/dist/src/crypto.d.ts.map +1 -1
- package/dist/src/crypto.js +10 -2
- package/dist/src/crypto.js.map +1 -1
- package/dist/src/fs.d.ts.map +1 -1
- package/dist/src/fs.js +21 -35
- package/dist/src/fs.js.map +1 -1
- package/dist/src/internal/bytecode.d.ts +11 -0
- package/dist/src/internal/bytecode.d.ts.map +1 -1
- package/dist/src/internal/bytecode.js +39 -2
- package/dist/src/internal/bytecode.js.map +1 -1
- package/dist/src/internal/fs.d.ts +15 -0
- package/dist/src/internal/fs.d.ts.map +1 -1
- package/dist/src/internal/fs.js +44 -0
- package/dist/src/internal/fs.js.map +1 -1
- package/dist/src/internal/global-dir.d.ts +2 -2
- package/dist/src/internal/global-dir.d.ts.map +1 -1
- package/dist/src/internal/global-dir.js +7 -1
- package/dist/src/internal/global-dir.js.map +1 -1
- package/dist/src/internal/lang.d.ts +3 -2
- package/dist/src/internal/lang.d.ts.map +1 -1
- package/dist/src/internal/lang.js +12 -6
- package/dist/src/internal/lang.js.map +1 -1
- package/dist/src/lang.js +2 -2
- package/dist/src/lang.js.map +1 -1
- package/dist/src/path.d.ts +13 -0
- package/dist/src/path.d.ts.map +1 -1
- package/dist/src/path.js +20 -0
- package/dist/src/path.js.map +1 -1
- package/dist/src/request.d.ts +11 -11
- package/dist/src/request.d.ts.map +1 -1
- package/dist/src/request.js +6 -6
- package/dist/src/request.js.map +1 -1
- package/dist/src/synchronization.js +6 -6
- package/dist/src/synchronization.js.map +1 -1
- package/package.json +7 -6
- package/src/bytecode.ts +15 -6
- package/src/crypto.ts +15 -2
- package/src/fs.ts +33 -53
- package/src/internal/bytecode.ts +64 -5
- package/src/internal/fs.ts +70 -0
- package/src/internal/global-dir.ts +10 -4
- package/src/internal/lang.ts +15 -6
- package/src/lang.ts +2 -2
- package/src/path.ts +23 -0
- package/src/request.ts +16 -16
- package/src/synchronization.ts +6 -6
package/src/fs.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as StreamParserJson from "@streamparser/json-node";
|
|
2
|
+
import type * as JsonStreamStringify from "json-stream-stringify";
|
|
2
3
|
import type { FileHandle } from "node:fs/promises";
|
|
3
4
|
|
|
4
5
|
import fsPromises from "node:fs/promises";
|
|
@@ -6,9 +7,6 @@ import { tmpdir } from "node:os";
|
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { pipeline } from "node:stream/promises";
|
|
8
9
|
|
|
9
|
-
import { JSONParser } from "@streamparser/json-node";
|
|
10
|
-
import { JsonStreamStringify } from "json-stream-stringify";
|
|
11
|
-
|
|
12
10
|
import { ensureError, ensureNodeErrnoExceptionError } from "./error.js";
|
|
13
11
|
import {
|
|
14
12
|
FileNotFoundError,
|
|
@@ -21,10 +19,18 @@ import {
|
|
|
21
19
|
DirectoryNotEmptyError,
|
|
22
20
|
} from "./errors/fs.js";
|
|
23
21
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
collectAllDirectoriesMatching,
|
|
23
|
+
collectAllFilesMatching,
|
|
26
24
|
} from "./internal/fs.js";
|
|
27
25
|
|
|
26
|
+
// We don't load @streamparser/json-node on startup because it's only
|
|
27
|
+
// used by readJsonFileAsStream for very large JSON files.
|
|
28
|
+
let streamParserJson: typeof StreamParserJson | undefined;
|
|
29
|
+
|
|
30
|
+
// We don't load json-stream-stringify on startup because it's only
|
|
31
|
+
// used by writeJsonFileAsStream for very large JSON objects.
|
|
32
|
+
let jsonStreamStringify: typeof JsonStreamStringify | undefined;
|
|
33
|
+
|
|
28
34
|
const AMBIGUOUS_CASING_DIR_ENTRY = Symbol("ambiguous");
|
|
29
35
|
|
|
30
36
|
type CaseFoldedEntry = string | typeof AMBIGUOUS_CASING_DIR_ENTRY;
|
|
@@ -310,33 +316,10 @@ export async function getAllFilesMatching(
|
|
|
310
316
|
matches?: (absolutePathToFile: string) => Promise<boolean> | boolean,
|
|
311
317
|
directoryFilter?: (absolutePathToDir: string) => Promise<boolean> | boolean,
|
|
312
318
|
): Promise<string[]> {
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
const results = await Promise.all(
|
|
316
|
-
dirContent.map(async (dirent) => {
|
|
317
|
-
const absolutePathToFile = path.join(dirFrom, dirent.name);
|
|
318
|
-
if (await isDirectoryDirentAware(absolutePathToFile, dirent)) {
|
|
319
|
-
if (
|
|
320
|
-
directoryFilter === undefined ||
|
|
321
|
-
(await directoryFilter(absolutePathToFile))
|
|
322
|
-
) {
|
|
323
|
-
return await getAllFilesMatching(
|
|
324
|
-
absolutePathToFile,
|
|
325
|
-
matches,
|
|
326
|
-
directoryFilter,
|
|
327
|
-
);
|
|
328
|
-
}
|
|
319
|
+
const results: string[] = [];
|
|
320
|
+
await collectAllFilesMatching(dirFrom, results, matches, directoryFilter);
|
|
329
321
|
|
|
330
|
-
|
|
331
|
-
} else if (matches === undefined || (await matches(absolutePathToFile))) {
|
|
332
|
-
return absolutePathToFile;
|
|
333
|
-
} else {
|
|
334
|
-
return [];
|
|
335
|
-
}
|
|
336
|
-
}),
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
return results.flat();
|
|
322
|
+
return results;
|
|
340
323
|
}
|
|
341
324
|
|
|
342
325
|
/**
|
|
@@ -358,24 +341,10 @@ export async function getAllDirectoriesMatching(
|
|
|
358
341
|
dirFrom: string,
|
|
359
342
|
matches?: (absolutePathToDir: string) => Promise<boolean> | boolean,
|
|
360
343
|
): Promise<string[]> {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const results = await Promise.all(
|
|
364
|
-
dirContent.map(async (dirent) => {
|
|
365
|
-
const absolutePathToFile = path.join(dirFrom, dirent.name);
|
|
366
|
-
if (!(await isDirectoryDirentAware(absolutePathToFile, dirent))) {
|
|
367
|
-
return [];
|
|
368
|
-
}
|
|
344
|
+
const results: string[] = [];
|
|
345
|
+
await collectAllDirectoriesMatching(dirFrom, results, matches);
|
|
369
346
|
|
|
370
|
-
|
|
371
|
-
return absolutePathToFile;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return await getAllDirectoriesMatching(absolutePathToFile, matches);
|
|
375
|
-
}),
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
return results.flat();
|
|
347
|
+
return results;
|
|
379
348
|
}
|
|
380
349
|
|
|
381
350
|
/**
|
|
@@ -459,9 +428,13 @@ export async function readJsonFileAsStream<T>(
|
|
|
459
428
|
|
|
460
429
|
const fileReadStream = fileHandle.createReadStream();
|
|
461
430
|
|
|
431
|
+
if (streamParserJson === undefined) {
|
|
432
|
+
streamParserJson = await import("@streamparser/json-node");
|
|
433
|
+
}
|
|
434
|
+
|
|
462
435
|
// NOTE: We set a separator to disable self-closing to be able to use the parser
|
|
463
436
|
// in the stream.pipeline context; see https://github.com/juanjoDiaz/streamparser-json/issues/47
|
|
464
|
-
const jsonParser = new JSONParser({
|
|
437
|
+
const jsonParser = new streamParserJson.JSONParser({
|
|
465
438
|
separator: "",
|
|
466
439
|
});
|
|
467
440
|
|
|
@@ -469,9 +442,12 @@ export async function readJsonFileAsStream<T>(
|
|
|
469
442
|
fileReadStream,
|
|
470
443
|
jsonParser,
|
|
471
444
|
async (
|
|
472
|
-
elements: AsyncIterable<ParsedElementInfo.ParsedElementInfo>,
|
|
445
|
+
elements: AsyncIterable<StreamParserJson.ParsedElementInfo.ParsedElementInfo>,
|
|
473
446
|
): Promise<any | undefined> => {
|
|
474
|
-
let value:
|
|
447
|
+
let value:
|
|
448
|
+
| StreamParserJson.JsonTypes.JsonPrimitive
|
|
449
|
+
| StreamParserJson.JsonTypes.JsonStruct
|
|
450
|
+
| undefined;
|
|
475
451
|
for await (const element of elements) {
|
|
476
452
|
value = element.value;
|
|
477
453
|
}
|
|
@@ -560,7 +536,11 @@ export async function writeJsonFileAsStream<T>(
|
|
|
560
536
|
try {
|
|
561
537
|
fileHandle = await fsPromises.open(absolutePathToFile, "w");
|
|
562
538
|
|
|
563
|
-
|
|
539
|
+
if (jsonStreamStringify === undefined) {
|
|
540
|
+
jsonStreamStringify = await import("json-stream-stringify");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const jsonStream = new jsonStreamStringify.JsonStreamStringify(object);
|
|
564
544
|
const fileWriteStream = fileHandle.createWriteStream();
|
|
565
545
|
|
|
566
546
|
await pipeline(jsonStream, fileWriteStream);
|
package/src/internal/bytecode.ts
CHANGED
|
@@ -29,6 +29,17 @@ export interface LibraryAddresses {
|
|
|
29
29
|
[contractName: string]: PrefixedHexString;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface BytecodeReplacement {
|
|
33
|
+
start: number;
|
|
34
|
+
length: number;
|
|
35
|
+
value: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface LibraryLinksIndex {
|
|
39
|
+
byName: Map<string, LibraryLink[]>;
|
|
40
|
+
byFqn: Map<string, LibraryLink>;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
/**
|
|
33
44
|
* Check that the provided library addresses are valid Ethereum addresses.
|
|
34
45
|
* If any of them are not, an InvalidLibraryAddressError is thrown.
|
|
@@ -61,13 +72,15 @@ export function checkAmbiguousOrUnnecessaryLinks(
|
|
|
61
72
|
): void {
|
|
62
73
|
const ambiguousLibraries: Record<string, LibraryLink[]> = {};
|
|
63
74
|
const unnecessaryLibraries: string[] = [];
|
|
75
|
+
const neededLibrariesIndex = indexLibraryLinks(neededLibraries);
|
|
64
76
|
|
|
65
77
|
for (const providedLibraryName of Object.keys(providedLibraries)) {
|
|
66
|
-
const
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
const matchingLibraryByFqn =
|
|
79
|
+
neededLibrariesIndex.byFqn.get(providedLibraryName);
|
|
80
|
+
const matchingLibraries =
|
|
81
|
+
matchingLibraryByFqn !== undefined
|
|
82
|
+
? [matchingLibraryByFqn]
|
|
83
|
+
: neededLibrariesIndex.byName.get(providedLibraryName) ?? [];
|
|
71
84
|
|
|
72
85
|
if (matchingLibraries.length > 1) {
|
|
73
86
|
ambiguousLibraries[providedLibraryName] = matchingLibraries;
|
|
@@ -85,6 +98,24 @@ export function checkAmbiguousOrUnnecessaryLinks(
|
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
function indexLibraryLinks(neededLibraries: LibraryLink[]): LibraryLinksIndex {
|
|
102
|
+
const byName = new Map<string, LibraryLink[]>();
|
|
103
|
+
const byFqn = new Map<string, LibraryLink>();
|
|
104
|
+
|
|
105
|
+
for (const neededLibrary of neededLibraries) {
|
|
106
|
+
byFqn.set(neededLibrary.libraryFqn, neededLibrary);
|
|
107
|
+
|
|
108
|
+
const sameNameLibraries = byName.get(neededLibrary.libraryName);
|
|
109
|
+
if (sameNameLibraries === undefined) {
|
|
110
|
+
byName.set(neededLibrary.libraryName, [neededLibrary]);
|
|
111
|
+
} else {
|
|
112
|
+
sameNameLibraries.push(neededLibrary);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { byName, byFqn };
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
/**
|
|
89
120
|
* Check that each library is only provided once, either by its name or its
|
|
90
121
|
* fully qualified name. If a library is provided more than once, an
|
|
@@ -127,3 +158,31 @@ export function checkMissingLibraryAddresses(
|
|
|
127
158
|
|
|
128
159
|
throw new MissingLibrariesError(missingLibraries);
|
|
129
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Apply a set of replacements to a bytecode string, returning the resulting
|
|
164
|
+
* bytecode. Each replacement overwrites `length` characters starting at
|
|
165
|
+
* `start` with `value`. Replacements must not overlap.
|
|
166
|
+
*/
|
|
167
|
+
export function applyBytecodeReplacements(
|
|
168
|
+
bytecode: string,
|
|
169
|
+
replacements: BytecodeReplacement[],
|
|
170
|
+
): string {
|
|
171
|
+
if (replacements.length === 0) {
|
|
172
|
+
return bytecode;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
replacements.sort((a, b) => a.start - b.start);
|
|
176
|
+
|
|
177
|
+
const parts: string[] = [];
|
|
178
|
+
let position = 0;
|
|
179
|
+
|
|
180
|
+
for (const { start, length, value } of replacements) {
|
|
181
|
+
parts.push(bytecode.slice(position, start), value);
|
|
182
|
+
position = start + length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
parts.push(bytecode.slice(position));
|
|
186
|
+
|
|
187
|
+
return parts.join("");
|
|
188
|
+
}
|
package/src/internal/fs.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Dirent } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import fsPromises from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
import { ensureNodeErrnoExceptionError } from "../error.js";
|
|
6
7
|
import { FileSystemAccessError, NotADirectoryError } from "../errors/fs.js";
|
|
@@ -55,3 +56,72 @@ export async function isDirectoryDirentAware(
|
|
|
55
56
|
|
|
56
57
|
return await isDirectory(absolutePath);
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Recursively walk the directory tree rooted at `dirFrom`, appending the
|
|
62
|
+
* absolute paths of every file accepted by `matches` to `results`. Descent
|
|
63
|
+
* into a subdirectory is gated by `directoryFilter`. When either callback is
|
|
64
|
+
* omitted, every file or directory is accepted.
|
|
65
|
+
*/
|
|
66
|
+
export async function collectAllFilesMatching(
|
|
67
|
+
dirFrom: string,
|
|
68
|
+
results: string[],
|
|
69
|
+
matches?: (absolutePathToFile: string) => Promise<boolean> | boolean,
|
|
70
|
+
directoryFilter?: (absolutePathToDir: string) => Promise<boolean> | boolean,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const dirContent = await readdirWithFileTypesOrEmpty(dirFrom);
|
|
73
|
+
|
|
74
|
+
await Promise.all(
|
|
75
|
+
dirContent.map(async (dirent) => {
|
|
76
|
+
const absolutePathToFile = path.join(dirFrom, dirent.name);
|
|
77
|
+
if (await isDirectoryDirentAware(absolutePathToFile, dirent)) {
|
|
78
|
+
if (
|
|
79
|
+
directoryFilter === undefined ||
|
|
80
|
+
(await directoryFilter(absolutePathToFile))
|
|
81
|
+
) {
|
|
82
|
+
await collectAllFilesMatching(
|
|
83
|
+
absolutePathToFile,
|
|
84
|
+
results,
|
|
85
|
+
matches,
|
|
86
|
+
directoryFilter,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return;
|
|
91
|
+
} else if (matches === undefined || (await matches(absolutePathToFile))) {
|
|
92
|
+
results.push(absolutePathToFile);
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Recursively walk the directory tree rooted at `dirFrom`, appending the
|
|
100
|
+
* absolute paths of every directory accepted by `matches` to `results`. When
|
|
101
|
+
* a directory matches, its descendants are not explored; when it does not,
|
|
102
|
+
* the walk continues into its subdirectories. When `matches` is omitted,
|
|
103
|
+
* every directory is accepted and recursion stops at the top level.
|
|
104
|
+
*/
|
|
105
|
+
export async function collectAllDirectoriesMatching(
|
|
106
|
+
dirFrom: string,
|
|
107
|
+
results: string[],
|
|
108
|
+
matches?: (absolutePathToDir: string) => Promise<boolean> | boolean,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
const dirContent = await readdirWithFileTypesOrEmpty(dirFrom);
|
|
111
|
+
|
|
112
|
+
await Promise.all(
|
|
113
|
+
dirContent.map(async (dirent) => {
|
|
114
|
+
const absolutePathToFile = path.join(dirFrom, dirent.name);
|
|
115
|
+
if (!(await isDirectoryDirentAware(absolutePathToFile, dirent))) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (matches === undefined || (await matches(absolutePathToFile))) {
|
|
120
|
+
results.push(absolutePathToFile);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await collectAllDirectoriesMatching(absolutePathToFile, results, matches);
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Paths } from "env-paths";
|
|
2
2
|
|
|
3
3
|
export const HARDHAT_PACKAGE_NAME = "hardhat";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// We don't load env-paths on startup because this module is transitively
|
|
6
|
+
// imported from many places but generatePaths is rarely called during
|
|
7
|
+
// bootstrap.
|
|
8
|
+
let envPaths: ((name: string) => Paths) | undefined;
|
|
9
|
+
|
|
10
|
+
export async function generatePaths(packageName: string): Promise<Paths> {
|
|
11
|
+
if (envPaths === undefined) {
|
|
12
|
+
({ default: envPaths } = await import("env-paths"));
|
|
13
|
+
}
|
|
8
14
|
return envPaths(packageName);
|
|
9
15
|
}
|
package/src/internal/lang.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import rfdc from "rfdc";
|
|
1
|
+
import type Rfdc from "rfdc";
|
|
3
2
|
|
|
4
3
|
import { isObject } from "../lang.js";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// We don't load rfdc on startup because it adds unnecessary
|
|
6
|
+
// overhead when this module is transitively imported but deep clone
|
|
7
|
+
// is not used.
|
|
8
|
+
let clone: ReturnType<typeof Rfdc> | undefined;
|
|
9
|
+
|
|
10
|
+
export async function getDeepCloneFunction(): Promise<ReturnType<typeof Rfdc>> {
|
|
11
|
+
if (clone === undefined) {
|
|
12
|
+
const { default: rfdc } = await import("rfdc");
|
|
9
13
|
clone = rfdc();
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -53,6 +57,9 @@ export function deepMergeImpl<T extends object, S extends object>(
|
|
|
53
57
|
return result;
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
// We don't load fast-equals on startup because it adds unnecessary
|
|
61
|
+
// overhead when this module is transitively imported but deep equal
|
|
62
|
+
// is not used.
|
|
56
63
|
let cachedCustomEqual: ((a: unknown, b: unknown) => boolean) | undefined;
|
|
57
64
|
|
|
58
65
|
/**
|
|
@@ -62,11 +69,13 @@ let cachedCustomEqual: ((a: unknown, b: unknown) => boolean) | undefined;
|
|
|
62
69
|
* @param y The second value to compare.
|
|
63
70
|
* @returns True if the values are deeply equal, false otherwise.
|
|
64
71
|
*/
|
|
65
|
-
export function customFastEqual<T>(x: T, y: T): boolean {
|
|
72
|
+
export async function customFastEqual<T>(x: T, y: T): Promise<boolean> {
|
|
66
73
|
if (cachedCustomEqual !== undefined) {
|
|
67
74
|
return cachedCustomEqual(x, y);
|
|
68
75
|
}
|
|
69
76
|
|
|
77
|
+
const { createCustomEqual } = await import("fast-equals");
|
|
78
|
+
|
|
70
79
|
cachedCustomEqual = createCustomEqual({
|
|
71
80
|
createCustomConfig: (defaultConfig) => ({
|
|
72
81
|
areTypedArraysEqual: (a, b, state) => {
|
package/src/lang.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
* @returns The deep clone of the provided value.
|
|
12
12
|
*/
|
|
13
13
|
export async function deepClone<T>(value: T): Promise<T> {
|
|
14
|
-
const _deepClone = getDeepCloneFunction();
|
|
14
|
+
const _deepClone = await getDeepCloneFunction();
|
|
15
15
|
|
|
16
16
|
return _deepClone<T>(value);
|
|
17
17
|
}
|
|
@@ -24,7 +24,7 @@ export async function deepClone<T>(value: T): Promise<T> {
|
|
|
24
24
|
* @returns True if the values are deeply equal, false otherwise.
|
|
25
25
|
*/
|
|
26
26
|
export async function deepEqual<T>(x: T, y: T): Promise<boolean> {
|
|
27
|
-
return customFastEqual(x, y);
|
|
27
|
+
return await customFastEqual(x, y);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
package/src/path.ts
CHANGED
|
@@ -53,3 +53,26 @@ export function shortenPath(absolutePath: string): string {
|
|
|
53
53
|
|
|
54
54
|
return absolutePath;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns a version of {@link name} that is safe to use as a single filename
|
|
59
|
+
* component on POSIX, macOS, and Windows.
|
|
60
|
+
*
|
|
61
|
+
* Strips characters that are reserved on at least one of those platforms
|
|
62
|
+
* (`<>:"/\|?*` and ASCII control chars), trims trailing dots and whitespace
|
|
63
|
+
* (Windows silently strips them on write), and falls back to `_` if the
|
|
64
|
+
* result is empty or one of the path-traversal literals `.` / `..`.
|
|
65
|
+
*
|
|
66
|
+
* @param name The string to sanitize.
|
|
67
|
+
* @returns A non-empty filename-safe string.
|
|
68
|
+
*/
|
|
69
|
+
export function sanitizeFilename(name: string): string {
|
|
70
|
+
let result = name.replace(/[<>:"/\\|?*\x00-\x1F\x7F]/g, "");
|
|
71
|
+
result = result.replace(/[\s.]+$/, "");
|
|
72
|
+
|
|
73
|
+
if (result === "" || result === "." || result === "..") {
|
|
74
|
+
result = "_";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
package/src/request.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type EventEmitter from "node:events";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
3
|
import type { ParsedUrlQueryInput } from "node:querystring";
|
|
4
|
-
import type * as
|
|
4
|
+
import type * as Undici from "undici";
|
|
5
5
|
|
|
6
6
|
import { open } from "node:fs/promises";
|
|
7
7
|
import querystring from "node:querystring";
|
|
8
|
-
import
|
|
8
|
+
import { pipeline } from "node:stream/promises";
|
|
9
9
|
|
|
10
10
|
import { ensureError } from "./error.js";
|
|
11
11
|
import {
|
|
@@ -24,18 +24,18 @@ import {
|
|
|
24
24
|
handleError,
|
|
25
25
|
} from "./internal/request.js";
|
|
26
26
|
|
|
27
|
-
export const DEFAULT_TIMEOUT_IN_MILLISECONDS = 300_000; // Aligned with
|
|
27
|
+
export const DEFAULT_TIMEOUT_IN_MILLISECONDS = 300_000; // Aligned with undici
|
|
28
28
|
export const DEFAULT_MAX_REDIRECTS = 10;
|
|
29
29
|
export const DEFAULT_POOL_MAX_CONNECTIONS = 128;
|
|
30
30
|
export const DEFAULT_USER_AGENT = "Hardhat";
|
|
31
31
|
|
|
32
|
-
export type Dispatcher =
|
|
33
|
-
export type TestDispatcher =
|
|
34
|
-
export type Interceptable =
|
|
32
|
+
export type Dispatcher = Undici.Dispatcher;
|
|
33
|
+
export type TestDispatcher = Undici.MockAgent;
|
|
34
|
+
export type Interceptable = Undici.Interceptable;
|
|
35
35
|
|
|
36
36
|
// We don't load undici on startup because this package is transitively imported
|
|
37
37
|
// from too many places and it's too complex to optimize case by case.
|
|
38
|
-
let undici: typeof
|
|
38
|
+
let undici: typeof Undici | undefined;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Options to configure the dispatcher.
|
|
@@ -90,7 +90,7 @@ export interface HttpResponse {
|
|
|
90
90
|
export async function getRequest(
|
|
91
91
|
url: string,
|
|
92
92
|
requestOptions: RequestOptions = {},
|
|
93
|
-
dispatcherOrDispatcherOptions?:
|
|
93
|
+
dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
|
|
94
94
|
): Promise<HttpResponse> {
|
|
95
95
|
if (undici === undefined) {
|
|
96
96
|
undici = await import("undici");
|
|
@@ -132,7 +132,7 @@ export async function postJsonRequest(
|
|
|
132
132
|
url: string,
|
|
133
133
|
body: unknown,
|
|
134
134
|
requestOptions: RequestOptions = {},
|
|
135
|
-
dispatcherOrDispatcherOptions?:
|
|
135
|
+
dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
|
|
136
136
|
): Promise<HttpResponse> {
|
|
137
137
|
if (undici === undefined) {
|
|
138
138
|
undici = await import("undici");
|
|
@@ -179,7 +179,7 @@ export async function postFormRequest(
|
|
|
179
179
|
url: string,
|
|
180
180
|
body: unknown,
|
|
181
181
|
requestOptions: RequestOptions = {},
|
|
182
|
-
dispatcherOrDispatcherOptions?:
|
|
182
|
+
dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
|
|
183
183
|
): Promise<HttpResponse> {
|
|
184
184
|
if (undici === undefined) {
|
|
185
185
|
undici = await import("undici");
|
|
@@ -225,7 +225,7 @@ export async function download(
|
|
|
225
225
|
url: string,
|
|
226
226
|
destination: string,
|
|
227
227
|
requestOptions: RequestOptions = {},
|
|
228
|
-
dispatcherOrDispatcherOptions?:
|
|
228
|
+
dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
|
|
229
229
|
): Promise<void> {
|
|
230
230
|
let statusCode: number | undefined;
|
|
231
231
|
let tempFilePath: string | undefined;
|
|
@@ -240,7 +240,7 @@ export async function download(
|
|
|
240
240
|
url,
|
|
241
241
|
requestOptions,
|
|
242
242
|
dispatcherOrDispatcherOptions,
|
|
243
|
-
)) as
|
|
243
|
+
)) as Undici.Dispatcher.ResponseData;
|
|
244
244
|
const { body } = response;
|
|
245
245
|
statusCode = response.statusCode;
|
|
246
246
|
|
|
@@ -257,7 +257,7 @@ export async function download(
|
|
|
257
257
|
|
|
258
258
|
const fileStream = fileHandle.createWriteStream();
|
|
259
259
|
|
|
260
|
-
await
|
|
260
|
+
await pipeline(body, fileStream);
|
|
261
261
|
} finally {
|
|
262
262
|
// NOTE: Historically, not closing the file handle caused issues on Windows,
|
|
263
263
|
// for example, when trying to move the file previously written to by this function
|
|
@@ -284,9 +284,9 @@ export async function download(
|
|
|
284
284
|
|
|
285
285
|
/**
|
|
286
286
|
* Creates a dispatcher based on the provided options.
|
|
287
|
-
* If the `proxy` option is set, it creates a {@link
|
|
288
|
-
* If the `pool` option is set to `true`, it creates a {@link
|
|
289
|
-
* Otherwise, it creates a basic {@link
|
|
287
|
+
* If the `proxy` option is set, it creates a {@link Undici.ProxyAgent} dispatcher.
|
|
288
|
+
* If the `pool` option is set to `true`, it creates a {@link Undici.Pool} dispatcher.
|
|
289
|
+
* Otherwise, it creates a basic {@link Undici.Agent} dispatcher.
|
|
290
290
|
*
|
|
291
291
|
* @param url The url to make requests to.
|
|
292
292
|
* @param options The options to configure the dispatcher. See {@link DispatcherOptions}.
|
package/src/synchronization.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { hostname } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import { createDebug } from "./debug.js";
|
|
7
7
|
import { ensureError, ensureNodeErrnoExceptionError } from "./error.js";
|
|
@@ -382,11 +382,11 @@ export class MultiProcessMutex {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
// Different hostname — can't verify PID remotely
|
|
385
|
-
if (metadata.hostname !==
|
|
385
|
+
if (metadata.hostname !== hostname()) {
|
|
386
386
|
throw new IncompatibleHostnameMultiProcessMutexError(
|
|
387
387
|
lockPath,
|
|
388
388
|
metadata.hostname,
|
|
389
|
-
|
|
389
|
+
hostname(),
|
|
390
390
|
);
|
|
391
391
|
}
|
|
392
392
|
|
|
@@ -475,7 +475,7 @@ export class MultiProcessMutex {
|
|
|
475
475
|
#buildMetadata(): LockMetadata {
|
|
476
476
|
return {
|
|
477
477
|
pid: process.pid,
|
|
478
|
-
hostname:
|
|
478
|
+
hostname: hostname(),
|
|
479
479
|
createdAt: Date.now(),
|
|
480
480
|
...(process.getuid !== undefined ? { uid: process.getuid() } : {}),
|
|
481
481
|
platform: process.platform,
|