updates/updates.js

202 lines
4.8 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node --throw-deprecation --trace-deprecation --trace-warnings
2017-12-03 11:15:02 +00:00
"use strict";
const args = require("minimist")(process.argv.slice(2), {
boolean: ["update", "u", "json", "j", "color", "no-color", "version", "v", "help", "h"]
});
args.update = args.update || args.u;
args.version = args.version || args.v;
args.json = args.json || args.j;
args.help = args.help || args.h;
if (args.help) {
process.stdout.write(`usage: updates [options]
2017-12-03 11:15:02 +00:00
Options:
2017-12-06 18:13:44 +00:00
--update, -u Update package.json
--json, -j Output a JSON object
2017-12-06 18:11:40 +00:00
--color Force-enable color output
--no-color Disable color output
--version, -v Print the version
--help -h Print this help
2017-12-03 11:15:02 +00:00
Examples:
$ updates
2017-12-03 11:35:25 +00:00
$ updates -u
2017-12-06 18:11:40 +00:00
$ updates -j\n`);
process.exit(0);
}
2017-12-03 11:15:02 +00:00
2017-12-03 12:17:31 +00:00
const os = require("os");
2017-12-03 11:15:02 +00:00
const path = require("path");
2017-12-06 18:11:40 +00:00
if (args.version) {
2017-12-06 18:11:40 +00:00
process.stdout.write(require(path.join(__dirname, "package.json")).version + os.EOL);
process.exit(0);
}
const fs = require("fs");
const got = require("got");
2017-12-03 11:15:02 +00:00
const semver = require("semver");
const columnify = require("columnify");
const chalk = require("chalk");
const esc = require("escape-string-regexp");
const url = "https://registry.npmjs.org/";
const packageFile = path.join(process.cwd(), "package.json");
2017-12-03 11:15:02 +00:00
const dependencyTypes = [
2017-12-03 11:15:02 +00:00
"dependencies",
"devDependencies",
"peerDependencies",
"bundledDependencies",
"optionalDependencies"
];
let pkg, pkgStr;
try {
pkgStr = fs.readFileSync(packageFile, "utf8");
} catch (err) {
finish(new Error("Unable to open package.json."));
}
try {
pkg = JSON.parse(pkgStr);
} catch (err) {
finish(new Error("Error parsing package.json:" + err.message));
}
const deps = {};
dependencyTypes.forEach(function(key) {
2017-12-03 11:15:02 +00:00
if (pkg[key]) {
Object.keys(pkg[key]).forEach(function(name) {
const range = pkg[key][name];
if (isValidSemverRange(range)) {
deps[name] = {range};
2017-12-03 11:15:02 +00:00
}
});
}
});
Promise.all(Object.keys(deps).map(dep => got(url + dep))).then(function(responses) {
responses.forEach(function(res) {
const registryData = JSON.parse(res.body);
const dep = registryData.name;
const oldRange = deps[dep].range;
const newRange = updateRange(oldRange, registryData["dist-tags"].latest);
if (oldRange === newRange) {
delete deps[dep];
2017-12-03 12:17:31 +00:00
} else {
deps[dep] = {old: deps[dep].range, new: newRange};
2017-12-03 12:17:31 +00:00
}
});
// log results
if (!Object.keys(deps).length) {
finish("All packages are up to date.");
2017-12-03 15:30:59 +00:00
}
// exit if -u is not given
if (!args.update) {
finish(0);
2017-12-03 11:15:02 +00:00
}
fs.writeFile(packageFile, updatePkg(), "utf8", function(err) {
2017-12-03 11:15:02 +00:00
if (err) {
finish(new Error("Error writing package.json:" + err.message));
2017-12-03 11:15:02 +00:00
} else {
finish("package.json updated!");
2017-12-03 11:15:02 +00:00
}
});
});
function finish(obj) {
const output = {};
if (typeof obj === "string") {
output.message = obj;
} else if (obj instanceof Error) {
output.error = obj.message;
}
if (args.json) {
output.results = deps;
logStr(JSON.stringify(output, null, 2));
2017-12-03 12:17:31 +00:00
} else {
if (Object.keys(deps).length) {
logStr(formatDeps(deps));
}
if (output.message || output.error) {
logStr(output.message || output.error);
2017-12-03 12:17:31 +00:00
}
}
process.exit(output.error ? 1 : 0);
}
function logStr(str) {
process.stdout.write(str + os.EOL);
2017-12-03 12:17:31 +00:00
}
2017-12-03 13:12:30 +00:00
function highlightDiff(a, b, added) {
const aParts = a.split(/\./);
const bParts = b.split(/\./);
let res = "";
for (let i = 0; i < aParts.length; i++) {
if (aParts[i] !== bParts[i]) {
if (/^[0-9]+$/.test(aParts[i])) {
res += chalk[added ? "green" : "red"](aParts.slice(i).join("."));
} else {
res += aParts[i].split("").map(function(char) {
if (/^[0-9]+$/.test(char)) {
return chalk[added ? "green" : "red"](char + ".");
} else {
return char;
}
2017-12-03 13:25:59 +00:00
}).join("") + chalk[added ? "green" : "red"](aParts.slice(i + 1).join("."));
2017-12-03 13:12:30 +00:00
}
break;
} else res += aParts[i] + ".";
}
return res;
}
function formatDeps() {
return columnify(Object.keys(deps).map(function(dep) {
return {
"package": dep,
"old": highlightDiff(deps[dep].old, deps[dep].new, false),
"new": highlightDiff(deps[dep].new, deps[dep].old, true),
};
2017-12-03 12:17:31 +00:00
}), {
2017-12-03 11:15:02 +00:00
columnSplitter: " ",
});
}
function updatePkg() {
2017-12-03 11:15:02 +00:00
let newPkgStr = pkgStr;
Object.keys(deps).forEach(function(dep) {
const re = new RegExp(`"${esc(dep)}": +"${esc(deps[dep].old)}"`, "g");
newPkgStr = newPkgStr.replace(re, `"${dep}": "${deps[dep].new}"`);
2017-12-03 11:15:02 +00:00
});
return newPkgStr;
}
// naive regex replace
function updateRange(range, version) {
2017-12-03 13:42:11 +00:00
return range.replace(/[0-9]+\.[0-9]+\.[0-9]+(-.+)?/, version);
2017-12-03 11:15:02 +00:00
}
function isValidSemverRange(range) {
let valid = false;
try {
semver.Range(range);
valid = true;
} catch (err) {}
return valid;
}