-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Expand file tree
/
Copy pathbundled-deps-resolve.js
More file actions
95 lines (85 loc) · 2.57 KB
/
Copy pathbundled-deps-resolve.js
File metadata and controls
95 lines (85 loc) · 2.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
const t = require('tap')
const { join } = require('node:path')
const fs = require('node:fs')
const { createRequire } = require('node:module')
const setup = require('./fixtures/setup.js')
// Walk an installed package tree and assert that every non-optional
// production dependency of every bundled package resolves from that
// package's own directory. This catches deps that were dropped from the
// hoisted bundle at pack time (e.g. a dev-only copy shadowing the real
// production version), which unit tests miss because they run against the
// source tree where a nested copy still resolves. See PR #9740 / #9722
// where `sigstore` went missing from the packed bundle.
const findMissingDeps = (npmRoot) => {
const req = createRequire(join(npmRoot, 'package.json'))
const missing = []
const seen = new Set()
const stack = [npmRoot]
while (stack.length) {
const dir = stack.pop()
if (seen.has(dir)) {
continue
}
seen.add(dir)
let pkg
try {
pkg = JSON.parse(fs.readFileSync(join(dir, 'package.json'), 'utf8'))
} catch {
continue
}
const optional = new Set(Object.keys(pkg.optionalDependencies || {}))
for (const dep of Object.keys(pkg.dependencies || {})) {
if (optional.has(dep)) {
continue
}
try {
req.resolve(dep, { paths: [dir] })
} catch {
missing.push(`${pkg.name || dir} -> ${dep}`)
}
}
const nodeModules = join(dir, 'node_modules')
let entries
try {
entries = fs.readdirSync(nodeModules, { withFileTypes: true })
} catch {
continue
}
for (const entry of entries) {
if (entry.name === '.bin') {
continue
}
const entryPath = join(nodeModules, entry.name)
if (entry.name.startsWith('@')) {
for (const scoped of fs.readdirSync(entryPath, { withFileTypes: true })) {
stack.push(join(entryPath, scoped.name))
}
} else {
stack.push(entryPath)
}
}
}
return missing.sort()
}
t.test('bundled production deps all resolve from the packed bundle', async t => {
const {
npm,
npmLocalTarball,
paths: { globalNodeModules },
} = await setup(t, {
testdir: {
project: {
'package.json': { name: 'npm', version: '999.999.999' },
},
},
})
const tarball = await npmLocalTarball()
await npm('install', tarball, '--global')
const npmRoot = join(globalNodeModules, 'npm')
const missing = findMissingDeps(npmRoot)
t.strictSame(
missing,
[],
'every non-optional production dependency resolves in the packed bundle'
)
})