index.js
3.59 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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
const {promisify} = require('util')
const fs = require('fs')
const readFile = promisify(fs.readFile)
const lstat = promisify(fs.lstat)
const readdir = promisify(fs.readdir)
const parse = require('json-parse-even-better-errors')
const { resolve, dirname, join, relative } = require('path')
const rpj = path => readFile(path, 'utf8')
.then(data => readBinDir(path, normalize(stripUnderscores(parse(data)))))
.catch(er => {
er.path = path
throw er
})
const normalizePackageBin = require('npm-normalize-package-bin')
// load the directories.bin folder as a 'bin' object
const readBinDir = async (path, data) => {
if (data.bin)
return data
const m = data.directories && data.directories.bin
if (!m || typeof m !== 'string')
return data
// cut off any monkey business, like setting directories.bin
// to ../../../etc/passwd or /etc/passwd or something like that.
const root = dirname(path)
const dir = join('.', join('/', m))
data.bin = await walkBinDir(root, dir, {})
return data
}
const walkBinDir = async (root, dir, obj) => {
const entries = await readdir(resolve(root, dir)).catch(() => [])
for (const entry of entries) {
if (entry.charAt(0) === '.')
continue
const f = resolve(root, dir, entry)
// ignore stat errors, weird file types, symlinks, etc.
const st = await lstat(f).catch(() => null)
if (!st)
continue
else if (st.isFile())
obj[entry] = relative(root, f)
else if (st.isDirectory())
await walkBinDir(root, join(dir, entry), obj)
}
return obj
}
// do not preserve _fields set in files, they are sus
const stripUnderscores = data => {
for (const key of Object.keys(data).filter(k => /^_/.test(k)))
delete data[key]
return data
}
const normalize = data => {
add_id(data)
fixBundled(data)
pruneRepeatedOptionals(data)
fixScripts(data)
fixFunding(data)
normalizePackageBin(data)
return data
}
rpj.normalize = normalize
const add_id = data => {
if (data.name && data.version)
data._id = `${data.name}@${data.version}`
return data
}
// it was once common practice to list deps both in optionalDependencies
// and in dependencies, to support npm versions that did not know abbout
// optionalDependencies. This is no longer a relevant need, so duplicating
// the deps in two places is unnecessary and excessive.
const pruneRepeatedOptionals = data => {
const od = data.optionalDependencies
const dd = data.dependencies || {}
if (od && typeof od === 'object') {
for (const name of Object.keys(od)) {
delete dd[name]
}
}
if (Object.keys(dd).length === 0)
delete data.dependencies
return data
}
const fixBundled = data => {
const bdd = data.bundledDependencies
const bd = data.bundleDependencies === undefined ? bdd
: data.bundleDependencies
if (bd === false)
data.bundleDependencies = []
else if (bd === true)
data.bundleDependencies = Object.keys(data.dependencies || {})
else if (bd && typeof bd === 'object') {
if (!Array.isArray(bd))
data.bundleDependencies = Object.keys(bd)
else
data.bundleDependencies = bd
} else
delete data.bundleDependencies
delete data.bundledDependencies
return data
}
const fixScripts = data => {
if (!data.scripts || typeof data.scripts !== 'object') {
delete data.scripts
return data
}
for (const [name, script] of Object.entries(data.scripts)) {
if (typeof script !== 'string')
delete data.scripts[name]
}
return data
}
const fixFunding = data => {
if (data.funding && typeof data.funding === 'string')
data.funding = { url: data.funding }
return data
}
module.exports = rpj