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>