123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- 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
|