2017-12-09 07:45:06 +00:00
|
|
|
#!/usr/bin/env node
|
2021-04-20 18:48:48 +00:00
|
|
|
import ansiRegex from "ansi-regex";
|
|
|
|
import minimist from "minimist";
|
|
|
|
import rat from "registry-auth-token";
|
|
|
|
import rc from "rc";
|
2023-07-04 20:21:21 +00:00
|
|
|
import {parse, coerce, diff, gt, gte, lt, neq, valid, validRange} from "semver";
|
2021-04-20 18:48:48 +00:00
|
|
|
import textTable from "text-table";
|
2024-03-16 03:18:40 +00:00
|
|
|
import {cwd, stdout, argv, env, exit} from "node:process";
|
2022-11-10 21:15:17 +00:00
|
|
|
import hostedGitInfo from "hosted-git-info";
|
2023-07-04 10:41:08 +00:00
|
|
|
import {join, dirname, basename, resolve} from "node:path";
|
2022-12-09 11:46:05 +00:00
|
|
|
import {lstatSync, readFileSync, truncateSync, writeFileSync, accessSync} from "node:fs";
|
2022-07-29 23:54:31 +00:00
|
|
|
import {timerel} from "timerel";
|
2023-05-23 19:56:51 +00:00
|
|
|
import supportsColor from "supports-color";
|
2023-05-23 20:40:16 +00:00
|
|
|
import {magenta, red, green, disableColor} from "glowie";
|
2023-06-16 20:45:23 +00:00
|
|
|
import {getProperty} from "dot-prop";
|
2023-06-17 23:30:21 +00:00
|
|
|
import pAll from "p-all";
|
2023-06-18 00:02:32 +00:00
|
|
|
import memize from "memize";
|
2023-08-31 00:14:02 +00:00
|
|
|
import picomatch from "picomatch";
|
2024-05-23 21:53:45 +00:00
|
|
|
import {version} from "./package.json" with {type: "json"};
|
|
|
|
import type {AuthOptions} from "registry-auth-token";
|
|
|
|
import type {AgentOptions} from "node:https";
|
|
|
|
|
|
|
|
type Npmrc = {
|
|
|
|
registry?: string,
|
|
|
|
ca?: string,
|
|
|
|
cafile?: string,
|
|
|
|
[other: string]: any,
|
|
|
|
}
|
2020-10-05 21:42:57 +00:00
|
|
|
|
2024-05-23 22:24:06 +00:00
|
|
|
type Dep = {
|
|
|
|
"old": string,
|
|
|
|
"new": string,
|
|
|
|
"oldPrint"?: string,
|
|
|
|
"newPrint"?: string,
|
|
|
|
"oldOriginal"?: string,
|
|
|
|
"info"?: string,
|
|
|
|
"age"?: string,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Deps = {
|
|
|
|
[name: string]: Dep,
|
|
|
|
}
|
|
|
|
|
|
|
|
type DepsByMode = {
|
|
|
|
[mode: string]: Deps,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Output = {
|
|
|
|
results: {
|
|
|
|
[mode: string]: {
|
|
|
|
[type: string]: Deps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type NpmData = {[other: string]: any};
|
|
|
|
|
|
|
|
type FindVersionOpts = {
|
|
|
|
range: string,
|
|
|
|
semvers: Set<string>,
|
|
|
|
usePre: boolean,
|
|
|
|
useRel: boolean,
|
|
|
|
useGreatest: boolean,
|
|
|
|
}
|
|
|
|
|
|
|
|
type FindNewVersionOpts = {
|
|
|
|
mode: string,
|
|
|
|
range: string,
|
|
|
|
usePre: boolean,
|
|
|
|
useRel: boolean,
|
|
|
|
useGreatest: boolean,
|
|
|
|
semvers: Set<string>,
|
|
|
|
}
|
|
|
|
|
2019-12-04 20:15:47 +00:00
|
|
|
// regexes for url dependencies. does only github and only hash or exact semver
|
2019-12-07 18:50:24 +00:00
|
|
|
// https://regex101.com/r/gCZzfK/2
|
2019-12-04 20:15:47 +00:00
|
|
|
const stripRe = /^.*?:\/\/(.*?@)?(github\.com[:/])/i;
|
2020-04-20 09:03:01 +00:00
|
|
|
const partsRe = /^([^/]+)\/([^/#]+)?.*?\/([0-9a-f]+|v?[0-9]+\.[0-9]+\.[0-9]+)$/i;
|
|
|
|
const hashRe = /^[0-9a-f]{7,}$/i;
|
2022-11-10 21:44:24 +00:00
|
|
|
const versionRe = /[0-9]+(\.[0-9]+)?(\.[0-9]+)?/g;
|
2024-05-23 21:53:45 +00:00
|
|
|
const esc = (str: string) => str.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
|
2023-07-04 20:21:21 +00:00
|
|
|
const gitInfo = memize(hostedGitInfo.fromUrl);
|
2023-06-18 00:02:32 +00:00
|
|
|
const registryAuthToken = memize(rat);
|
|
|
|
const normalizeUrl = memize(url => url.endsWith("/") ? url.substring(0, url.length - 1) : url);
|
2024-05-23 21:53:45 +00:00
|
|
|
const packageVersion = version || "0.0.0";
|
2024-03-18 23:55:36 +00:00
|
|
|
const sep = "\0";
|
2020-08-06 22:11:23 +00:00
|
|
|
|
2020-03-25 23:06:31 +00:00
|
|
|
const args = minimist(argv.slice(2), {
|
2018-02-21 19:44:13 +00:00
|
|
|
boolean: [
|
2018-10-15 06:05:54 +00:00
|
|
|
"E", "error-on-outdated",
|
2019-07-12 14:37:24 +00:00
|
|
|
"U", "error-on-unchanged",
|
2018-09-02 19:43:16 +00:00
|
|
|
"h", "help",
|
|
|
|
"j", "json",
|
|
|
|
"n", "no-color",
|
2018-10-04 05:58:20 +00:00
|
|
|
"u", "update",
|
2018-09-02 19:43:16 +00:00
|
|
|
"v", "version",
|
2020-10-20 19:04:33 +00:00
|
|
|
"V", "verbose",
|
2018-02-21 19:44:13 +00:00
|
|
|
],
|
2018-07-24 07:40:47 +00:00
|
|
|
string: [
|
2019-11-19 19:52:28 +00:00
|
|
|
"d", "allow-downgrade",
|
2018-09-02 19:43:16 +00:00
|
|
|
"f", "file",
|
|
|
|
"g", "greatest",
|
2018-12-21 22:17:39 +00:00
|
|
|
"m", "minor",
|
|
|
|
"P", "patch",
|
2018-09-02 19:43:16 +00:00
|
|
|
"p", "prerelease",
|
2019-02-25 21:46:39 +00:00
|
|
|
"R", "release",
|
2018-09-02 19:43:16 +00:00
|
|
|
"r", "registry",
|
2018-10-16 15:18:06 +00:00
|
|
|
"t", "types",
|
2023-06-15 20:57:37 +00:00
|
|
|
"githubapi", // undocumented, only for tests
|
2023-06-16 20:45:23 +00:00
|
|
|
"pypiapi", // undocumented, only for tests
|
2018-07-24 07:40:47 +00:00
|
|
|
],
|
2018-02-21 19:44:13 +00:00
|
|
|
alias: {
|
2019-11-19 19:52:28 +00:00
|
|
|
d: "allow-downgrade",
|
2018-10-15 06:05:54 +00:00
|
|
|
E: "error-on-outdated",
|
2019-07-12 14:37:24 +00:00
|
|
|
U: "error-on-unchanged",
|
2018-10-16 15:18:06 +00:00
|
|
|
e: "exclude",
|
2018-08-23 20:40:19 +00:00
|
|
|
f: "file",
|
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-12-21 22:17:39 +00:00
|
|
|
m: "minor",
|
2018-07-10 15:32:19 +00:00
|
|
|
n: "no-color",
|
2018-12-21 22:17:39 +00:00
|
|
|
P: "patch",
|
2018-05-31 17:33:22 +00:00
|
|
|
p: "prerelease",
|
2018-07-24 07:40:47 +00:00
|
|
|
r: "registry",
|
2019-02-25 21:46:39 +00:00
|
|
|
R: "release",
|
2018-11-19 09:18:32 +00:00
|
|
|
s: "semver",
|
2019-07-13 16:41:30 +00:00
|
|
|
S: "sockets",
|
2018-10-16 15:18:06 +00:00
|
|
|
t: "types",
|
2018-05-31 17:33:22 +00:00
|
|
|
u: "update",
|
2018-02-21 19:44:13 +00:00
|
|
|
v: "version",
|
2020-10-20 19:04:33 +00:00
|
|
|
V: "verbose",
|
2018-04-30 06:31:42 +00:00
|
|
|
},
|
2017-12-08 13:39:52 +00:00
|
|
|
});
|
|
|
|
|
2023-05-23 20:40:16 +00:00
|
|
|
if (args["no-color"] || !supportsColor.stdout) disableColor();
|
2020-08-13 18:37:05 +00:00
|
|
|
|
2023-08-31 00:14:02 +00:00
|
|
|
const greatest = argSetToRegexes(parseMixedArg(args.greatest));
|
|
|
|
const prerelease = argSetToRegexes(parseMixedArg(args.prerelease));
|
|
|
|
const release = argSetToRegexes(parseMixedArg(args.release));
|
|
|
|
const patch = argSetToRegexes(parseMixedArg(args.patch));
|
|
|
|
const minor = argSetToRegexes(parseMixedArg(args.minor));
|
2019-11-19 19:52:28 +00:00
|
|
|
const allowDowngrade = parseMixedArg(args["allow-downgrade"]);
|
2020-03-08 18:12:16 +00:00
|
|
|
const githubApiUrl = args.githubapi ? normalizeUrl(args.githubapi) : "https://api.github.com";
|
2023-06-16 20:45:23 +00:00
|
|
|
const pypiApiUrl = args.pypiapi ? normalizeUrl(args.pypiapi) : "https://pypi.org";
|
2024-05-23 21:53:45 +00:00
|
|
|
const stripV = (str: string) => str.replace(/^v/, "");
|
2020-09-18 15:53:31 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function matchesAny(str: string, set: boolean | Set<RegExp>) {
|
2023-08-31 00:14:02 +00:00
|
|
|
for (const re of (set instanceof Set ? set : [])) {
|
|
|
|
if (re.test(str)) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
const registryUrl = memize((scope: string, npmrc: Npmrc) => {
|
2023-07-04 21:24:55 +00:00
|
|
|
const url = npmrc[`${scope}:registry`] || npmrc.registry;
|
|
|
|
return url.endsWith("/") ? url : `${url}/`;
|
|
|
|
});
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function findUpSync(filename: string, dir: string): string | null {
|
2020-03-08 14:59:50 +00:00
|
|
|
const path = join(dir, filename);
|
2024-03-18 23:59:53 +00:00
|
|
|
try { accessSync(path); return path; } catch {}
|
2020-03-08 14:59:50 +00:00
|
|
|
const parent = dirname(dir);
|
2024-03-18 23:59:53 +00:00
|
|
|
return parent === dir ? null : findUpSync(filename, parent);
|
2020-03-08 14:59:50 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function getAuthAndRegistry(name: string, registry: string, authTokenOpts: AuthOptions, npmrc: Npmrc) {
|
2019-06-28 21:25:10 +00:00
|
|
|
if (!name.startsWith("@")) {
|
2019-07-13 16:03:47 +00:00
|
|
|
return [registryAuthToken(registry, authTokenOpts), registry];
|
2019-06-28 21:25:10 +00:00
|
|
|
} else {
|
2024-05-23 21:53:45 +00:00
|
|
|
const scope = (/@[a-z0-9][\w-.]+/.exec(name) || [""])[0];
|
2020-03-08 18:12:16 +00:00
|
|
|
const url = normalizeUrl(registryUrl(scope, npmrc));
|
2019-06-28 21:25:10 +00:00
|
|
|
if (url !== registry) {
|
|
|
|
try {
|
2019-07-13 16:03:47 +00:00
|
|
|
const newAuth = registryAuthToken(url, authTokenOpts);
|
2022-08-24 23:13:46 +00:00
|
|
|
if (newAuth?.token) return [newAuth, url];
|
2023-01-06 21:59:17 +00:00
|
|
|
} catch {}
|
2019-06-27 17:15:01 +00:00
|
|
|
}
|
2023-01-06 21:59:17 +00:00
|
|
|
return [registryAuthToken(registry, authTokenOpts), registry];
|
2019-02-11 18:39:40 +00:00
|
|
|
}
|
2019-06-27 17:15:01 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
const getFetchOpts = memize((agentOpts: AgentOptions, authType?: string, authToken?: string) => {
|
2023-06-18 00:02:32 +00:00
|
|
|
return {
|
|
|
|
...(Object.keys(agentOpts).length && {agentOpts}),
|
|
|
|
headers: {
|
|
|
|
"user-agent": `updates/${packageVersion}`,
|
|
|
|
...(authToken && {Authorization: `${authType} ${authToken}`}),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function doFetch(url: string, opts: RequestInit) {
|
2024-03-18 23:59:53 +00:00
|
|
|
if (args.verbose) console.error(`${magenta("fetch")} ${url}`);
|
|
|
|
const res = await fetch(url, opts);
|
|
|
|
if (args.verbose) console.error(`${res.ok ? green("done") : red("error")} ${url}`);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function fetchNpmInfo(name: string, type: string, originalRegistry: string, agentOpts: AgentOptions, authTokenOpts: AuthOptions, npmrc: Npmrc) {
|
2024-03-19 00:30:46 +00:00
|
|
|
const [auth, registry] = getAuthAndRegistry(name, originalRegistry, authTokenOpts, npmrc);
|
|
|
|
const packageName = type === "resolutions" ? basename(name) : name;
|
2020-10-21 18:22:26 +00:00
|
|
|
const urlName = packageName.replace(/\//g, "%2f");
|
|
|
|
const url = `${registry}/${urlName}`;
|
2022-08-24 23:13:46 +00:00
|
|
|
|
2024-03-18 23:59:53 +00:00
|
|
|
const res = await doFetch(url, getFetchOpts(agentOpts, auth?.type, auth?.token));
|
2022-06-03 20:55:00 +00:00
|
|
|
if (res?.ok) {
|
2020-10-21 18:22:26 +00:00
|
|
|
return [await res.json(), type, registry, name];
|
2020-03-09 20:54:27 +00:00
|
|
|
} else {
|
2022-06-03 20:55:00 +00:00
|
|
|
if (res?.status && res?.statusText) {
|
2020-03-09 20:54:27 +00:00
|
|
|
throw new Error(`Received ${res.status} ${res.statusText} for ${name} from ${registry}`);
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unable to fetch ${name} from ${registry}`);
|
2019-02-11 18:39:40 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-18 18:55:18 +00:00
|
|
|
}
|
2018-07-29 20:41:46 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function fetchPypiInfo(name: string, type: string, agentOpts: AgentOptions) {
|
2023-06-16 20:45:23 +00:00
|
|
|
const url = `${pypiApiUrl}/pypi/${name}/json`;
|
|
|
|
|
2024-03-18 23:59:53 +00:00
|
|
|
const res = await doFetch(url, getFetchOpts(agentOpts));
|
2023-06-16 20:45:23 +00:00
|
|
|
if (res?.ok) {
|
|
|
|
return [await res.json(), type, null, name];
|
|
|
|
} else {
|
|
|
|
if (res?.status && res?.statusText) {
|
|
|
|
throw new Error(`Received ${res.status} ${res.statusText} for ${name} from PyPi`);
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unable to fetch ${name} from PyPi`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function getInfoUrl({repository, homepage, info}: {repository: string | {[other: string]: any}, homepage: string, info: {[other: string]: any}}, registry: string, name: string) {
|
2023-06-16 20:45:23 +00:00
|
|
|
if (info) { // pypi
|
|
|
|
repository =
|
|
|
|
info.project_urls.repository ||
|
|
|
|
info.project_urls.Repository ||
|
|
|
|
info.project_urls.repo ||
|
|
|
|
info.project_urls.Repo ||
|
|
|
|
info.project_urls.source ||
|
|
|
|
info.project_urls.Source ||
|
|
|
|
info.project_urls["source code"] ||
|
|
|
|
info.project_urls["Source Code"] ||
|
|
|
|
info.project_urls.homepage ||
|
|
|
|
info.project_urls.Homepage ||
|
|
|
|
`https://pypi.org/project/${name}/`;
|
|
|
|
}
|
|
|
|
|
2020-10-03 12:50:52 +00:00
|
|
|
let infoUrl;
|
2019-06-27 17:15:01 +00:00
|
|
|
if (registry === "https://npm.pkg.github.com") {
|
|
|
|
return `https://github.com/${name.replace(/^@/, "")}`;
|
|
|
|
} else if (repository) {
|
2019-07-05 16:35:26 +00:00
|
|
|
const url = typeof repository === "string" ? repository : repository.url;
|
2022-11-10 21:15:17 +00:00
|
|
|
const info = gitInfo(url);
|
2023-06-20 13:42:56 +00:00
|
|
|
const browse = info?.browse?.();
|
|
|
|
if (browse) {
|
2024-03-19 00:30:46 +00:00
|
|
|
infoUrl = browse;
|
2020-10-03 12:50:52 +00:00
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
if (infoUrl && typeof repository !== "string" && repository.directory && info?.treepath) {
|
2020-10-03 12:50:52 +00:00
|
|
|
infoUrl = `${infoUrl}/${info.treepath}/HEAD/${repository.directory}`;
|
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!infoUrl && typeof repository !== "string" && repository?.url && /^https?:/.test(repository.url)) {
|
2020-10-03 12:50:52 +00:00
|
|
|
infoUrl = repository.url;
|
|
|
|
}
|
2023-06-20 13:42:56 +00:00
|
|
|
if (!infoUrl && url) {
|
|
|
|
infoUrl = url;
|
|
|
|
}
|
2020-10-03 12:50:52 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:46:39 +00:00
|
|
|
return infoUrl || homepage || "";
|
2020-09-18 18:55:18 +00:00
|
|
|
}
|
2018-11-20 12:17:19 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function finishWithMessage(message: string) {
|
2024-03-19 00:30:46 +00:00
|
|
|
console.info(args.json ? JSON.stringify({message}) : message);
|
2023-09-05 22:48:25 +00:00
|
|
|
doExit();
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function doExit(err?: Error | void) {
|
2023-09-04 23:00:06 +00:00
|
|
|
if (err) {
|
2024-03-19 00:30:46 +00:00
|
|
|
const error = err.stack ?? err.message;
|
|
|
|
console.info(args.json ? JSON.stringify({error}) : red(error));
|
2017-12-08 13:39:52 +00:00
|
|
|
}
|
2023-09-04 23:00:06 +00:00
|
|
|
process.exit(err ? 1 : 0);
|
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function outputDeps(deps: DepsByMode = {}) {
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const mode of Object.keys(deps)) {
|
|
|
|
for (const value of Object.values(deps[mode])) {
|
2024-05-23 21:53:45 +00:00
|
|
|
if (typeof value.oldPrint === "string") {
|
2023-09-04 23:00:06 +00:00
|
|
|
value.old = value.oldPrint;
|
|
|
|
delete value.oldPrint;
|
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
if (typeof value.newPrint === "string") {
|
2023-09-04 23:00:06 +00:00
|
|
|
value.new = value.newPrint;
|
|
|
|
delete value.newPrint;
|
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
if (typeof value.oldOriginal === "string") {
|
2023-09-04 23:00:06 +00:00
|
|
|
value.old = value.oldOriginal;
|
|
|
|
delete value.oldOriginal;
|
|
|
|
}
|
2022-11-10 21:44:24 +00:00
|
|
|
}
|
2020-03-08 18:12:16 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
let num = 0;
|
|
|
|
for (const mode of Object.keys(deps)) {
|
|
|
|
num += Object.keys(deps[mode]).length;
|
|
|
|
}
|
|
|
|
|
2017-12-08 13:39:52 +00:00
|
|
|
if (args.json) {
|
2024-05-23 21:53:45 +00:00
|
|
|
const output: Output = {results: {}};
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const mode of Object.keys(deps)) {
|
|
|
|
for (const [key, value] of Object.entries(deps[mode])) {
|
2019-12-07 18:43:13 +00:00
|
|
|
const [type, name] = key.split(sep);
|
2023-09-04 23:00:06 +00:00
|
|
|
if (!output.results[mode]) output.results[mode] = {};
|
|
|
|
if (!output.results[mode][type]) output.results[mode][type] = {};
|
|
|
|
output.results[mode][type][name] = value;
|
2019-09-08 19:50:57 +00:00
|
|
|
}
|
2018-07-24 17:41:44 +00:00
|
|
|
}
|
2018-10-25 20:36:41 +00:00
|
|
|
console.info(JSON.stringify(output));
|
2023-09-04 23:00:06 +00:00
|
|
|
} else if (num) {
|
|
|
|
console.info(formatDeps(deps));
|
2017-12-03 12:17:31 +00:00
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
|
2018-10-15 06:05:54 +00:00
|
|
|
if (args["error-on-outdated"]) {
|
2023-09-04 23:00:06 +00:00
|
|
|
return num ? 2 : 0;
|
2019-07-12 14:37:24 +00:00
|
|
|
} else if (args["error-on-unchanged"]) {
|
2023-09-04 23:00:06 +00:00
|
|
|
return num ? 0 : 2;
|
2018-10-15 04:52:37 +00:00
|
|
|
} else {
|
2023-09-04 23:00:06 +00:00
|
|
|
return 0;
|
2018-10-15 04:52:37 +00:00
|
|
|
}
|
2017-12-08 13:39:52 +00:00
|
|
|
}
|
|
|
|
|
2020-03-25 23:06:31 +00:00
|
|
|
// preserve file metadata on windows
|
2024-05-23 21:53:45 +00:00
|
|
|
async function write(file: string, content: string) {
|
2023-07-05 17:37:36 +00:00
|
|
|
const {platform} = await import("node:os");
|
2020-03-25 23:06:31 +00:00
|
|
|
const isWindows = platform() === "win32";
|
|
|
|
if (isWindows) truncateSync(file, 0);
|
|
|
|
writeFileSync(file, content, isWindows ? {flag: "r+"} : undefined);
|
2019-08-20 17:54:07 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function highlightDiff(a: string, b: string, colorFn: (str: string) => string) {
|
2023-06-22 23:45:10 +00:00
|
|
|
if (a === b) return a;
|
2017-12-03 13:12:30 +00:00
|
|
|
const aParts = a.split(/\./);
|
|
|
|
const bParts = b.split(/\./);
|
2018-07-30 16:55:32 +00:00
|
|
|
const versionPartRe = /^[0-9a-zA-Z-.]+$/;
|
2017-12-03 13:12:30 +00:00
|
|
|
|
2022-06-03 21:03:46 +00:00
|
|
|
let res = "";
|
2017-12-03 13:12:30 +00:00
|
|
|
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])) {
|
2023-07-24 09:43:39 +00:00
|
|
|
res += colorFn(aParts.slice(i).join("."));
|
2017-12-03 13:12:30 +00:00
|
|
|
} else {
|
2018-04-30 08:42:39 +00:00
|
|
|
res += aParts[i].split("").map(char => {
|
2023-07-24 09:43:39 +00:00
|
|
|
return versionPartRe.test(char) ? colorFn(char) : char;
|
|
|
|
}).join("") + colorFn(`.${aParts.slice(i + 1).join(".")}`);
|
2017-12-03 13:12:30 +00:00
|
|
|
}
|
|
|
|
break;
|
2018-07-10 15:19:11 +00:00
|
|
|
} else {
|
2020-03-08 10:00:46 +00:00
|
|
|
res += `${aParts[i]}.`;
|
2018-07-10 15:19:11 +00:00
|
|
|
}
|
2017-12-03 13:12:30 +00:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function formatDeps(deps: DepsByMode) {
|
2020-03-08 16:06:58 +00:00
|
|
|
const arr = [["NAME", "OLD", "NEW", "AGE", "INFO"]];
|
2023-07-19 23:02:46 +00:00
|
|
|
const seen = new Set();
|
2019-02-04 18:12:15 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const mode of Object.keys(deps)) {
|
|
|
|
for (const [key, data] of Object.entries(deps[mode])) {
|
|
|
|
const name = key.split(sep)[1];
|
|
|
|
const id = `${mode}|${name}`;
|
|
|
|
if (seen.has(id)) continue;
|
|
|
|
seen.add(id);
|
|
|
|
arr.push([
|
|
|
|
name,
|
|
|
|
highlightDiff(data.old, data.new, red),
|
|
|
|
highlightDiff(data.new, data.old, green),
|
|
|
|
data.age || "",
|
2024-05-23 21:53:45 +00:00
|
|
|
data.info || "",
|
2023-09-04 23:00:06 +00:00
|
|
|
]);
|
|
|
|
}
|
2019-09-08 19:50:57 +00:00
|
|
|
}
|
2019-02-04 18:12:15 +00:00
|
|
|
|
2020-03-08 13:40:48 +00:00
|
|
|
return textTable(arr, {
|
2022-06-03 20:41:48 +00:00
|
|
|
hsep: " ",
|
2022-06-03 21:03:46 +00:00
|
|
|
stringLength: str => str.replace(ansiRegex(), "").length,
|
2017-12-03 11:15:02 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function updatePackageJson(pkgStr: string, deps: Deps) {
|
2017-12-03 11:15:02 +00:00
|
|
|
let newPkgStr = pkgStr;
|
2019-09-08 19:50:57 +00:00
|
|
|
for (const key of Object.keys(deps)) {
|
2022-08-24 23:13:46 +00:00
|
|
|
const name = key.split(sep)[1];
|
2022-11-10 21:59:20 +00:00
|
|
|
const old = deps[key].oldOriginal || deps[key].old;
|
2023-06-19 23:09:54 +00:00
|
|
|
const re = new RegExp(`"${esc(name)}": *"${esc(old)}"`, "g");
|
2019-09-08 19:50:57 +00:00
|
|
|
newPkgStr = newPkgStr.replace(re, `"${name}": "${deps[key].new}"`);
|
2018-08-23 20:28:33 +00:00
|
|
|
}
|
2017-12-03 11:15:02 +00:00
|
|
|
return newPkgStr;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function updateProjectToml(pkgStr: string, deps: Deps) {
|
2023-06-19 23:09:54 +00:00
|
|
|
let newPkgStr = pkgStr;
|
|
|
|
for (const key of Object.keys(deps)) {
|
|
|
|
const name = key.split(sep)[1];
|
|
|
|
const old = deps[key].oldOriginal || deps[key].old;
|
|
|
|
const re = new RegExp(`${esc(name)} *= *"${esc(old)}"`, "g");
|
|
|
|
newPkgStr = newPkgStr.replace(re, `${name} = "${deps[key].new}"`);
|
|
|
|
}
|
|
|
|
return newPkgStr;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function updateRange(range: string, version: string) {
|
2022-11-09 17:30:20 +00:00
|
|
|
return range.replace(/[0-9]+\.[0-9]+\.[0-9]+(-.+)?/g, version);
|
2017-12-03 11:15:02 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function isVersionPrerelease(version: string) {
|
2023-07-04 20:21:21 +00:00
|
|
|
const parsed = parse(version);
|
2019-03-28 20:22:49 +00:00
|
|
|
if (!parsed) return false;
|
|
|
|
return Boolean(parsed.prerelease.length);
|
2019-01-19 19:05:26 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function isRangePrerelease(range: string) {
|
2023-07-04 20:21:21 +00:00
|
|
|
// can not use coerce here because it ignores prerelease tags
|
2019-01-19 19:05:26 +00:00
|
|
|
return /[0-9]+\.[0-9]+\.[0-9]+-.+/.test(range);
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function rangeToVersion(range: string) {
|
2019-01-19 19:05:26 +00:00
|
|
|
try {
|
2024-05-27 21:44:41 +00:00
|
|
|
return coerce(range)?.version ?? "";
|
2020-03-25 23:06:31 +00:00
|
|
|
} catch {
|
2024-05-27 21:44:41 +00:00
|
|
|
return "";
|
2019-01-19 19:05:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function findVersion(data: NpmData, versions: string[], {range, semvers, usePre, useRel, useGreatest}: FindVersionOpts) {
|
2020-03-15 17:23:42 +00:00
|
|
|
let tempVersion = rangeToVersion(range);
|
2018-12-22 15:24:24 +00:00
|
|
|
let tempDate = 0;
|
2020-03-15 17:23:42 +00:00
|
|
|
usePre = isRangePrerelease(range) || usePre;
|
2019-01-20 21:26:30 +00:00
|
|
|
|
|
|
|
if (usePre) {
|
2020-03-15 17:23:42 +00:00
|
|
|
semvers.add("prerelease");
|
|
|
|
if (semvers.has("patch")) semvers.add("prepatch");
|
|
|
|
if (semvers.has("minor")) semvers.add("preminor");
|
|
|
|
if (semvers.has("major")) semvers.add("premajor");
|
2019-01-20 21:26:30 +00:00
|
|
|
}
|
2018-11-19 09:18:32 +00:00
|
|
|
|
|
|
|
for (const version of versions) {
|
2023-07-04 20:21:21 +00:00
|
|
|
const parsed = parse(version);
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!parsed || !tempVersion || parsed.prerelease.length && (!usePre || useRel)) continue;
|
2018-11-19 09:18:32 +00:00
|
|
|
|
2023-07-04 20:21:21 +00:00
|
|
|
const d = diff(tempVersion, parsed.version);
|
|
|
|
if (!d || !semvers.has(d)) continue;
|
2018-12-22 15:24:24 +00:00
|
|
|
|
2019-06-29 13:25:04 +00:00
|
|
|
// some registries like github don't have data.time available, fall back to greatest on them
|
2020-03-15 17:23:42 +00:00
|
|
|
if (useGreatest || !("time" in data)) {
|
2024-05-27 21:44:41 +00:00
|
|
|
if (gte(rangeToVersion(parsed?.version), tempVersion)) {
|
2019-01-19 15:22:06 +00:00
|
|
|
tempVersion = parsed.version;
|
|
|
|
}
|
2018-12-22 15:24:24 +00:00
|
|
|
} else {
|
|
|
|
const date = (new Date(data.time[version])).getTime();
|
|
|
|
if (date >= 0 && date > tempDate) {
|
|
|
|
tempVersion = parsed.version;
|
|
|
|
tempDate = date;
|
2018-11-19 09:18:32 +00:00
|
|
|
}
|
2018-02-21 19:44:13 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-19 09:18:32 +00:00
|
|
|
|
2019-03-28 20:22:49 +00:00
|
|
|
return tempVersion || null;
|
2018-02-21 19:44:13 +00:00
|
|
|
}
|
2018-09-02 19:43:16 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function findNewVersion(data: NpmData, {mode, range, useGreatest, useRel, usePre, semvers}: FindNewVersionOpts) {
|
2023-06-16 20:45:23 +00:00
|
|
|
if (range === "*") return null; // ignore wildcard
|
|
|
|
if (range.includes("||")) return null; // ignore or-chains
|
2023-09-04 23:00:06 +00:00
|
|
|
const versions = Object.keys(mode === "pypi" ? data.releases : data.versions)
|
2023-07-04 20:21:21 +00:00
|
|
|
.filter(version => valid(version));
|
2023-06-16 20:45:23 +00:00
|
|
|
const version = findVersion(data, versions, {range, semvers, usePre, useRel, useGreatest});
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!version) return null;
|
2019-01-19 19:05:26 +00:00
|
|
|
|
2023-06-16 20:45:23 +00:00
|
|
|
if (useGreatest) {
|
2019-01-19 19:05:26 +00:00
|
|
|
return version;
|
|
|
|
} else {
|
2023-06-16 20:45:23 +00:00
|
|
|
let latestTag;
|
2023-06-20 13:42:56 +00:00
|
|
|
let originalLatestTag;
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "pypi") {
|
2023-06-20 13:42:56 +00:00
|
|
|
originalLatestTag = data.info.version; // may not be a 3-part semver
|
2024-05-27 21:44:41 +00:00
|
|
|
latestTag = rangeToVersion(data.info.version); // add .0 to 6.0 so semver eats it
|
2023-06-16 20:45:23 +00:00
|
|
|
} else {
|
|
|
|
latestTag = data["dist-tags"].latest;
|
|
|
|
}
|
|
|
|
|
2024-05-27 21:44:41 +00:00
|
|
|
const oldVersion = rangeToVersion(range);
|
2023-06-16 20:45:23 +00:00
|
|
|
const oldIsPre = isRangePrerelease(range);
|
2019-01-19 19:05:26 +00:00
|
|
|
const newIsPre = isVersionPrerelease(version);
|
2019-11-19 19:52:28 +00:00
|
|
|
const latestIsPre = isVersionPrerelease(latestTag);
|
2023-07-04 20:21:21 +00:00
|
|
|
const isGreater = gt(version, oldVersion);
|
2019-01-19 19:05:26 +00:00
|
|
|
|
|
|
|
// update to new prerelease
|
2023-06-16 20:45:23 +00:00
|
|
|
if (!useRel && usePre || (oldIsPre && newIsPre)) {
|
2019-01-19 19:05:26 +00:00
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
2019-02-25 21:46:39 +00:00
|
|
|
// downgrade from prerelease to release on --release-only
|
2023-06-16 20:45:23 +00:00
|
|
|
if (useRel && !isGreater && oldIsPre && !newIsPre) {
|
2019-02-25 21:46:39 +00:00
|
|
|
return version;
|
|
|
|
}
|
2019-02-25 20:50:12 +00:00
|
|
|
|
2019-01-19 19:05:26 +00:00
|
|
|
// update from prerelease to release
|
|
|
|
if (oldIsPre && !newIsPre && isGreater) {
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// do not downgrade from prerelease to release
|
|
|
|
if (oldIsPre && !newIsPre && !isGreater) {
|
2019-01-20 21:26:30 +00:00
|
|
|
return null;
|
2019-01-19 19:05:26 +00:00
|
|
|
}
|
|
|
|
|
2019-02-25 21:46:39 +00:00
|
|
|
// check if latestTag is allowed by semvers
|
2023-07-04 20:21:21 +00:00
|
|
|
const d = diff(oldVersion, latestTag);
|
|
|
|
if (d && d !== "prerelease" && !semvers.has(d.replace(/^pre/, ""))) {
|
2019-02-25 21:46:39 +00:00
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevent upgrading to prerelease with --release-only
|
2023-06-16 20:45:23 +00:00
|
|
|
if (useRel && isVersionPrerelease(latestTag)) {
|
2019-02-25 21:46:39 +00:00
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
2019-11-19 19:52:51 +00:00
|
|
|
// prevent downgrade to older version except with --allow-downgrade
|
2023-07-04 20:21:21 +00:00
|
|
|
if (lt(latestTag, oldVersion) && !latestIsPre) {
|
2023-08-31 00:14:02 +00:00
|
|
|
if (allowDowngrade === true || matchesAny(data.name, allowDowngrade)) {
|
2019-11-19 19:52:28 +00:00
|
|
|
return latestTag;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 19:05:26 +00:00
|
|
|
// in all other cases, return latest dist-tag
|
2023-06-20 13:42:56 +00:00
|
|
|
return originalLatestTag ?? latestTag;
|
2019-01-19 19:05:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function fetchGitHub(url: string) {
|
|
|
|
const opts: RequestInit = {};
|
2024-03-18 23:38:03 +00:00
|
|
|
const token = env.UPDATES_GITHUB_API_TOKEN || env.GITHUB_API_TOKEN || env.GH_TOKEN || env.HOMEBREW_GITHUB_API_TOKEN;
|
|
|
|
if (token) {
|
|
|
|
opts.headers = {Authorization: `Bearer ${token}`};
|
|
|
|
}
|
2024-03-18 23:59:53 +00:00
|
|
|
return doFetch(url, opts);
|
2024-03-18 23:38:03 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function getLastestCommit(user: string, repo: string): Promise<{hash: string, commit: {[other: string]: any}}> {
|
2024-03-18 23:38:03 +00:00
|
|
|
const url = `${githubApiUrl}/repos/${user}/${repo}/commits`;
|
|
|
|
const res = await fetchGitHub(url);
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!res || !res.ok) return {hash: "", commit: {}};
|
2024-03-18 23:38:03 +00:00
|
|
|
const data = await res.json();
|
|
|
|
const {sha: hash, commit} = data[0];
|
|
|
|
return {hash, commit};
|
|
|
|
}
|
|
|
|
|
2024-03-18 23:41:28 +00:00
|
|
|
// return list of tags sorted old to new
|
|
|
|
// TODO: newDate support, semver matching
|
2024-05-23 21:53:45 +00:00
|
|
|
async function getTags(user: string, repo: string): Promise<string[]> {
|
2024-03-18 23:41:28 +00:00
|
|
|
const res = await fetchGitHub(`${githubApiUrl}/repos/${user}/${repo}/git/refs/tags`);
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!res || !res.ok) return [];
|
2024-03-18 23:41:28 +00:00
|
|
|
const data = await res.json();
|
2024-05-23 21:53:45 +00:00
|
|
|
const tags = data.map((entry: {ref: string}) => entry.ref.replace(/^refs\/tags\//, ""));
|
2024-03-18 23:41:28 +00:00
|
|
|
return tags;
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function selectTag(tags: string[], oldRef: string, useGreatest: boolean) {
|
2024-04-16 22:03:11 +00:00
|
|
|
const oldRefBare = stripV(oldRef);
|
2024-04-10 22:25:39 +00:00
|
|
|
if (!valid(oldRefBare)) return;
|
|
|
|
|
|
|
|
if (!useGreatest) {
|
|
|
|
const lastTag = tags.at(-1);
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!lastTag) return;
|
2024-04-16 22:03:11 +00:00
|
|
|
const lastTagBare = stripV(lastTag);
|
2024-04-10 22:25:39 +00:00
|
|
|
if (!valid(lastTagBare)) return;
|
|
|
|
|
|
|
|
if (neq(oldRefBare, lastTagBare)) {
|
|
|
|
return lastTag;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let greatestTag = oldRef;
|
2024-04-16 22:03:11 +00:00
|
|
|
let greatestTagBare = stripV(oldRef);
|
2024-04-10 22:25:39 +00:00
|
|
|
|
|
|
|
for (const tag of tags) {
|
2024-04-16 22:03:11 +00:00
|
|
|
const tagBare = stripV(tag);
|
2024-04-10 22:25:39 +00:00
|
|
|
if (!valid(tagBare)) continue;
|
|
|
|
if (!greatestTag || gt(tagBare, greatestTagBare)) {
|
|
|
|
greatestTag = tag;
|
|
|
|
greatestTagBare = tagBare;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (neq(oldRefBare, greatestTagBare)) {
|
|
|
|
return greatestTag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function checkUrlDep(key: string, dep: Dep, useGreatest: boolean) {
|
2019-12-04 20:15:47 +00:00
|
|
|
const stripped = dep.old.replace(stripRe, "");
|
2019-12-06 05:37:39 +00:00
|
|
|
const [_, user, repo, oldRef] = partsRe.exec(stripped) || [];
|
2019-12-04 20:15:47 +00:00
|
|
|
if (!user || !repo || !oldRef) return;
|
|
|
|
|
|
|
|
if (hashRe.test(oldRef)) {
|
2024-03-18 23:38:03 +00:00
|
|
|
const {hash, commit} = await getLastestCommit(user, repo);
|
2024-05-23 21:53:45 +00:00
|
|
|
if (!hash) return;
|
2020-03-08 16:06:58 +00:00
|
|
|
|
2022-06-03 21:03:46 +00:00
|
|
|
const newDate = commit?.committer?.date ?? commit?.author?.date;
|
2024-03-18 23:38:03 +00:00
|
|
|
const newRef = hash.substring(0, oldRef.length);
|
2019-12-04 20:15:47 +00:00
|
|
|
if (oldRef !== newRef) {
|
|
|
|
const newRange = dep.old.replace(oldRef, newRef);
|
2020-03-08 16:06:58 +00:00
|
|
|
return {key, newRange, user, repo, oldRef, newRef, newDate};
|
2019-12-04 20:15:47 +00:00
|
|
|
}
|
2024-03-18 23:41:28 +00:00
|
|
|
} else {
|
|
|
|
const tags = await getTags(user, repo);
|
2024-04-10 22:25:39 +00:00
|
|
|
const newTag = selectTag(tags, oldRef, useGreatest);
|
|
|
|
if (newTag) {
|
|
|
|
return {key, newRange: newTag, user, repo, oldRef, newRef: newTag};
|
2019-12-04 20:15:47 +00:00
|
|
|
}
|
2019-12-03 18:19:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function normalizeRange(range: string) {
|
2022-11-10 21:44:24 +00:00
|
|
|
const versionMatches = range.match(versionRe);
|
2022-12-21 10:04:42 +00:00
|
|
|
if (versionMatches?.length !== 1) return range;
|
2024-05-27 21:44:41 +00:00
|
|
|
return range.replace(versionRe, rangeToVersion(versionMatches[0]));
|
2022-11-10 21:44:24 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function parseMixedArg(arg: any) {
|
2020-03-09 20:54:27 +00:00
|
|
|
if (arg === undefined) {
|
|
|
|
return false;
|
|
|
|
} else if (arg === "") {
|
2018-09-02 19:43:16 +00:00
|
|
|
return true;
|
|
|
|
} else if (typeof arg === "string") {
|
2020-03-15 17:26:40 +00:00
|
|
|
return arg.includes(",") ? new Set(arg.split(",")) : new Set([arg]);
|
2018-09-02 19:43:16 +00:00
|
|
|
} else if (Array.isArray(arg)) {
|
2020-03-15 17:26:40 +00:00
|
|
|
return new Set(arg);
|
2018-09-02 19:43:16 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function extractCerts(str: string): string[] {
|
|
|
|
return Array.from(str.matchAll(/(----BEGIN CERT[^]+?IFICATE----)/g), (m: string[]) => m[0]);
|
2023-07-04 20:21:21 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
async function getCerts(extra: string[] = []) {
|
2023-07-19 23:02:46 +00:00
|
|
|
return [...(await import("node:tls")).rootCertificates, ...extra];
|
2023-07-04 20:21:21 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 00:14:02 +00:00
|
|
|
// convert arg from cli or config to regex
|
2024-05-23 21:53:45 +00:00
|
|
|
function argToRegex(arg: string | RegExp, cli: boolean) {
|
|
|
|
if (cli && typeof arg === "string") {
|
2023-08-31 00:14:02 +00:00
|
|
|
return /\/.+\//.test(arg) ? new RegExp(arg.slice(1, -1)) : picomatch.makeRe(arg);
|
|
|
|
} else {
|
|
|
|
return arg instanceof RegExp ? arg : picomatch.makeRe(arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse cli arg into regex set
|
2024-05-23 21:53:45 +00:00
|
|
|
function argSetToRegexes(arg: any) {
|
2023-08-31 00:14:02 +00:00
|
|
|
if (arg instanceof Set) {
|
|
|
|
const ret = new Set();
|
|
|
|
for (const entry of arg) {
|
|
|
|
ret.add(argToRegex(entry, true));
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:17:17 +00:00
|
|
|
// parse include/exclude into a Set of regexes
|
2024-05-23 21:53:45 +00:00
|
|
|
function matchersToRegexSet(cliArgs: string[], configArgs: string[]): Set<RegExp> {
|
2023-08-25 23:17:17 +00:00
|
|
|
const ret = new Set();
|
|
|
|
for (const arg of cliArgs || []) {
|
2023-08-31 00:14:02 +00:00
|
|
|
ret.add(argToRegex(arg, true));
|
2023-08-25 23:17:17 +00:00
|
|
|
}
|
|
|
|
for (const arg of configArgs || []) {
|
2023-08-31 00:14:02 +00:00
|
|
|
ret.add(argToRegex(arg, false));
|
2023-08-25 23:17:17 +00:00
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
return ret as Set<RegExp>;
|
2023-08-25 23:17:17 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function canInclude(name: string, mode: string, include: Set<RegExp>, exclude: Set<RegExp>) {
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "pypi" && name === "python") return false;
|
2023-09-05 22:48:25 +00:00
|
|
|
if (!include.size && !exclude.size) return true;
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const re of exclude) {
|
|
|
|
if (re.test(name)) return false;
|
|
|
|
}
|
|
|
|
for (const re of include) {
|
2023-09-05 22:48:25 +00:00
|
|
|
if (re.test(name)) return true;
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2023-09-05 23:13:02 +00:00
|
|
|
return include.size ? false : true;
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
function resolveFiles(filesArg: Set<string>): Set<string> {
|
2023-09-05 18:16:56 +00:00
|
|
|
const resolvedFiles = new Set();
|
|
|
|
|
|
|
|
if (filesArg) { // check passed files
|
|
|
|
for (const file of filesArg) {
|
|
|
|
let stat;
|
|
|
|
try {
|
|
|
|
stat = lstatSync(file);
|
|
|
|
} catch (err) {
|
2024-05-23 21:53:45 +00:00
|
|
|
throw new Error(`Unable to open ${file}: ${(err as Error).message}`);
|
2023-09-05 18:16:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stat?.isFile()) {
|
|
|
|
resolvedFiles.add(resolve(file));
|
|
|
|
} else if (stat?.isDirectory()) {
|
|
|
|
for (const filename of ["package.json", "pyproject.toml"]) {
|
|
|
|
const f = join(file, filename);
|
|
|
|
let stat;
|
|
|
|
try {
|
|
|
|
stat = lstatSync(f);
|
|
|
|
} catch {}
|
|
|
|
if (stat?.isFile()) {
|
|
|
|
resolvedFiles.add(resolve(f));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error(`${file} is neither a file nor directory`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { // search for files
|
|
|
|
for (const filename of ["package.json", "pyproject.toml"]) {
|
2024-03-19 00:30:46 +00:00
|
|
|
const file = findUpSync(filename, cwd());
|
2023-09-05 18:16:56 +00:00
|
|
|
if (file) resolvedFiles.add(resolve(file));
|
|
|
|
}
|
|
|
|
}
|
2024-05-23 21:53:45 +00:00
|
|
|
return resolvedFiles as Set<string>;
|
2023-09-05 18:16:56 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 00:28:11 +00:00
|
|
|
async function main() {
|
2022-10-26 15:31:23 +00:00
|
|
|
for (const stream of [process.stdout, process.stderr]) {
|
2024-05-23 21:53:45 +00:00
|
|
|
// @ts-ignore
|
2022-10-26 15:31:23 +00:00
|
|
|
stream?._handle?.setBlocking?.(true);
|
|
|
|
}
|
|
|
|
|
2024-03-18 23:55:36 +00:00
|
|
|
const maxSockets = 96;
|
2024-05-23 21:53:45 +00:00
|
|
|
const concurrency = typeof args.sockets === "number" ? args.sockets : maxSockets;
|
2023-09-05 18:16:56 +00:00
|
|
|
const {help, version, file: filesArg, types, update} = args;
|
2023-06-16 20:45:23 +00:00
|
|
|
|
|
|
|
if (help) {
|
2022-10-26 15:31:23 +00:00
|
|
|
stdout.write(`usage: updates [options]
|
|
|
|
|
|
|
|
Options:
|
2023-06-16 20:45:23 +00:00
|
|
|
-u, --update Update versions and write package file
|
2023-09-04 23:16:47 +00:00
|
|
|
-f, --file <path,...> File or directory to use, defaults to current directory
|
2023-08-31 00:14:02 +00:00
|
|
|
-i, --include <pkg,...> Include only given packages
|
|
|
|
-e, --exclude <pkg,...> Exclude given packages
|
2022-10-26 15:31:23 +00:00
|
|
|
-p, --prerelease [<pkg,...>] Consider prerelease versions
|
|
|
|
-R, --release [<pkg,...>] Only use release versions, may downgrade
|
|
|
|
-g, --greatest [<pkg,...>] Prefer greatest over latest version
|
|
|
|
-t, --types <type,...> Check only given dependency types
|
|
|
|
-P, --patch [<pkg,...>] Consider only up to semver-patch
|
|
|
|
-m, --minor [<pkg,...>] Consider only up to semver-minor
|
|
|
|
-d, --allow-downgrade [<pkg,...>] Allow version downgrades when using latest version
|
|
|
|
-E, --error-on-outdated Exit with code 2 when updates are available and 0 when not
|
|
|
|
-U, --error-on-unchanged Exit with code 0 when updates are available and 2 when not
|
|
|
|
-r, --registry <url> Override npm registry URL
|
2024-03-18 23:55:36 +00:00
|
|
|
-S, --sockets <num> Maximum number of parallel HTTP sockets opened. Default: ${maxSockets}
|
2022-10-26 15:31:23 +00:00
|
|
|
-j, --json Output a JSON object
|
|
|
|
-n, --no-color Disable color output
|
|
|
|
-v, --version Print the version
|
|
|
|
-V, --verbose Print verbose output to stderr
|
|
|
|
-h, --help Print this help
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
$ updates
|
2023-08-25 23:17:17 +00:00
|
|
|
$ updates -u
|
2023-09-05 18:13:05 +00:00
|
|
|
$ updates -e '@vitejs/*'
|
2023-08-25 23:17:17 +00:00
|
|
|
$ updates -e '/^react-(dom)?/'
|
|
|
|
$ updates -f package.json
|
|
|
|
$ updates -f pyproject.toml
|
2022-10-26 15:31:23 +00:00
|
|
|
`);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2023-06-16 20:45:23 +00:00
|
|
|
if (version) {
|
2023-06-18 00:02:32 +00:00
|
|
|
console.info(packageVersion);
|
2022-10-26 15:31:23 +00:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
// output vars
|
2024-05-23 21:53:45 +00:00
|
|
|
const deps: DepsByMode = {};
|
|
|
|
const maybeUrlDeps: DepsByMode = {};
|
|
|
|
const pkgStrs: {[other: string]: string} = {};
|
|
|
|
const filePerMode: {[other: string]: string} = {};
|
2023-09-04 23:00:06 +00:00
|
|
|
let numDependencies = 0;
|
2023-06-16 20:45:23 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
for (const file of resolveFiles(parseMixedArg(filesArg) as Set<string>)) {
|
2023-09-04 23:00:06 +00:00
|
|
|
const projectDir = dirname(resolve(file));
|
|
|
|
const filename = basename(file);
|
2022-10-26 13:03:43 +00:00
|
|
|
|
2024-03-19 00:30:46 +00:00
|
|
|
const mode = filename === "pyproject.toml" ? "pypi" : "npm";
|
2023-09-04 23:00:06 +00:00
|
|
|
filePerMode[mode] = file;
|
|
|
|
if (!deps[mode]) deps[mode] = {};
|
2023-06-16 20:45:23 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
let config: {[other: string]: any} = {};
|
2023-04-23 21:29:15 +00:00
|
|
|
try {
|
2024-03-09 01:52:24 +00:00
|
|
|
({default: config} = await Promise.any([
|
|
|
|
"updates.config.js",
|
2024-05-17 22:42:47 +00:00
|
|
|
"updates.config.ts",
|
2024-03-09 01:52:24 +00:00
|
|
|
"updates.config.mjs",
|
2024-05-17 22:42:47 +00:00
|
|
|
"updates.config.mts",
|
2024-03-09 01:52:24 +00:00
|
|
|
".config/updates.js",
|
2024-05-17 22:42:47 +00:00
|
|
|
".config/updates.ts",
|
2024-03-09 01:52:24 +00:00
|
|
|
".config/updates.mjs",
|
2024-05-17 22:42:47 +00:00
|
|
|
".config/updates.mts",
|
2024-03-09 01:52:24 +00:00
|
|
|
].map(str => import(join(projectDir, ...str.split("/"))))));
|
|
|
|
} catch {}
|
2023-04-24 16:29:25 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
let includeCli: string[] = [];
|
|
|
|
let excludeCli: string[] = [];
|
2023-09-04 23:14:35 +00:00
|
|
|
if (args.include && args.include !== true) { // cli
|
|
|
|
includeCli = (Array.isArray(args.include) ? args.include : [args.include]).flatMap(item => item.split(","));
|
|
|
|
}
|
|
|
|
if (args.exclude && args.exclude !== true) {
|
|
|
|
excludeCli = (Array.isArray(args.exclude) ? args.exclude : [args.exclude]).flatMap(item => item.split(","));
|
|
|
|
}
|
|
|
|
const include = matchersToRegexSet(includeCli, config?.include);
|
|
|
|
const exclude = matchersToRegexSet(excludeCli, config?.exclude);
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
const agentOpts: AgentOptions = {};
|
|
|
|
const npmrc: Npmrc = rc("npm", {registry: "https://registry.npmjs.org"});
|
2024-03-19 00:30:46 +00:00
|
|
|
const authTokenOpts = {npmrc, recursive: true};
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "npm") {
|
|
|
|
if (npmrc["strict-ssl"] === false) {
|
|
|
|
agentOpts.rejectUnauthorized = false;
|
|
|
|
}
|
2024-03-19 00:30:46 +00:00
|
|
|
if (npmrc?.cafile) {
|
2024-05-23 21:53:45 +00:00
|
|
|
agentOpts.ca = await getCerts(extractCerts(readFileSync(npmrc.cafile, "utf8")));
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2024-03-19 00:30:46 +00:00
|
|
|
if (npmrc?.ca) {
|
2024-05-23 21:53:45 +00:00
|
|
|
const cas = Array.isArray(npmrc.ca) ? npmrc.ca : [npmrc.ca];
|
|
|
|
agentOpts.ca = await getCerts(cas.flatMap(ca => extractCerts(ca)));
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2023-06-16 20:45:23 +00:00
|
|
|
}
|
2022-10-26 13:03:43 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
let dependencyTypes;
|
|
|
|
if (types) {
|
|
|
|
dependencyTypes = Array.isArray(types) ? types : types.split(",");
|
|
|
|
} else if ("types" in config && Array.isArray(config.types)) {
|
|
|
|
dependencyTypes = config.types;
|
2023-06-16 20:45:23 +00:00
|
|
|
} else {
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "npm") {
|
|
|
|
dependencyTypes = [
|
|
|
|
"dependencies",
|
|
|
|
"devDependencies",
|
|
|
|
"optionalDependencies",
|
|
|
|
"peerDependencies",
|
|
|
|
"resolutions",
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
dependencyTypes = [
|
|
|
|
"tool.poetry.dependencies",
|
|
|
|
"tool.poetry.dev-dependencies",
|
|
|
|
"tool.poetry.test-dependencies",
|
|
|
|
"tool.poetry.group.dev.dependencies",
|
|
|
|
"tool.poetry.group.test.dependencies",
|
|
|
|
];
|
|
|
|
}
|
2023-06-16 20:45:23 +00:00
|
|
|
}
|
2022-10-26 13:03:43 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
let pkg: {[other: string]: any};
|
2023-09-04 23:00:06 +00:00
|
|
|
try {
|
|
|
|
pkgStrs[mode] = readFileSync(file, "utf8");
|
|
|
|
} catch (err) {
|
2024-05-23 21:53:45 +00:00
|
|
|
throw new Error(`Unable to open ${file}: ${(err as Error).message}`);
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2022-10-26 13:03:43 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
try {
|
|
|
|
if (mode === "npm") {
|
|
|
|
pkg = JSON.parse(pkgStrs[mode]);
|
|
|
|
} else {
|
|
|
|
pkg = (await import("@iarna/toml/parse-string.js")).default(pkgStrs[mode]);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2024-05-23 21:53:45 +00:00
|
|
|
throw new Error(`Error parsing ${file}: ${(err as Error).message}`);
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2023-08-25 23:17:17 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const depType of dependencyTypes) {
|
2024-05-23 21:53:45 +00:00
|
|
|
let obj: {[other: string]: string};
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "npm") {
|
|
|
|
obj = pkg[depType] || {};
|
|
|
|
} else {
|
|
|
|
obj = getProperty(pkg, depType) || {};
|
2022-10-26 13:03:43 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const [name, value] of Object.entries(obj)) {
|
2024-05-23 21:53:45 +00:00
|
|
|
if (validRange(value) && canInclude(name, mode, include, exclude)) {
|
|
|
|
// @ts-ignore
|
2023-09-04 23:00:06 +00:00
|
|
|
deps[mode][`${depType}${sep}${name}`] = {
|
|
|
|
old: normalizeRange(value),
|
|
|
|
oldOriginal: value,
|
2024-05-23 21:53:45 +00:00
|
|
|
} as Partial<Dep>;
|
|
|
|
} else if (mode === "npm" && canInclude(name, mode, include, exclude)) {
|
|
|
|
// @ts-ignore
|
2023-09-04 23:00:06 +00:00
|
|
|
maybeUrlDeps[`${depType}${sep}${name}`] = {
|
|
|
|
old: value,
|
2024-05-23 21:53:45 +00:00
|
|
|
} as Partial<Dep>;
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-26 16:22:10 +00:00
|
|
|
}
|
2022-10-26 13:03:43 +00:00
|
|
|
|
2023-09-05 18:18:58 +00:00
|
|
|
numDependencies += Object.keys(deps[mode]).length + Object.keys(maybeUrlDeps).length;
|
|
|
|
if (!numDependencies) continue;
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
let registry: string;
|
2023-09-04 23:00:06 +00:00
|
|
|
if (mode === "npm") {
|
|
|
|
registry = normalizeUrl(args.registry || config.registry || npmrc.registry);
|
|
|
|
}
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
const entries = await pAll(Object.keys(deps[mode]).map(key => () => {
|
|
|
|
const [type, name] = key.split(sep);
|
|
|
|
if (mode === "npm") {
|
2024-03-19 00:30:46 +00:00
|
|
|
return fetchNpmInfo(name, type, registry, agentOpts, authTokenOpts, npmrc);
|
2023-09-04 23:00:06 +00:00
|
|
|
} else {
|
|
|
|
return fetchPypiInfo(name, type, agentOpts);
|
|
|
|
}
|
2024-03-18 23:55:36 +00:00
|
|
|
}), {concurrency});
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
for (const [data, type, registry, name] of entries) {
|
|
|
|
if (data?.error) throw new Error(data.error);
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
const useGreatest = typeof greatest === "boolean" ? greatest : matchesAny(data.name, greatest);
|
|
|
|
const usePre = typeof prerelease === "boolean" ? prerelease : matchesAny(data.name, prerelease);
|
|
|
|
const useRel = typeof release === "boolean" ? release : matchesAny(data.name, release);
|
2019-10-18 00:28:11 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
let semvers;
|
|
|
|
if (patch === true || matchesAny(data.name, patch)) {
|
2024-03-18 23:55:36 +00:00
|
|
|
semvers = new Set(["patch"]);
|
2023-09-04 23:00:06 +00:00
|
|
|
} else if (minor === true || matchesAny(data.name, minor)) {
|
2024-03-18 23:55:36 +00:00
|
|
|
semvers = new Set(["patch", "minor"]);
|
2023-09-04 23:00:06 +00:00
|
|
|
} else {
|
2024-03-18 23:55:36 +00:00
|
|
|
semvers = new Set(["patch", "minor", "major"]);
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2023-06-20 13:42:56 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
const key = `${type}${sep}${name}`;
|
|
|
|
const oldRange = deps[mode][key].old;
|
|
|
|
const newVersion = findNewVersion(data, {
|
|
|
|
usePre, useRel, useGreatest, semvers, range: oldRange, mode,
|
|
|
|
});
|
|
|
|
const newRange = updateRange(oldRange, newVersion);
|
|
|
|
|
|
|
|
if (!newVersion || oldRange === newRange) {
|
|
|
|
delete deps[mode][key];
|
2023-06-20 13:42:56 +00:00
|
|
|
} else {
|
2023-09-04 23:00:06 +00:00
|
|
|
deps[mode][key].new = newRange;
|
|
|
|
|
|
|
|
if (mode === "npm") {
|
|
|
|
deps[mode][key].info = getInfoUrl(data?.versions?.[newVersion], registry, data.name);
|
|
|
|
} else {
|
|
|
|
deps[mode][key].info = getInfoUrl(data, registry, data.info.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.time?.[newVersion]) {
|
|
|
|
deps[mode][key].age = timerel(data.time[newVersion], {noAffix: true});
|
|
|
|
} else if (data.releases?.[newVersion]?.[0]?.upload_time_iso_8601) {
|
|
|
|
deps[mode][key].age = timerel(data.releases[newVersion][0].upload_time_iso_8601, {noAffix: true});
|
|
|
|
}
|
2023-06-20 13:42:56 +00:00
|
|
|
}
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
2023-06-20 13:42:56 +00:00
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
if (Object.keys(maybeUrlDeps).length) {
|
2024-03-18 23:55:36 +00:00
|
|
|
const results = await pAll(Object.entries(maybeUrlDeps).map(([key, dep]) => () => {
|
2023-09-04 23:00:06 +00:00
|
|
|
const name = key.split(sep)[1];
|
|
|
|
const useGreatest = typeof greatest === "boolean" ? greatest : matchesAny(name, greatest);
|
2024-05-23 21:53:45 +00:00
|
|
|
// @ts-ignore
|
|
|
|
return checkUrlDep(key, dep, useGreatest);
|
2024-03-18 23:55:36 +00:00
|
|
|
}), {concurrency});
|
2023-09-04 23:00:06 +00:00
|
|
|
|
|
|
|
for (const res of (results || []).filter(Boolean)) {
|
2024-05-23 21:53:45 +00:00
|
|
|
// @ts-ignore
|
2023-09-04 23:00:06 +00:00
|
|
|
const {key, newRange, user, repo, oldRef, newRef, newDate} = res;
|
|
|
|
deps[mode][key] = {
|
2024-05-23 21:53:45 +00:00
|
|
|
// @ts-ignore
|
2023-09-04 23:00:06 +00:00
|
|
|
old: maybeUrlDeps[key].old,
|
|
|
|
new: newRange,
|
|
|
|
oldPrint: hashRe.test(oldRef) ? oldRef.substring(0, 7) : oldRef,
|
|
|
|
newPrint: hashRe.test(newRef) ? newRef.substring(0, 7) : newRef,
|
|
|
|
info: `https://github.com/${user}/${repo}`,
|
|
|
|
...(newDate ? {age: timerel(newDate, {noAffix: true})} : {}),
|
|
|
|
};
|
2023-06-16 20:45:23 +00:00
|
|
|
}
|
2019-10-18 00:28:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
if (numDependencies === 0) {
|
2023-09-19 16:08:24 +00:00
|
|
|
finishWithMessage("No dependencies found, nothing to do.");
|
2023-09-04 23:00:06 +00:00
|
|
|
doExit();
|
2019-12-03 18:19:30 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
let numEntries = 0;
|
|
|
|
for (const mode of Object.keys(deps)) {
|
|
|
|
numEntries += Object.keys(deps[mode]).length;
|
2019-10-18 00:28:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
if (!numEntries) {
|
2023-09-05 22:48:25 +00:00
|
|
|
finishWithMessage("All dependencies are up to date.");
|
2023-09-04 23:00:06 +00:00
|
|
|
doExit();
|
2019-10-18 00:28:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
const exitCode = outputDeps(deps);
|
|
|
|
|
|
|
|
if (update) {
|
|
|
|
for (const mode of Object.keys(deps)) {
|
2023-09-05 18:25:46 +00:00
|
|
|
if (!Object.keys(deps[mode]).length) continue;
|
2023-09-04 23:00:06 +00:00
|
|
|
try {
|
2024-02-09 10:54:22 +00:00
|
|
|
const fn = (mode === "npm") ? updatePackageJson : updateProjectToml;
|
2023-09-04 23:00:06 +00:00
|
|
|
await write(filePerMode[mode], fn(pkgStrs[mode], deps[mode]));
|
|
|
|
} catch (err) {
|
2024-05-23 21:53:45 +00:00
|
|
|
throw new Error(`Error writing ${basename(filePerMode[mode])}: ${(err as Error).message}`);
|
2023-09-04 23:00:06 +00:00
|
|
|
}
|
|
|
|
|
2023-09-05 22:48:25 +00:00
|
|
|
// TODO: json
|
2023-09-04 23:00:06 +00:00
|
|
|
console.info(green(`✨ ${basename(filePerMode[mode])} updated`));
|
2023-06-19 23:09:54 +00:00
|
|
|
}
|
2019-10-18 00:28:11 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 21:53:45 +00:00
|
|
|
process.exit(exitCode);
|
2019-10-18 00:28:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:00:06 +00:00
|
|
|
main().catch(doExit).then(doExit);
|