Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/compile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ module.exports = async (snippet, { tmpdir = DEFAULT_TMPDIR, allow = {}, nodePath
? allDependencies.filter(dep => {
const name = installDependencies.extractPackageName(dep)
const version = dep.slice(name.length + 1)
try {
const pkgPath = require.resolve(path.join(name, 'package.json'), { paths: nodePaths })
if (version === 'latest') return false
return JSON.parse(readFileSync(pkgPath, 'utf8')).version !== version
} catch {
return true
for (const np of nodePaths) {
try {
const pkg = JSON.parse(readFileSync(path.join(np, name, 'package.json'), 'utf8'))
if (version === 'latest') return false
return pkg.version !== version
} catch {}
}
return true
})
: allDependencies

Expand Down
35 changes: 35 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ test('resolve require dependencies', async t => {
t.is(await run(fn('foo')), false)
})

test('install dependencies not directly in nodePaths', async t => {
const path = require('path')
const { mkdirSync, writeFileSync, rmSync } = require('fs')

// Simulate production: a nodePaths dir deep in node_modules
// with a dependency (cheerio) only in a *parent* node_modules
const testDir = path.join(require('os').tmpdir(), `isolated-fn-nodepaths-${Date.now()}`)
const nodePathDir = path.join(testDir, 'deep', 'nested', 'node_modules')
mkdirSync(nodePathDir, { recursive: true })

// Place a fake package in a parent directory's node_modules.
// require.resolve({ paths }) would find it via traversal,
// but esbuild's nodePaths does not traverse parents.
const parentPkgDir = path.join(testDir, 'node_modules', 'is-standard-emoji')
mkdirSync(parentPkgDir, { recursive: true })
writeFileSync(
path.join(parentPkgDir, 'package.json'),
'{"name":"is-standard-emoji","version":"1.0.0"}'
)
writeFileSync(path.join(parentPkgDir, 'index.js'), 'module.exports = () => false')

const instance = require('..')({ nodePaths: [nodePathDir] })
const fn = instance(emoji => {
const isEmoji = require('is-standard-emoji@1.0.0')
return isEmoji(emoji)
})

// If the bug is present, the dep would be skipped (found via parent traversal)
// and esbuild would use the fake parent version returning false.
// With the fix, the dep is installed fresh into tmpdir and works correctly.
t.is(await run(fn('🙌')), true)

rmSync(testDir, { recursive: true, force: true })
})

test('runs async code', async t => {
const fn = isolatedFunction(async duration => {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
Expand Down