From e0badb2f2d172ad2e0b3c48a8313e84df5faf0c4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 8 Aug 2023 10:39:51 +0200 Subject: [PATCH] fix(kernel): fast module loading fails on Windows (EPERM) In https://github.com/aws/jsii/pull/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. --- packages/@jsii/kernel/src/link.ts | 58 +++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/@jsii/kernel/src/link.ts b/packages/@jsii/kernel/src/link.ts index 8d518209da..830c228fbe 100644 --- a/packages/@jsii/kernel/src/link.ts +++ b/packages/@jsii/kernel/src/link.ts @@ -6,6 +6,7 @@ import { statSync, symlinkSync, } from 'fs'; +import * as os from 'os'; import { dirname, join } from 'path'; /** @@ -16,31 +17,50 @@ import { dirname, join } from 'path'; const PRESERVE_SYMLINKS = process.execArgv.includes('--preserve-symlinks'); /** - * Creates directories containing hard links if possible, and falls back on - * copy otherwise. + * Link existing to destination directory * - * @param existing is the original file or directory to link. - * @param destination is the new file or directory to create. + * - If Node has been started with a module resolution strategy that does not + * resolve symlinks (so peerDependencies can be found), use symlinking. + * Symlinking may fail on Windows for non-Admin users. + * - If not symlinking the entire directory, crawl the directory tree and + * hardlink all files (if possible), copying them if not. + * + * @param existingRoot is the original file or directory to link. + * @param destinationRoot is the new file or directory to create. */ -export function link(existing: string, destination: string): void { - if (PRESERVE_SYMLINKS) { - mkdirSync(dirname(destination), { recursive: true }); - symlinkSync(existing, destination); - return; - } +export function link(existingRoot: string, destinationRoot: string): void { + mkdirSync(dirname(destinationRoot), { recursive: true }); - const stat = statSync(existing); - if (!stat.isDirectory()) { + if (PRESERVE_SYMLINKS) { try { - linkSync(existing, destination); - } catch { - copyFileSync(existing, destination); + symlinkSync(existingRoot, destinationRoot); + return; + } catch (e: any) { + // On Windows, non-Admin users aren't allowed to create symlinks. In that case, fall back to the copying workflow. + const winNoSymlink = e.code === 'EPERM' && os.platform() === 'win32'; + + if (!winNoSymlink) { + throw e; + } } - return; } + // Fall back to the slow method + recurse(existingRoot, destinationRoot); + + function recurse(existing: string, destination: string): void { + const stat = statSync(existing); + if (!stat.isDirectory()) { + try { + linkSync(existing, destination); + } catch { + copyFileSync(existing, destination); + } + return; + } - mkdirSync(destination, { recursive: true }); - for (const file of readdirSync(existing)) { - link(join(existing, file), join(destination, file)); + mkdirSync(destination, { recursive: true }); + for (const file of readdirSync(existing)) { + recurse(join(existing, file), join(destination, file)); + } } }