diff --git a/src/analyze.ts b/src/analyze.ts index a4ba9862..ba2a7c81 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -12,6 +12,7 @@ import { Parser } from 'acorn'; import bindings from 'bindings'; import { isIdentifierRead, isLoop, isVarLoop } from './utils/ast-helpers'; import { glob } from 'glob'; +import { fsIgnoreEnoent } from './utils/fs-ignore-enoent'; import { getPackageBase } from './utils/get-package-base'; import { pregyp, nbind } from './utils/binary-locators'; import { @@ -347,6 +348,7 @@ export default async function analyze( ignore: assetDirPath + '/**/node_modules/**/*', dot: true, nodir: true, + fs: fsIgnoreEnoent, }); files .filter( @@ -571,6 +573,7 @@ export default async function analyze( mark: true, ignore: wildcardDirPath + '/**/node_modules/**/*', nodir: true, + fs: fsIgnoreEnoent, }); files .filter( diff --git a/src/utils/fs-ignore-enoent.ts b/src/utils/fs-ignore-enoent.ts new file mode 100644 index 00000000..27a493a9 --- /dev/null +++ b/src/utils/fs-ignore-enoent.ts @@ -0,0 +1,82 @@ +import * as nativeFs from 'fs'; + +function createMockStats() { + return { + isDirectory: () => false, + isFile: () => false, + isSymbolicLink: () => false, + isBlockDevice: () => false, + isCharacterDevice: () => false, + isFIFO: () => false, + isSocket: () => false, + }; +} + +export const fsIgnoreEnoent = { + ...nativeFs, + statSync: (path: any, ...args: any[]) => { + try { + return nativeFs.statSync(path, ...args as any); + } catch (err: any) { + if (err.code === 'ENOENT') { + return createMockStats() as any; + } + throw err; + } + }, + lstatSync: (path: any, ...args: any[]) => { + try { + return nativeFs.lstatSync(path, ...args as any); + } catch (err: any) { + if (err.code === 'ENOENT') { + return createMockStats() as any; + } + throw err; + } + }, + stat: (path: any, ...args: any[]) => { + const cb = args[args.length - 1]; + if (typeof cb !== 'function') return (nativeFs.stat as any)(path, ...args); + args.pop(); + return nativeFs.stat(path, ...args, (err, stats) => { + if (err && err.code === 'ENOENT') { + return cb(null, createMockStats()); + } + return cb(err, stats); + }); + }, + lstat: (path: any, ...args: any[]) => { + const cb = args[args.length - 1]; + if (typeof cb !== 'function') return (nativeFs.lstat as any)(path, ...args); + args.pop(); + return nativeFs.lstat(path, ...args, (err, stats) => { + if (err && err.code === 'ENOENT') { + return cb(null, createMockStats()); + } + return cb(err, stats); + }); + }, + promises: { + ...nativeFs.promises, + stat: async (path: any, opts?: any) => { + try { + return await nativeFs.promises.stat(path, opts); + } catch (err: any) { + if (err.code === 'ENOENT') { + return createMockStats() as any; + } + throw err; + } + }, + lstat: async (path: any, opts?: any) => { + try { + return await nativeFs.promises.lstat(path, opts); + } catch (err: any) { + if (err.code === 'ENOENT') { + return createMockStats() as any; + } + throw err; + } + }, + }, +}; diff --git a/src/utils/sharedlib-emit.ts b/src/utils/sharedlib-emit.ts index 73cb6a8c..aa1a639e 100644 --- a/src/utils/sharedlib-emit.ts +++ b/src/utils/sharedlib-emit.ts @@ -1,6 +1,7 @@ import os from 'os'; import path from 'path'; import { glob } from 'glob'; +import { fsIgnoreEnoent } from './fs-ignore-enoent'; import { getPackageBase } from './get-package-base'; import { Job } from '../node-file-trace'; @@ -28,6 +29,7 @@ export async function sharedLibEmit(p: string, job: Job) { ignore: pkgPath.replaceAll(path.sep, path.posix.sep) + '/**/node_modules/**/*', dot: true, + fs: fsIgnoreEnoent, }, ); await Promise.all(files.map((file) => job.emitFile(file, 'sharedlib', p))); diff --git a/test/unit.test.js b/test/unit.test.js index 662ae82b..ce737182 100644 --- a/test/unit.test.js +++ b/test/unit.test.js @@ -22,6 +22,7 @@ const skipOnWindows = [ 'yarn-workspaces-base-root', 'yarn-workspace-esm', 'asset-symlink', + 'asset-symlink-broken', 'require-symlink', ]; const skipOnMac = []; diff --git a/test/unit/asset-symlink-broken/input.js b/test/unit/asset-symlink-broken/input.js new file mode 100644 index 00000000..b7f24fdd --- /dev/null +++ b/test/unit/asset-symlink-broken/input.js @@ -0,0 +1,5 @@ + +const path = require('path'); +// This will trigger glob traversal in the directory +const dir = path.join(__dirname, './'); +console.log(dir); diff --git a/test/unit/asset-symlink-broken/output.js b/test/unit/asset-symlink-broken/output.js new file mode 100644 index 00000000..e8735f37 --- /dev/null +++ b/test/unit/asset-symlink-broken/output.js @@ -0,0 +1,5 @@ + +[ + "package.json", + "test/unit/asset-symlink-broken/input.js" +]