Last active
January 4, 2020 00:07
-
-
Save aparajita/3311405ab7a047426c9a9e2c07852880 to your computer and use it in GitHub Desktop.
npmls: A (much) better npm listing tool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
"use strict"; | |
const child_process = require("child_process"); | |
const fs = require("fs"); | |
const help = `Usage: npmls.js [-gh|--help] [filter...] | |
** Requires Node >= 4 ** | |
Displays an alphabetical listing of top level packages. | |
OPTIONS | |
-g Display global packages. | |
-h|--help Display this help. | |
FILTERING | |
By default, all of the packages are listed in alphabetical order. | |
When listing the current directory, the current package is listed | |
at the top. | |
If you only want to see specific packages, you can filter the package names | |
in the same way the 'ls' command filters filenames. The following matching | |
characters are valid: | |
* Replaces zero or more characters | |
? Replaces one character | |
{one,two[,...]} Selects a comma-delimited list of alternates | |
Passing more than filter ORs the results of each filter. To avoid shell escaping, | |
filters that use matching characters should be quote enclosed. | |
EXAMPLES | |
Given the packages: acorn, eslint, gulp-eslint, gulp-jscs, jscs | |
'eslint' => eslint | |
'*eslint*' => eslint, gulp-eslint | |
'*eslint*' '*jscs*' => eslint, gulp-eslint, gulp-jscs, jscs | |
'*{eslint,jscs}*' => eslint, gulp-eslint, gulp-jscs, jscs | |
'gulp*' => gulp-eslint, gulp-jscs`; | |
function parseOptions() { | |
const options = { filters: [] }; | |
// Skip 'node' and this script in argv | |
for (const arg of process.argv.slice(2)) { | |
let option; | |
switch (arg) { | |
case "-g": | |
options.global = true; | |
break; | |
case "-h": | |
case "--help": | |
options.help = true; | |
break; | |
default: | |
if (arg[0] === "-") { | |
console.log(`unknown option '${arg}'`); | |
process.exit(1); | |
} else { | |
options.filters.push(arg); | |
} | |
} | |
} | |
return options; | |
} | |
function makeFilterRegex(args) { | |
args = args.map(arg => | |
arg | |
.replace(/\*/g, ".*") | |
.replace(/\?/g, ".") | |
.replace(/\{.+?}/g, match => { | |
const alternates = match | |
.slice(1, -1) | |
.split(",") | |
.map(item => item.trim()); | |
return "(" + alternates.join("|") + ")"; | |
}) | |
); | |
return new RegExp("^(?:" + args.join("|") + ")$"); | |
} | |
function parsePackage(pkg) { | |
const match = /^(.+\s+)?([@/\w.-]+?)@(\S+)/.exec(pkg); | |
if (match) { | |
return { | |
name: match[2], | |
version: match[3], | |
warning: match[1] | |
}; | |
} | |
} | |
function ls() { | |
const options = parseOptions(); | |
if (options.help) { | |
console.log(help); | |
process.exit(0); | |
} | |
let pkg = {}; | |
if (!options.global) { | |
try { | |
pkg = JSON.parse(fs.readFileSync("./package.json", "utf8")); | |
} catch (e) { | |
console.error(e.message); | |
return; | |
} | |
} | |
const dependencies = Object.keys(pkg.dependencies || {}); | |
const devDependencies = Object.keys(pkg.devDependencies || {}); | |
let cmd = "npm ls --depth 0"; | |
if (options.global) { | |
cmd += " -g"; | |
} | |
child_process.exec(cmd, (error, stdout, stderr) => { | |
if (error && !stdout) { | |
console.error(error.toString()); | |
process.exit(1); | |
} else { | |
let lines = stdout.trim().split("\n"); | |
const firstLine = lines.shift(); | |
lines = lines.map(line => line.substr(4)); | |
if (options.global) { | |
console.log(`\x1b[32m\x1b[1m${firstLine}\x1b[0m`); | |
} else { | |
// Start with the current package | |
const { name, version } = parsePackage(firstLine); | |
console.log(`\x1b[4m\x1b[32m\x1b[1m${name}\x1b[0m \x1b[37m(${version})\x1b[0m`); | |
} | |
let filter; | |
if (options.filters.length) { | |
filter = makeFilterRegex(options.filters); | |
} | |
const modules = lines.reduce((result, line) => { | |
const info = parsePackage(line); | |
if (info) { | |
result.push(info); | |
} | |
return result; | |
}, []); | |
for (const module of modules) { | |
if (filter && !filter.test(module.name)) { | |
continue; | |
} | |
const color = (options.global || dependencies.includes(module.name)) ? '\x1b[33m' : ''; | |
const warning = module.warning ? ` \x1b[31m[${module.warning.trim()}]\x1b[0m` : ''; | |
console.log(`${color}${module.name}\x1b[0m \x1b[37m(${module.version})\x1b[0m${warning}`); | |
} | |
} | |
}); | |
} | |
ls(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment