1 | const {promisify} = require('util')
|
---|
2 | const fs = require('fs')
|
---|
3 | const readFile = promisify(fs.readFile)
|
---|
4 | const lstat = promisify(fs.lstat)
|
---|
5 | const readdir = promisify(fs.readdir)
|
---|
6 | const parse = require('json-parse-even-better-errors')
|
---|
7 |
|
---|
8 | const { resolve, dirname, join, relative } = require('path')
|
---|
9 |
|
---|
10 | const rpj = path => readFile(path, 'utf8')
|
---|
11 | .then(data => readBinDir(path, normalize(stripUnderscores(parse(data)))))
|
---|
12 | .catch(er => {
|
---|
13 | er.path = path
|
---|
14 | throw er
|
---|
15 | })
|
---|
16 |
|
---|
17 | const normalizePackageBin = require('npm-normalize-package-bin')
|
---|
18 |
|
---|
19 | // load the directories.bin folder as a 'bin' object
|
---|
20 | const readBinDir = async (path, data) => {
|
---|
21 | if (data.bin)
|
---|
22 | return data
|
---|
23 |
|
---|
24 | const m = data.directories && data.directories.bin
|
---|
25 | if (!m || typeof m !== 'string')
|
---|
26 | return data
|
---|
27 |
|
---|
28 | // cut off any monkey business, like setting directories.bin
|
---|
29 | // to ../../../etc/passwd or /etc/passwd or something like that.
|
---|
30 | const root = dirname(path)
|
---|
31 | const dir = join('.', join('/', m))
|
---|
32 | data.bin = await walkBinDir(root, dir, {})
|
---|
33 | return data
|
---|
34 | }
|
---|
35 |
|
---|
36 | const walkBinDir = async (root, dir, obj) => {
|
---|
37 | const entries = await readdir(resolve(root, dir)).catch(() => [])
|
---|
38 | for (const entry of entries) {
|
---|
39 | if (entry.charAt(0) === '.')
|
---|
40 | continue
|
---|
41 | const f = resolve(root, dir, entry)
|
---|
42 | // ignore stat errors, weird file types, symlinks, etc.
|
---|
43 | const st = await lstat(f).catch(() => null)
|
---|
44 | if (!st)
|
---|
45 | continue
|
---|
46 | else if (st.isFile())
|
---|
47 | obj[entry] = relative(root, f)
|
---|
48 | else if (st.isDirectory())
|
---|
49 | await walkBinDir(root, join(dir, entry), obj)
|
---|
50 | }
|
---|
51 | return obj
|
---|
52 | }
|
---|
53 |
|
---|
54 | // do not preserve _fields set in files, they are sus
|
---|
55 | const stripUnderscores = data => {
|
---|
56 | for (const key of Object.keys(data).filter(k => /^_/.test(k)))
|
---|
57 | delete data[key]
|
---|
58 | return data
|
---|
59 | }
|
---|
60 |
|
---|
61 | const normalize = data => {
|
---|
62 | add_id(data)
|
---|
63 | fixBundled(data)
|
---|
64 | pruneRepeatedOptionals(data)
|
---|
65 | fixScripts(data)
|
---|
66 | fixFunding(data)
|
---|
67 | normalizePackageBin(data)
|
---|
68 | return data
|
---|
69 | }
|
---|
70 |
|
---|
71 | rpj.normalize = normalize
|
---|
72 |
|
---|
73 | const add_id = data => {
|
---|
74 | if (data.name && data.version)
|
---|
75 | data._id = `${data.name}@${data.version}`
|
---|
76 | return data
|
---|
77 | }
|
---|
78 |
|
---|
79 | // it was once common practice to list deps both in optionalDependencies
|
---|
80 | // and in dependencies, to support npm versions that did not know abbout
|
---|
81 | // optionalDependencies. This is no longer a relevant need, so duplicating
|
---|
82 | // the deps in two places is unnecessary and excessive.
|
---|
83 | const pruneRepeatedOptionals = data => {
|
---|
84 | const od = data.optionalDependencies
|
---|
85 | const dd = data.dependencies || {}
|
---|
86 | if (od && typeof od === 'object') {
|
---|
87 | for (const name of Object.keys(od)) {
|
---|
88 | delete dd[name]
|
---|
89 | }
|
---|
90 | }
|
---|
91 | if (Object.keys(dd).length === 0)
|
---|
92 | delete data.dependencies
|
---|
93 | return data
|
---|
94 | }
|
---|
95 |
|
---|
96 | const fixBundled = data => {
|
---|
97 | const bdd = data.bundledDependencies
|
---|
98 | const bd = data.bundleDependencies === undefined ? bdd
|
---|
99 | : data.bundleDependencies
|
---|
100 |
|
---|
101 | if (bd === false)
|
---|
102 | data.bundleDependencies = []
|
---|
103 | else if (bd === true)
|
---|
104 | data.bundleDependencies = Object.keys(data.dependencies || {})
|
---|
105 | else if (bd && typeof bd === 'object') {
|
---|
106 | if (!Array.isArray(bd))
|
---|
107 | data.bundleDependencies = Object.keys(bd)
|
---|
108 | else
|
---|
109 | data.bundleDependencies = bd
|
---|
110 | } else
|
---|
111 | delete data.bundleDependencies
|
---|
112 |
|
---|
113 | delete data.bundledDependencies
|
---|
114 | return data
|
---|
115 | }
|
---|
116 |
|
---|
117 | const fixScripts = data => {
|
---|
118 | if (!data.scripts || typeof data.scripts !== 'object') {
|
---|
119 | delete data.scripts
|
---|
120 | return data
|
---|
121 | }
|
---|
122 |
|
---|
123 | for (const [name, script] of Object.entries(data.scripts)) {
|
---|
124 | if (typeof script !== 'string')
|
---|
125 | delete data.scripts[name]
|
---|
126 | }
|
---|
127 | return data
|
---|
128 | }
|
---|
129 |
|
---|
130 | const fixFunding = data => {
|
---|
131 | if (data.funding && typeof data.funding === 'string')
|
---|
132 | data.funding = { url: data.funding }
|
---|
133 | return data
|
---|
134 | }
|
---|
135 |
|
---|
136 | module.exports = rpj
|
---|