2017-12-09 07:45:06 +00:00
|
|
|
#!/usr/bin/env node
|
2017-12-03 11:15:02 +00:00
|
|
|
"use strict";
|
|
|
|
|
2017-12-08 13:39:52 +00:00
|
|
|
const args = require("minimist")(process.argv.slice(2), {
|
2018-02-21 19:44:13 +00:00
|
|
|
boolean: [
|
2018-07-10 15:32:19 +00:00
|
|
|
"color", "c",
|
2018-07-19 21:28:41 +00:00
|
|
|
"greatest", "g",
|
2018-05-31 17:33:22 +00:00
|
|
|
"help", "h",
|
|
|
|
"json", "j",
|
2018-07-19 21:28:41 +00:00
|
|
|
"no-color", "n",
|
2018-05-31 17:33:22 +00:00
|
|
|
"prerelease", "p",
|
|
|
|
"update", "u",
|
|
|
|
"version", "v",
|
2018-02-21 19:44:13 +00:00
|
|
|
],
|
|
|
|
alias: {
|
2018-07-10 15:32:19 +00:00
|
|
|
c: "color",
|
2018-04-30 07:05:52 +00:00
|
|
|
e: "exclude",
|
2018-07-19 21:28:41 +00:00
|
|
|
g: "greatest",
|
2018-05-31 17:33:22 +00:00
|
|
|
h: "help",
|
|
|
|
i: "include",
|
2018-02-21 19:44:13 +00:00
|
|
|
j: "json",
|
2018-07-10 15:32:19 +00:00
|
|
|
n: "no-color",
|
2018-05-31 17:33:22 +00:00
|
|
|
p: "prerelease",
|
|
|
|
u: "update",
|
2018-02-21 19:44:13 +00:00
|
|
|
v: "version",
|
2018-04-30 06:31:42 +00:00
|
|
|
},
|
2017-12-08 13:39:52 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (args.help) {
|
2017-12-06 18:05:53 +00:00
|
|
|
process.stdout.write(`usage: updates [options]
|
|
|
|
|
2017-12-03 11:15:02 +00:00
|
|
|
Options:
|
2018-05-31 17:33:22 +00:00
|
|
|
-u, --update Update package.json
|
|
|
|
-p, --prerelease Consider prerelease versions
|
|
|
|
-j, --json Output a JSON object
|
2018-07-19 21:28:41 +00:00
|
|
|
-g, --greatest Prefer greatest over latest version
|
2018-05-31 17:33:22 +00:00
|
|
|
-i, --include <pkg,...> Only include given packages
|
|
|
|
-e, --exclude <pkg,...> Exclude given packages
|
|
|
|
-c, --color Force-enable color output
|
|
|
|
-n, --no-color Disable color output
|
|
|
|
-v, --version Print the version
|
|
|
|
-h, --help Print this help
|
2018-03-03 19:39:39 +00:00
|
|
|
|
2017-12-03 11:15:02 +00:00
|
|
|
Examples:
|
|
|
|
$ updates
|
2017-12-03 11:35:25 +00:00
|
|
|
$ updates -u
|
2018-06-11 18:04:58 +00:00
|
|
|
$ updates -u -e semver
|
2018-03-03 19:39:39 +00:00
|
|
|
`);
|
2017-12-06 18:05:53 +00:00
|
|
|
process.exit(0);
|
|
|
|
}
|
2017-12-03 11:15:02 +00:00
|
|
|
|
|
|
|
const path = require("path");
|
2017-12-06 18:11:40 +00:00
|
|
|
|
2017-12-08 13:39:52 +00:00
|
|
|
if (args.version) {
|
2018-03-03 19:47:48 +00:00
|
|
|
console.info(require(path.join(__dirname, "package.json")).version);
|
2017-12-06 18:11:40 +00:00
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:32:19 +00:00
|
|
|
if (args["color"]) process.env.FORCE_COLOR = "1";
|
|
|
|
if (args["no-color"]) process.env.FORCE_COLOR = "0";
|
2017-12-09 05:49:35 +00:00
|
|
|
|
2017-12-06 18:11:40 +00:00
|
|
|
const fs = require("fs");
|
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/";
|
2017-12-08 13:39:52 +00:00
|
|
|
const packageFile = path.join(process.cwd(), "package.json");
|
2018-07-10 21:04:08 +00:00
|
|
|
const versionPartRe = /^[0-9a-zA-Z-.]+$/;
|
2017-12-03 11:15:02 +00:00
|
|
|
|
2017-12-06 18:05:53 +00:00
|
|
|
const dependencyTypes = [
|
2017-12-03 11:15:02 +00:00
|
|
|
"dependencies",
|
|
|
|
"devDependencies",
|
|
|
|
"peerDependencies",
|
|
|
|
"optionalDependencies"
|
2017-12-06 18:05:53 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
let pkg, pkgStr;
|
2018-02-22 05:28:14 +00:00
|
|
|
const deps = {};
|
2017-12-06 18:05:53 +00:00
|
|
|
|
|
|
|
try {
|
2017-12-08 13:39:52 +00:00
|
|
|
pkgStr = fs.readFileSync(packageFile, "utf8");
|
2017-12-06 18:05:53 +00:00
|
|
|
} catch (err) {
|
2018-05-31 17:33:22 +00:00
|
|
|
finish(new Error(`Unable to open package.json: ${err.message}`));
|
2017-12-06 18:05:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
pkg = JSON.parse(pkgStr);
|
|
|
|
} catch (err) {
|
2018-05-31 17:33:22 +00:00
|
|
|
finish(new Error(`Error parsing package.json: ${err.message}`));
|
2017-12-06 18:05:53 +00:00
|
|
|
}
|
|
|
|
|
2018-04-30 07:11:31 +00:00
|
|
|
let include, exclude;
|
|
|
|
if (args.include) include = args.include.split(",");
|
|
|
|
if (args.exclude) exclude = args.exclude.split(",");
|
2018-04-30 06:31:42 +00:00
|
|
|
|
2018-04-30 08:42:39 +00:00
|
|
|
dependencyTypes.forEach(key => {
|
2017-12-03 11:15:02 +00:00
|
|
|
if (pkg[key]) {
|
2018-04-30 08:42:39 +00:00
|
|
|
const names = Object.keys(pkg[key])
|
|
|
|
.filter(name => !include ? true : include.includes(name))
|
|
|
|
.filter(name => !exclude ? true : !exclude.includes(name));
|
|
|
|
|
|
|
|
names.forEach(name => {
|
2017-12-09 05:47:15 +00:00
|
|
|
const old = pkg[key][name];
|
|
|
|
if (isValidSemverRange(old)) {
|
|
|
|
deps[name] = {old};
|
2017-12-03 11:15:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-04-30 07:05:52 +00:00
|
|
|
if (!Object.keys(deps).length) {
|
2018-04-30 07:11:31 +00:00
|
|
|
finish(new Error("No packages match the given include/exclude parameters"));
|
2018-04-30 07:05:52 +00:00
|
|
|
}
|
|
|
|
|
2018-07-10 15:32:19 +00:00
|
|
|
const fetch = require("make-fetch-happen");
|
|
|
|
|
2018-07-10 14:36:57 +00:00
|
|
|
Promise.all(Object.keys(deps).map(dep => fetch(url + dep).then(r => r.json()))).then(d => {
|
|
|
|
d.forEach(data => {
|
2018-07-10 15:19:11 +00:00
|
|
|
let newVersion;
|
2018-07-19 21:28:41 +00:00
|
|
|
if (args.greatest) {
|
2018-07-10 15:19:11 +00:00
|
|
|
newVersion = findHighestVersion(Object.keys(data.versions));
|
2018-07-19 21:28:41 +00:00
|
|
|
} else {
|
|
|
|
newVersion = data["dist-tags"].latest;
|
2018-07-10 15:19:11 +00:00
|
|
|
}
|
|
|
|
|
2018-04-30 08:42:39 +00:00
|
|
|
const oldRange = deps[data.name].old;
|
2018-07-10 15:19:11 +00:00
|
|
|
const newRange = updateRange(oldRange, newVersion);
|
2018-02-21 19:44:13 +00:00
|
|
|
|
2018-07-10 15:19:11 +00:00
|
|
|
if (!newVersion || oldRange === newRange) {
|
2018-04-30 08:42:39 +00:00
|
|
|
delete deps[data.name];
|
2017-12-03 12:17:31 +00:00
|
|
|
} else {
|
2018-04-30 08:42:39 +00:00
|
|
|
deps[data.name].new = newRange;
|
2017-12-03 12:17:31 +00:00
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// log results
|
|
|
|
if (!Object.keys(deps).length) {
|
2018-05-31 17:33:22 +00:00
|
|
|
finish("All packages are up to date.");
|
2017-12-03 15:30:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// exit if -u is not given
|
2017-12-08 13:39:52 +00:00
|
|
|
if (!args.update) {
|
|
|
|
finish(0);
|
2017-12-03 11:15:02 +00:00
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
|
2018-04-30 08:42:39 +00:00
|
|
|
fs.writeFile(packageFile, updatePkg(), "utf8", err => {
|
2017-12-03 11:15:02 +00:00
|
|
|
if (err) {
|
2018-05-31 17:33:22 +00:00
|
|
|
finish(new Error(`Error writing package.json: ${err.message}`));
|
2017-12-03 11:15:02 +00:00
|
|
|
} else {
|
2017-12-08 13:39:52 +00:00
|
|
|
finish("package.json updated!");
|
2017-12-03 11:15:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-03-03 19:39:39 +00:00
|
|
|
function finish(obj, opts) {
|
|
|
|
opts = opts || {};
|
2017-12-08 13:39:52 +00:00
|
|
|
const output = {};
|
|
|
|
if (typeof obj === "string") {
|
|
|
|
output.message = obj;
|
|
|
|
} else if (obj instanceof Error) {
|
|
|
|
output.error = obj.message;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.json) {
|
|
|
|
output.results = deps;
|
2018-03-03 19:47:48 +00:00
|
|
|
console.info(JSON.stringify(output, null, 2));
|
2017-12-03 12:17:31 +00:00
|
|
|
} else {
|
2017-12-08 13:39:52 +00:00
|
|
|
if (Object.keys(deps).length) {
|
2018-03-03 19:47:48 +00:00
|
|
|
console.info(formatDeps(deps));
|
2017-12-08 13:39:52 +00:00
|
|
|
}
|
|
|
|
if (output.message || output.error) {
|
2018-03-03 19:47:48 +00:00
|
|
|
console.info(output.message || output.error);
|
2017-12-03 12:17:31 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
|
2018-04-30 07:05:52 +00:00
|
|
|
process.exit(opts.exitCode || (output.error ? 1 : 0));
|
2017-12-08 13:39:52 +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]) {
|
2018-07-10 21:04:08 +00:00
|
|
|
if (versionPartRe.test(aParts[i])) {
|
2017-12-03 13:12:30 +00:00
|
|
|
res += chalk[added ? "green" : "red"](aParts.slice(i).join("."));
|
|
|
|
} else {
|
2018-04-30 08:42:39 +00:00
|
|
|
res += aParts[i].split("").map(char => {
|
2018-07-10 21:04:08 +00:00
|
|
|
if (versionPartRe.test(char)) {
|
2018-07-10 15:19:11 +00:00
|
|
|
return chalk[added ? "green" : "red"](char);
|
2017-12-03 13:12:30 +00:00
|
|
|
} else {
|
|
|
|
return char;
|
|
|
|
}
|
2018-07-10 15:19:11 +00:00
|
|
|
}).join("") + chalk[added ? "green" : "red"]("." + aParts.slice(i + 1).join("."));
|
2017-12-03 13:12:30 +00:00
|
|
|
}
|
|
|
|
break;
|
2018-07-10 15:19:11 +00:00
|
|
|
} else {
|
|
|
|
res += aParts[i] + ".";
|
|
|
|
}
|
2017-12-03 13:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2017-12-08 13:39:52 +00:00
|
|
|
function formatDeps() {
|
2018-04-30 08:42:39 +00:00
|
|
|
return columnify(Object.keys(deps).map(dep => {
|
2017-12-08 13:39:52 +00:00
|
|
|
return {
|
2017-12-10 17:32:11 +00:00
|
|
|
"name": dep,
|
2017-12-08 13:39:52 +00:00
|
|
|
"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
|
|
|
}), {
|
2018-05-31 17:33:22 +00:00
|
|
|
columnSplitter: " ".repeat(4),
|
2017-12-03 11:15:02 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-08 13:39:52 +00:00
|
|
|
function updatePkg() {
|
2017-12-03 11:15:02 +00:00
|
|
|
let newPkgStr = pkgStr;
|
2018-04-30 08:42:39 +00:00
|
|
|
Object.keys(deps).forEach(dep => {
|
2017-12-08 13:39:52 +00:00
|
|
|
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-10 17:27:29 +00:00
|
|
|
return range.replace(/[0-9]+\.[0-9]+\.[0-9]+(-.+)?/g, 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;
|
|
|
|
}
|
2018-02-21 19:44:13 +00:00
|
|
|
|
|
|
|
// find the newest version, ignoring prerelease version unless they are requested
|
|
|
|
function findHighestVersion(versions) {
|
2018-07-10 21:04:08 +00:00
|
|
|
let highest = "0.0.0";
|
2018-02-21 19:44:13 +00:00
|
|
|
while (versions.length) {
|
|
|
|
const parsed = semver.parse(versions.pop());
|
|
|
|
if (!args.prerelease && parsed.prerelease.length) continue;
|
2018-07-10 21:04:08 +00:00
|
|
|
if (semver.gt(parsed.version, highest)) {
|
2018-02-21 19:44:13 +00:00
|
|
|
highest = parsed.version;
|
|
|
|
}
|
|
|
|
}
|
2018-07-16 20:24:13 +00:00
|
|
|
return highest === "0.0.0" ? null : highest;
|
2018-02-21 19:44:13 +00:00
|
|
|
}
|