diff --git a/.changeset/bright-jeans-confess.md b/.changeset/bright-jeans-confess.md new file mode 100644 index 0000000000..742783ecec --- /dev/null +++ b/.changeset/bright-jeans-confess.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-outdated": minor +"pnpm": minor +--- + +`pnpm outdated` command supports a `--sort-by=name` option for sorting outdated dependencies by package name [#8523](https://github.com/pnpm/pnpm/pull/8523). diff --git a/reviewing/plugin-commands-outdated/src/outdated.ts b/reviewing/plugin-commands-outdated/src/outdated.ts index 6454508523..f4fe3f1baf 100644 --- a/reviewing/plugin-commands-outdated/src/outdated.ts +++ b/reviewing/plugin-commands-outdated/src/outdated.ts @@ -23,6 +23,7 @@ import renderHelp from 'render-help' import stripAnsi from 'strip-ansi' import { DEFAULT_COMPARATORS, + NAME_COMPARATOR, type OutdatedWithVersionDiff, } from './utils' import { outdatedRecursive } from './recursive' @@ -40,6 +41,7 @@ export function rcOptionsTypes (): Record { ], allTypes), compatible: Boolean, format: ['table', 'list', 'json'], + 'sort-by': 'name', } } @@ -105,6 +107,10 @@ For options that may be used with `-r`, see "pnpm help recursive"', description: 'Don\'t check "optionalDependencies"', name: '--no-optional', }, + { + description: 'Specify the sorting method. Currently only `name` is supported.', + name: '--sort-by', + }, OPTIONS.globalDir, ...UNIVERSAL_OPTIONS, ], @@ -125,6 +131,7 @@ export type OutdatedCommandOptions = { long?: boolean recursive?: boolean format?: 'table' | 'list' | 'json' + sortBy?: 'name' } & Pick columnFns.map((fn) => fn(outdatedPkg))), ] let detailsColumnMaxWidth = 40 @@ -265,9 +272,9 @@ function renderOutdatedTable (outdatedPackages: readonly OutdatedPackage[], opts }) } -function renderOutdatedList (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean }): string { +function renderOutdatedList (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean, sortBy?: 'name' }): string { if (outdatedPackages.length === 0) return '' - return sortOutdatedPackages(outdatedPackages) + return sortOutdatedPackages(outdatedPackages, { sortBy: opts.sortBy }) .map((outdatedPkg) => { let info = `${chalk.bold(renderPackageName(outdatedPkg))} ${renderCurrent(outdatedPkg)} ${chalk.grey('=>')} ${renderLatest(outdatedPkg)}` @@ -294,8 +301,8 @@ export interface OutdatedPackageJSONOutput { latestManifest?: PackageManifest } -function renderOutdatedJSON (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean }): string { - const outdatedPackagesJSON: Record = sortOutdatedPackages(outdatedPackages) +function renderOutdatedJSON (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean, sortBy?: 'name' }): string { + const outdatedPackagesJSON: Record = sortOutdatedPackages(outdatedPackages, { sortBy: opts.sortBy }) .reduce((acc, outdatedPkg) => { acc[outdatedPkg.packageName] = { current: outdatedPkg.current, @@ -312,9 +319,11 @@ function renderOutdatedJSON (outdatedPackages: readonly OutdatedPackage[], opts: return JSON.stringify(outdatedPackagesJSON, null, 2) } -function sortOutdatedPackages (outdatedPackages: readonly OutdatedPackage[]) { +function sortOutdatedPackages (outdatedPackages: readonly OutdatedPackage[], opts?: { sortBy?: 'name' }) { + const sortBy = opts?.sortBy + const comparators = (sortBy === 'name') ? [NAME_COMPARATOR] : DEFAULT_COMPARATORS return sortWith( - DEFAULT_COMPARATORS, + comparators, outdatedPackages.map(toOutdatedWithVersionDiff) ) } diff --git a/reviewing/plugin-commands-outdated/src/utils.ts b/reviewing/plugin-commands-outdated/src/utils.ts index 2425752417..722fdfd3c8 100644 --- a/reviewing/plugin-commands-outdated/src/utils.ts +++ b/reviewing/plugin-commands-outdated/src/utils.ts @@ -8,12 +8,13 @@ export interface OutdatedWithVersionDiff extends OutdatedPackage { export type Comparator = (o1: OutdatedWithVersionDiff, o2: OutdatedWithVersionDiff) => number +export const NAME_COMPARATOR: Comparator = (o1, o2) => o1.packageName.localeCompare(o2.packageName) /** * Default comparators used as the argument to `ramda.sortWith()`. */ export const DEFAULT_COMPARATORS: Comparator[] = [ sortBySemverChange, - (o1, o2) => o1.packageName.localeCompare(o2.packageName), // eslint-disable-line @typescript-eslint/explicit-module-boundary-types + NAME_COMPARATOR, (o1, o2) => (o1.current && o2.current) ? o1.current.localeCompare(o2.current) : 0, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types ] diff --git a/reviewing/plugin-commands-outdated/test/index.ts b/reviewing/plugin-commands-outdated/test/index.ts index 84a5b1eaed..03fc5ada75 100644 --- a/reviewing/plugin-commands-outdated/test/index.ts +++ b/reviewing/plugin-commands-outdated/test/index.ts @@ -408,3 +408,29 @@ test('pnpm outdated: catalog protocol', async () => { └─────────────┴─────────┴────────┘ `) }) + +test('pnpm outdated: support --sortField option', async () => { + tempDir() + + fs.copyFileSync(path.join(hasOutdatedDepsFixture, 'pnpm-lock.yaml'), path.resolve('pnpm-lock.yaml')) + fs.copyFileSync(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json')) + + const { output, exitCode } = await outdated.handler({ + ...OUTDATED_OPTIONS, + dir: hasOutdatedDepsFixture, + sortBy: 'name', + }) + + expect(exitCode).toBe(1) + expect(stripAnsi(output)).toBe(`\ +┌──────────────────────┬──────────────────────┬────────────┐ +│ Package │ Current │ Latest │ +├──────────────────────┼──────────────────────┼────────────┤ +│ @pnpm.e2e/deprecated │ 1.0.0 │ Deprecated │ +├──────────────────────┼──────────────────────┼────────────┤ +│ is-negative │ 1.0.0 (wanted 2.1.0) │ 2.1.0 │ +├──────────────────────┼──────────────────────┼────────────┤ +│ is-positive (dev) │ 1.0.0 (wanted 3.1.0) │ 3.1.0 │ +└──────────────────────┴──────────────────────┴────────────┘ +`) +}) diff --git a/reviewing/plugin-commands-outdated/test/recursive.ts b/reviewing/plugin-commands-outdated/test/recursive.ts index 5fd758fff9..7aaa23d9fd 100644 --- a/reviewing/plugin-commands-outdated/test/recursive.ts +++ b/reviewing/plugin-commands-outdated/test/recursive.ts @@ -4,7 +4,7 @@ import { install } from '@pnpm/plugin-commands-installation' import { outdated } from '@pnpm/plugin-commands-outdated' import { preparePackages } from '@pnpm/prepare' import stripAnsi from 'strip-ansi' -import { DEFAULT_OPTS } from './utils' +import { DEFAULT_OPTS, DEFAULT_OUTDATED_OPTS } from './utils' test('pnpm recursive outdated', async () => { preparePackages([ @@ -50,7 +50,7 @@ test('pnpm recursive outdated', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), recursive: true, @@ -75,7 +75,7 @@ test('pnpm recursive outdated', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), production: false, @@ -95,7 +95,7 @@ test('pnpm recursive outdated', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), long: true, @@ -121,7 +121,7 @@ test('pnpm recursive outdated', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), format: 'list', @@ -151,7 +151,7 @@ Dependent: project-2 { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), recursive: true, @@ -192,7 +192,7 @@ Dependent: project-2 { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), format: 'list', @@ -227,7 +227,7 @@ https://github.com/kevva/is-positive#readme { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), recursive: true, @@ -265,7 +265,7 @@ test('pnpm recursive outdated: format json when there are no outdated dependenci const { allProjects, selectedProjectsGraph } = await filterPackagesFromDir(process.cwd(), []) const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), format: 'json', @@ -320,7 +320,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), recursive: true, @@ -343,7 +343,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), production: false, @@ -363,7 +363,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async () => { { const { output, exitCode } = await outdated.handler({ - ...DEFAULT_OPTS, + ...DEFAULT_OUTDATED_OPTS, allProjects, dir: process.cwd(), recursive: true, diff --git a/reviewing/plugin-commands-outdated/test/utils/index.ts b/reviewing/plugin-commands-outdated/test/utils/index.ts index fa796c132f..5ec219608a 100644 --- a/reviewing/plugin-commands-outdated/test/utils/index.ts +++ b/reviewing/plugin-commands-outdated/test/utils/index.ts @@ -53,3 +53,8 @@ export const DEFAULT_OPTS = { workspaceConcurrency: 4, virtualStoreDirMaxLength: 120, } + +export const DEFAULT_OUTDATED_OPTS = { + ...DEFAULT_OPTS, + sortBy: 'name' as const, +}