npmcheck.js[source]
xml
<glacius:metadata> | |
<title>npmcheck.js</title> | |
<description>Script to check for updated NPM dependencies</description> | |
<category>Programming</category> | |
<category>Code snippets</category> | |
<category>Node.js</category> | |
<category>JavaScript</category> | |
</glacius:metadata> | |
<p> | |
This script parses a <code>package.json</code> file and checks NPM for updates | |
to all dependencies. | |
</p> | |
<hr /> | |
<glacius:code lang="javascript"><![CDATA[ | |
#!/usr/bin/env node | |
const path = require('path'); | |
const exec = require('child_process').exec; | |
const cwd = process.cwd(); | |
let dir = process.argv[2]; | |
if (!dir) { | |
dir = cwd; | |
} | |
if (!path.isAbsolute(dir)) { | |
dir = path.resolve(dir); | |
} | |
const pkg = require(path.join(dir, 'package.json')); | |
const deps = pkg.dependencies || {}; | |
const devDeps = pkg.devDependencies || {}; | |
const green = `\u001b[32m`; | |
const red = `\u001b[31m`; | |
const cyan = `\u001b[36m`; | |
const blue = `\u001b[34m`; | |
const bold = `\u001b[1m`; | |
const underline = `\u001b[4m`; | |
const reset = `\u001b[0m`; | |
const results = { | |
deps: {}, | |
devDeps: {}, | |
}; | |
const pending = Object.keys(deps).map(name => ({ name, currentVersion: deps[name] })) | |
.concat(Object.keys(devDeps).map(name => ({name, currentVersion: devDeps[name]}))); | |
const stats = { | |
inFlight: 0, | |
total: pending.length, | |
processed: 0, | |
failed: 0, | |
}; | |
const printProgress = () => { | |
process.stdout.write('\r' + ' '.repeat(70)); | |
process.stdout.write(`\r${bold}${stats.processed}/${stats.total}${reset} dependencies checked (${stats.inFlight} in flight)`); | |
}; | |
const check = async (name, currentVersion) => { | |
stats.inFlight++; | |
printProgress(); | |
return new Promise((resolve, reject) => { | |
exec(`npm info ${name} version`, (err, stdout) => { | |
stats.processed++; | |
stats.inFlight--; | |
if (err) { | |
stats.failed++; | |
} | |
printProgress(); | |
const version = (stdout || '').trim() || '???'; | |
if (deps[name]) { | |
results.deps[name] = { | |
current: currentVersion, | |
latest: version, | |
} | |
} else { | |
results.devDeps[name] = { | |
current: currentVersion, | |
latest: version, | |
} | |
} | |
resolve(runNext()); | |
}); | |
}); | |
}; | |
const runNext = () => { | |
const next = pending.shift(); | |
if (next) { | |
return check(next.name, next.currentVersion); | |
} | |
return Promise.resolve(); | |
}; | |
const maxInFlight = 5; | |
const promises = []; | |
for (let i = 0; i < maxInFlight; i++) { | |
const next = pending.shift(); | |
if (!next) { | |
break; | |
} | |
promises.push(check(next.name, next.currentVersion)); | |
} | |
Promise.all(promises) | |
.then(() => { | |
console.log('\r' + ' '.repeat(70)); | |
const maxLength = Object.keys(results.deps) | |
.concat(Object.keys(results.devDeps)) | |
.reduce((max, name) => Math.max(max, name.length), 30); | |
const print = (deps) => { | |
Object.keys(deps).sort().forEach((name) => { | |
const value = deps[name]; | |
const isCurrent = value.current === value.latest; | |
console.log(`${blue}${name}${reset} ` + '.'.repeat(maxLength - name.length + 5) + ' ' + | |
(isCurrent ? `${green}${value.current}${reset}` : `${red}${value.current}${reset} → ${bold}${cyan}${value.latest}${reset}`)); | |
}); | |
}; | |
console.log(`${bold}${underline}Dependencies${reset}`); | |
print(results.deps); | |
console.log(); | |
console.log(`${bold}${underline}Dev Dependencies${reset}`); | |
print(results.devDeps); | |
console.log(); | |
process.exit(); | |
}) | |
.catch((err) => { | |
console.error(err); | |
process.exit(1); | |
}); | |
]]></glacius:code> | |
<h2>Sample output</h2> | |
<glacius:figure glacius:src="npmcheck-output-resized.mp4" glacius:thumbnail="npmcheck-output-poster.jpeg" type="video"> | |
<caption><code>npmcheck.js</code> in action</caption> | |
</glacius:figure> | |