diff --git a/README.md b/README.md index e0ec416..6d8849e 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,15 @@ Put a `updates.config.js` or `updates.config.mjs` in the root of your project, u export default { exclude: [ "semver", + /^react/, ], }; ``` ### Config File Options -- `include` *Array[String]*: Array of dependencies to include -- `exclude` *Array[String]*: Array of dependencies to exclude +- `include` *Array[String|Regexp]*: Array of dependencies to include +- `exclude` *Array[String|Regexp]*: Array of dependencies to exclude - `types` *Array[String]*: Array of dependency types - `registry` *String*: URL to npm registry diff --git a/package-lock.json b/package-lock.json index d32f28d..05c9fa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1477,18 +1477,6 @@ "@esbuild/win32-x64": "0.18.19" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint": { "version": "8.46.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", @@ -1814,6 +1802,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", diff --git a/snapshots/updates.test.js.snap b/snapshots/updates.test.js.snap index a240287..9f7e9e0 100644 --- a/snapshots/updates.test.js.snap +++ b/snapshots/updates.test.js.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`exclude version deps 1`] = ` +exports[`exclude 1`] = ` { "dependencies": { "updates": { @@ -12,6 +12,18 @@ exports[`exclude version deps 1`] = ` } `; +exports[`exclude 2`] = ` +{ + "dependencies": { + "react": { + "info": "https://github.com/facebook/react/tree/HEAD/packages/react", + "new": "18.2.0", + "old": "18.0", + }, + }, +} +`; + exports[`greatest 1`] = ` { "dependencies": { @@ -78,7 +90,7 @@ exports[`greatest 1`] = ` } `; -exports[`include version deps #2 1`] = ` +exports[`include 1`] = ` { "dependencies": { "noty": { @@ -90,7 +102,19 @@ exports[`include version deps #2 1`] = ` } `; -exports[`include version deps 1`] = ` +exports[`include 2 1`] = ` +{ + "dependencies": { + "noty": { + "info": "https://github.com/needim/noty", + "new": "3.2.0-beta", + "old": "3.1.0", + }, + }, +} +`; + +exports[`include 3 1`] = ` { "dependencies": { "noty": { diff --git a/updates.js b/updates.js index 18f6ab3..4e6fc7d 100755 --- a/updates.js +++ b/updates.js @@ -611,6 +611,18 @@ async function getCerts(extra = []) { return [...(await import("node:tls")).rootCertificates, ...extra]; } +// parse include/exclude into a Set of regexes +function matchersToRegexSet(cliArgs, configArgs) { + const ret = new Set(); + for (const arg of cliArgs || []) { + ret.add(new RegExp(/\/.+\//.test(arg) ? arg.slice(1, -1) : `^${esc(arg)}$`)); + } + for (const arg of configArgs || []) { + ret.add(arg instanceof RegExp ? arg : new RegExp(`^${esc(arg)}$`)); + } + return ret; +} + async function main() { for (const stream of [process.stdout, process.stderr]) { stream?._handle?.setBlocking?.(true); @@ -622,13 +634,13 @@ async function main() { stdout.write(`usage: updates [options] Options: - -l, --language Language to check, either 'js' or 'py' -u, --update Update versions and write package file + -f, --file Use given package file or module directory + -i, --include Include only given packages, supports '/regex/' syntax + -e, --exclude Exclude given packages, supports '/regex/' syntax -p, --prerelease [] Consider prerelease versions -R, --release [] Only use release versions, may downgrade -g, --greatest [] Prefer greatest over latest version - -i, --include Include only given packages - -e, --exclude Exclude given packages -t, --types Check only given dependency types -P, --patch [] Consider only up to semver-patch -m, --minor [] Consider only up to semver-minor @@ -636,7 +648,7 @@ async function main() { -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 Override npm registry URL - -f, --file Use given package file or module directory + -l, --language Language to check, either 'js' or 'py' -S, --sockets Maximum number of parallel HTTP sockets opened. Default: ${MAX_SOCKETS} -j, --json Output a JSON object -n, --no-color Disable color output @@ -646,7 +658,10 @@ async function main() { Examples: $ updates - $ updates -u && npm i + $ updates -u + $ updates -e '/^react-(dom)?/' + $ updates -f package.json + $ updates -f pyproject.toml `); exit(0); } @@ -763,22 +778,25 @@ async function main() { finish(new Error(`Error parsing ${packageFile}: ${err.message}`)); } - let include, exclude; - if (args.include && args.include !== true) { - include = new Set(((Array.isArray(args.include) ? args.include : [args.include]).flatMap(item => item.split(",")))); - } else if ("include" in config && Array.isArray(config.include)) { - include = new Set(config.include); + let includeCli, excludeCli; + 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) { - exclude = new Set(((Array.isArray(args.exclude) ? args.exclude : [args.exclude]).flatMap(item => item.split(",")))); - } else if ("exclude" in config && Array.isArray(config.exclude)) { - exclude = new Set(config.exclude); + 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); + function canInclude(name, language) { if (language === "py" && name === "python") return false; - if (exclude?.has?.(name) === true) return false; - if (include?.has?.(name) === false) return false; + for (const re of exclude) { + if (re.test(name)) return false; + } + for (const re of include) { + if (!re.test(name)) return false; + } return true; } @@ -806,7 +824,7 @@ async function main() { } if (!Object.keys(deps).length && !Object.keys(maybeUrlDeps).length) { - if (include || exclude) { + if (include.size || exclude.size) { finish(new Error(`No dependencies match the given include/exclude filters`)); } else { finish("No dependencies present, nothing to do"); diff --git a/updates.test.js b/updates.test.js index 8e0e4d1..04a06f4 100644 --- a/updates.test.js +++ b/updates.test.js @@ -167,9 +167,11 @@ test("greatest", makeTest("-j -g")); test("prerelease", makeTest("-j -g -p")); test("release", makeTest("-j -R")); test("patch", makeTest("-j -P")); -test("include version deps", makeTest("-j -i noty")); -test("include version deps #2", makeTest("-j -i noty -i noty,noty")); -test("exclude version deps", makeTest("-j -e gulp-sourcemaps,prismjs,svgstore,html-webpack-plugin,noty,jpeg-buffer-orientation,styled-components,@babel/preset-env,versions/updates,react")); +test("include", makeTest("-j -i noty")); +test("include 2", makeTest("-j -i noty -i noty,noty")); +test("include 3", makeTest("-j -i /^noty/")); +test("exclude", makeTest("-j -e gulp-sourcemaps,prismjs,svgstore,html-webpack-plugin,noty,jpeg-buffer-orientation,styled-components,@babel/preset-env,versions/updates,react")); +test("exclude", makeTest("-j -e gulp-sourcemaps -i /react/")); test("pypi", makeTest( `-j -f ${fileURLToPath(new URL("fixtures/pyproject.toml", import.meta.url))}`,