updates/updates.js

197 lines
4.8 KiB
JavaScript
Raw Normal View History

2017-12-03 11:15:02 +00:00
#!/usr/bin/env node
"use strict";
const args = process.argv.slice(2);
if (args.includes("-h") || args.includes("--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 Enable JSON output
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.includes("-v") || args.includes("--version")) {
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 dependencyTypes = [
2017-12-03 11:15:02 +00:00
"dependencies",
"devDependencies",
"peerDependencies",
"bundledDependencies",
"optionalDependencies"
];
let pkg, pkgStr;
try {
pkgStr = fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8");
} catch (err) {
log("Unable to open package.json.");
process.exit(1);
}
try {
pkg = JSON.parse(pkgStr);
} catch (err) {
log("Error parsing package.json:" + err.message);
process.exit(1);
}
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.push({name, range});
}
});
}
});
Promise.all(deps.map(dep => got(`${url}${dep.name}`))).then(function(responses) {
return responses.map(function(response, i) {
const dep = Object.keys(deps)[i];
const newVersion = JSON.parse(response.body)["dist-tags"]["latest"];
return {
package: deps[dep].name,
old: deps[dep].range,
new: updateRange(deps[dep].range, newVersion),
};
2017-12-07 05:37:45 +00:00
}).filter(function(result) {
return result.old !== result.new;
2017-12-03 11:15:02 +00:00
});
2017-12-07 05:37:45 +00:00
}).then(function(results) {
// log results
2017-12-03 12:17:31 +00:00
if (!results.length) {
log("All packages are up to date.");
2017-12-03 13:57:48 +00:00
process.exit(0);
2017-12-03 15:30:59 +00:00
} else {
if (args.includes("-j") || args.includes("--json")) {
log(results);
2017-12-03 12:17:31 +00:00
} else {
log(formatResults(results));
2017-12-03 12:17:31 +00:00
}
2017-12-03 15:30:59 +00:00
}
// exit if -u is not given
if (!args.includes("-u") && !args.includes("--update")) {
2017-12-03 11:15:02 +00:00
process.exit(0);
}
return results;
}).then(function(results) {
2017-12-03 11:18:28 +00:00
fs.writeFile("package.json", updatePkg(results), "utf8", function(err) {
2017-12-03 11:15:02 +00:00
if (err) {
log(err);
2017-12-03 11:15:02 +00:00
process.exit(1);
} else {
log("package.json updated!");
2017-12-03 11:15:02 +00:00
process.exit(0);
}
});
});
function log(obj) {
if (args.includes("-j") || args.includes("--json")) {
2017-12-03 12:17:31 +00:00
if (typeof obj === "string") {
obj = {message: obj};
} else if (obj instanceof Error) {
obj = {error: obj.message};
}
process.stdout.write(JSON.stringify(obj, null, 2) + os.EOL);
} else {
if (obj instanceof Error) {
process.stderr.write(obj + os.EOL);
} else {
process.stdout.write(obj + os.EOL);
}
}
}
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;
}
2017-12-03 11:15:02 +00:00
function formatResults(results) {
return columnify(results.map(r => Object.assign({}, r)).map(function(output) {
if (output.new !== output.old) {
2017-12-03 11:15:02 +00:00
return {
"package": output.package,
"old": highlightDiff(output.old, output.new, false),
"new": highlightDiff(output.new, output.old, true),
2017-12-03 11:15:02 +00:00
};
}
2017-12-03 12:17:31 +00:00
}), {
2017-12-03 11:15:02 +00:00
columnSplitter: " ",
});
}
2017-12-03 11:18:28 +00:00
function updatePkg(results) {
2017-12-03 11:15:02 +00:00
let newPkgStr = pkgStr;
results.forEach(function(result) {
const re = new RegExp(`"${esc(result.package)}": +"${esc(result.old)}"`, "g");
newPkgStr = newPkgStr.replace(re, `"${result.package}": "${result.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;
}