From 2196dee2b5cf74eff6ec6afbe3a189bce9230595 Mon Sep 17 00:00:00 2001 From: Jakub Drozdek <30927218+jakubdrozdek@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:59:43 +0200 Subject: [PATCH] Add exactCurrentVersion format option --- README.md | 3 ++- src/cli-options.ts | 5 +++-- src/lib/getPackageJson.ts | 34 ++++++++++++++++++++++++++++++++++ src/lib/getPackageVersion.ts | 27 +++++++++++++++++++++++++++ src/lib/getRepoUrl.ts | 24 ++++-------------------- src/lib/logging.ts | 6 +++++- src/types/PackageFile.ts | 1 + src/types/RunOptions.json | 5 ++++- src/types/RunOptions.ts | 2 +- 9 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 src/lib/getPackageJson.ts create mode 100644 src/lib/getPackageVersion.ts diff --git a/README.md b/README.md index c6c7eb69..7110a7d5 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ Options that take no arguments can be negated by prefixing them with `--no-`, e. --format <value> - Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines. (default: []) + Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines, exactCurrentVersion. (default: []) -g, --global @@ -544,6 +544,7 @@ Modify the output formatting or show additional information. Specify one or more repoInfers and displays links to the package's source code repository. Requires packages to be installed. timeShows the publish time of each upgrade. linesPrints name@version on separate lines. Useful for piping to npm install. + exactCurrentVersionPrints the exact current version number instead of a range. ## groupFunction diff --git a/src/cli-options.ts b/src/cli-options.ts index d96f4eac..b7023bee 100755 --- a/src/cli-options.ts +++ b/src/cli-options.ts @@ -176,6 +176,7 @@ const extendedHelpFormat: ExtendedHelp = ({ markdown }) => { ['repo', `Infers and displays links to the package's source code repository. Requires packages to be installed.`], ['time', 'Shows the publish time of each upgrade.'], ['lines', 'Prints name@version on separate lines. Useful for piping to npm install.'], + ['exactCurrentVersion', 'Prints the exact current version number instead of a range.'], ], }) @@ -664,11 +665,11 @@ const cliOptions: CLIOption[] = [ long: 'format', arg: 'value', description: - 'Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines.', + 'Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines, exactCurrentVersion.', parse: value => (typeof value === 'string' ? value.split(',') : value), default: [], type: 'string[]', - choices: ['group', 'ownerChanged', 'repo', 'time', 'lines'], + choices: ['group', 'ownerChanged', 'repo', 'time', 'lines', 'exactCurrentVersion'], help: extendedHelpFormat, }, { diff --git a/src/lib/getPackageJson.ts b/src/lib/getPackageJson.ts new file mode 100644 index 00000000..1c86c76e --- /dev/null +++ b/src/lib/getPackageJson.ts @@ -0,0 +1,34 @@ +import fs from 'fs/promises' +import path from 'path' +import { PackageFile } from '../types/PackageFile' +import exists from './exists' + +/** Gets the package.json contents of an installed package. */ +async function getPackageJson( + packageName: string, + { + pkgFile, + }: { + /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */ + pkgFile?: string + } = {}, +): Promise { + const requirePaths = require.resolve.paths(packageName) || [] + const pkgFileNodeModules = pkgFile ? [path.join(path.dirname(pkgFile), 'node_modules')] : [] + const localNodeModules = [path.join(process.cwd(), 'node_modules')] + const nodeModulePaths = [...pkgFileNodeModules, ...localNodeModules, ...requirePaths] + + for (const basePath of nodeModulePaths) { + const packageJsonPath = path.join(basePath, packageName, 'package.json') + if (await exists(packageJsonPath)) { + try { + const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')) + return packageJson + } catch (e) {} + } + } + + return null +} + +export default getPackageJson diff --git a/src/lib/getPackageVersion.ts b/src/lib/getPackageVersion.ts new file mode 100644 index 00000000..89f7ed93 --- /dev/null +++ b/src/lib/getPackageVersion.ts @@ -0,0 +1,27 @@ +import { PackageFile } from '../types/PackageFile' +import getPackageJson from './getPackageJson' + +/** + * @param packageName A package name as listed in package.json's dependencies list + * @param packageJson Optional param to specify a object representation of a package.json file instead of loading from node_modules + * @returns The package version or null if a version could not be determined + */ +async function getPackageVersion( + packageName: string, + packageJson?: PackageFile, + { + pkgFile, + }: { + /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */ + pkgFile?: string + } = {}, +) { + if (packageJson) { + return packageJson.version + } + + const loadedPackageJson = await getPackageJson(packageName, { pkgFile }) + return loadedPackageJson?.version ?? null +} + +export default getPackageVersion diff --git a/src/lib/getRepoUrl.ts b/src/lib/getRepoUrl.ts index f6851985..49e42cd0 100644 --- a/src/lib/getRepoUrl.ts +++ b/src/lib/getRepoUrl.ts @@ -1,10 +1,8 @@ -import fs from 'fs/promises' import hostedGitInfo from 'hosted-git-info' -import path from 'path' import { URL } from 'url' import { PackageFile } from '../types/PackageFile' import { PackageFileRepository } from '../types/PackageFileRepository' -import exists from './exists' +import getPackageJson from './getPackageJson' /** Gets the repo url of an installed package. */ async function getPackageRepo( @@ -15,23 +13,9 @@ async function getPackageRepo( /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */ pkgFile?: string } = {}, -): Promise { - const requirePaths = require.resolve.paths(packageName) || [] - const pkgFileNodeModules = pkgFile ? [path.join(path.dirname(pkgFile), 'node_modules')] : [] - const localNodeModules = [path.join(process.cwd(), 'node_modules')] - const nodeModulePaths = [...pkgFileNodeModules, ...localNodeModules, ...requirePaths] - - for (const basePath of nodeModulePaths) { - const packageJsonPath = path.join(basePath, packageName, 'package.json') - if (await exists(packageJsonPath)) { - try { - const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')) - return packageJson.repository - } catch (e) {} - } - } - - return null +): Promise { + const packageJson = await getPackageJson(packageName, { pkgFile }) + return packageJson?.repository ?? null } /** diff --git a/src/lib/logging.ts b/src/lib/logging.ts index 091c3427..fdc69588 100755 --- a/src/lib/logging.ts +++ b/src/lib/logging.ts @@ -10,6 +10,7 @@ import { VersionResult } from '../types/VersionResult' import { VersionSpec } from '../types/VersionSpec' import chalk from './chalk' import filterObject from './filterObject' +import getPackageVersion from './getPackageVersion' import getRepoUrl from './getRepoUrl' import { colorizeDiff, @@ -168,7 +169,10 @@ export async function toDependencyTable({ Object.keys(toDeps) .sort() .map(async dep => { - const from = fromDeps[dep] || '' + const from = + (format?.includes('exactCurrentVersion') + ? await getPackageVersion(dep, undefined, { pkgFile }) + : fromDeps[dep]) || '' const toRaw = toDeps[dep] || '' const to = getVersion(toRaw) const ownerChanged = ownersChangedDeps diff --git a/src/types/PackageFile.ts b/src/types/PackageFile.ts index 99223c1e..e7ad7d87 100644 --- a/src/types/PackageFile.ts +++ b/src/types/PackageFile.ts @@ -22,4 +22,5 @@ export interface PackageFile { repository?: string | PackageFileRepository scripts?: Index workspaces?: string[] | { packages: string[] } + version?: string } diff --git a/src/types/RunOptions.json b/src/types/RunOptions.json index 900237ba..4f716ad8 100644 --- a/src/types/RunOptions.json +++ b/src/types/RunOptions.json @@ -68,6 +68,9 @@ "$ref": "#/definitions/Index", "description": "A very generic object." }, + "version": { + "type": "string" + }, "workspaces": { "anyOf": [ { @@ -284,7 +287,7 @@ "description": "Filter on package version using comma-or-space-delimited list, /regex/, or predicate function. Run \"ncu --help --filterVersion\" for details." }, "format": { - "description": "Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines. Run \"ncu --help --format\" for details.", + "description": "Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines, exactCurrentVersion. Run \"ncu --help --format\" for details.", "items": { "type": "string" }, diff --git a/src/types/RunOptions.ts b/src/types/RunOptions.ts index e4565233..f45d6b38 100644 --- a/src/types/RunOptions.ts +++ b/src/types/RunOptions.ts @@ -85,7 +85,7 @@ export interface RunOptions { /** Filter on package version using comma-or-space-delimited list, /regex/, or predicate function. Run "ncu --help --filterVersion" for details. */ filterVersion?: string | RegExp | (string | RegExp)[] | FilterFunction - /** Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines. Run "ncu --help --format" for details. */ + /** Modify the output formatting or show additional information. Specify one or more comma-delimited values: group, ownerChanged, repo, time, lines, exactCurrentVersion. Run "ncu --help --format" for details. */ format?: string[] /** Check global packages instead of in the current project. */