Skip to content

Commit fac6cbb

Browse files
authored
fix(kernel): fast module loading fails on Windows (EPERM) (#4212)
In #4181, a faster method to load modules was introduced: symlinking instead of recursing through the directory tree, mostly affecting the load times of large modules. Since Windows Vista, non-Administrator users on Windows aren't allowed to create symlinks anymore, so this new loading method fails for users working in corporate Windows environments. Catch the error and fall back to the slower copying method if that happens. Fixes #4208. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent 5ebb43b commit fac6cbb

File tree

1 file changed

+39
-19
lines changed

1 file changed

+39
-19
lines changed

packages/@jsii/kernel/src/link.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
statSync,
77
symlinkSync,
88
} from 'fs';
9+
import * as os from 'os';
910
import { dirname, join } from 'path';
1011

1112
/**
@@ -16,31 +17,50 @@ import { dirname, join } from 'path';
1617
const PRESERVE_SYMLINKS = process.execArgv.includes('--preserve-symlinks');
1718

1819
/**
19-
* Creates directories containing hard links if possible, and falls back on
20-
* copy otherwise.
20+
* Link existing to destination directory
2121
*
22-
* @param existing is the original file or directory to link.
23-
* @param destination is the new file or directory to create.
22+
* - If Node has been started with a module resolution strategy that does not
23+
* resolve symlinks (so peerDependencies can be found), use symlinking.
24+
* Symlinking may fail on Windows for non-Admin users.
25+
* - If not symlinking the entire directory, crawl the directory tree and
26+
* hardlink all files (if possible), copying them if not.
27+
*
28+
* @param existingRoot is the original file or directory to link.
29+
* @param destinationRoot is the new file or directory to create.
2430
*/
25-
export function link(existing: string, destination: string): void {
26-
if (PRESERVE_SYMLINKS) {
27-
mkdirSync(dirname(destination), { recursive: true });
28-
symlinkSync(existing, destination);
29-
return;
30-
}
31+
export function link(existingRoot: string, destinationRoot: string): void {
32+
mkdirSync(dirname(destinationRoot), { recursive: true });
3133

32-
const stat = statSync(existing);
33-
if (!stat.isDirectory()) {
34+
if (PRESERVE_SYMLINKS) {
3435
try {
35-
linkSync(existing, destination);
36-
} catch {
37-
copyFileSync(existing, destination);
36+
symlinkSync(existingRoot, destinationRoot);
37+
return;
38+
} catch (e: any) {
39+
// On Windows, non-Admin users aren't allowed to create symlinks. In that case, fall back to the copying workflow.
40+
const winNoSymlink = e.code === 'EPERM' && os.platform() === 'win32';
41+
42+
if (!winNoSymlink) {
43+
throw e;
44+
}
3845
}
39-
return;
4046
}
47+
// Fall back to the slow method
48+
recurse(existingRoot, destinationRoot);
49+
50+
function recurse(existing: string, destination: string): void {
51+
const stat = statSync(existing);
52+
if (!stat.isDirectory()) {
53+
try {
54+
linkSync(existing, destination);
55+
} catch {
56+
copyFileSync(existing, destination);
57+
}
58+
return;
59+
}
4160

42-
mkdirSync(destination, { recursive: true });
43-
for (const file of readdirSync(existing)) {
44-
link(join(existing, file), join(destination, file));
61+
mkdirSync(destination, { recursive: true });
62+
for (const file of readdirSync(existing)) {
63+
recurse(join(existing, file), join(destination, file));
64+
}
4565
}
4666
}

0 commit comments

Comments
 (0)