Skip to content

Instantly share code, notes, and snippets.

@isaacs
Created December 7, 2019 08:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isaacs/262f31fd9f37b25f27e4f74e20e2c491 to your computer and use it in GitHub Desktop.
Save isaacs/262f31fd9f37b25f27e4f74e20e2c491 to your computer and use it in GitHub Desktop.
const bundled = require('npm-bundled')
const {resolve, basename, dirname} = require('path')
const {promisify} = require('util')
const readdirScoped = promisify(require('readdir-scoped-modules'))
const fs = require('fs')
const stat = promisify(fs.stat)
const readdir_ = promisify(fs.readdir)
const readdir = async path =>
(await readdir_(path)).filter(p => p !== 'node_modules')
const getPkg = path => {
try {
return require(path)
} catch (er) {
return {}
}
}
const getBins = ({path}) => {
const dir = dirname(path)
const base = basename(path)
const scope = basename(dir)
const nm = /^@.+/.test(scope)
? dirname(dir) : dir
const pkg = getPkg(resolve(path, 'package.json'))
if (typeof pkg.bin === 'string' && pkg.name)
pkg.bin = { [pkg.name]: pkg.bin }
if (!pkg.bin || typeof pkg.bin !== 'object')
return []
return Promise.all(Object.values(pkg.bin).map(b => resolve(path, b))
.concat(Object.keys(pkg.bin).map(b => [
resolve(nm, '.bin', b),
resolve(nm, '.bin', b + '.cmd'),
resolve(nm, '.bin', b + '.ps1'),
]).flat()).map(b => stat(b).catch((er) => null).then(st => st && b)))
.then(bins => bins.filter(b => b))
}
const _currentDepth = Symbol('_currentDepth')
const pkgContents = ({
path,
depth = 1,
[_currentDepth]: currentDepth = 0,
}) =>
currentDepth === depth ? Promise.resolve([path])
: Promise.all([
readdir(path).catch(e => null),
readdirScoped(resolve(path, 'node_modules')).catch(e => null),
currentDepth === 0 ? bundled({path}) : [],
currentDepth === 0 ? getBins({path}) : [],
]).then(([direct, nm, bundled, bins]) => [
...(direct ? direct.map(d => resolve(path, d)) : [path]),
...(nm && nm.length === bundled.length ? [resolve(path, 'node_modules')]
: bundled.map(d => resolve(path, 'node_modules', d))),
...bins,
]).then(list =>
list.length === 1 && list[0] === path || currentDepth + 1 === depth ? list
: Promise.all(list.map(path => module.exports({
depth,
path,
[_currentDepth]: currentDepth + 1,
}))))
.then(list => list.flat())
module.exports = options => pkgContents({
...options,
path: resolve(options.path),
})
if (require.main === module) {
const options = { path: '.', depth: Infinity }
process.argv.slice(2).forEach(arg => {
let match
if ((match = arg.match(/^--depth=([0-9]+|Infinity)/)) ||
(match = arg.match(/^-d([0-9]+|Infinity)/)))
options.depth = +match[1]
else
options.path = arg
})
module.exports(options)
.then(list => list.forEach(p => console.log(p)))
.catch(er => console.error(er))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment