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 137 138 139 140 141 142 143 144 145 146 147
|
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 => {
addId(data)
fixBundled(data)
pruneRepeatedOptionals(data)
fixScripts(data)
fixFunding(data)
normalizePackageBin(data)
return data
}
rpj.normalize = normalize
const addId = 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
|