feat: outdated command add sort-by option (#8523)

* feat: `outdated` command add `sortField` option
Currently, the default sorting result is a combination of multiple
rules, including sorting by `packageName`, sorting by `SemverChange`,
and sorting by the `current` field.
I wanted to set `sortField` to support configuration of
`name`/`semver`/`current`, but considering that the other two values
do not seem to make much sense except `name`, I
will keep the current situation for the time being.

* feat: add changeset

* fix: format

* chore: update test

* test: update

* fix: update

* refactor: rename to sort-by

* refactor: outdated

* docs: update changeset

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
btea
2024-09-28 08:56:20 +08:00
committed by GitHub
parent 3e8a225992
commit f6e9677e04
6 changed files with 68 additions and 21 deletions

View File

@@ -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).

View File

@@ -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<string, unknown> {
], 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<Config,
| 'allProjects'
| 'ca'
@@ -216,7 +223,7 @@ export async function handler (
}
}
function renderOutdatedTable (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean }): string {
function renderOutdatedTable (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean, sortBy?: 'name' }): string {
if (outdatedPackages.length === 0) return ''
const columnNames = [
'Package',
@@ -241,7 +248,7 @@ function renderOutdatedTable (outdatedPackages: readonly OutdatedPackage[], opts
const data = [
columnNames,
...sortOutdatedPackages(outdatedPackages)
...sortOutdatedPackages(outdatedPackages, { sortBy: opts.sortBy })
.map((outdatedPkg) => 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<string, OutdatedPackageJSONOutput> = sortOutdatedPackages(outdatedPackages)
function renderOutdatedJSON (outdatedPackages: readonly OutdatedPackage[], opts: { long?: boolean, sortBy?: 'name' }): string {
const outdatedPackagesJSON: Record<string, OutdatedPackageJSONOutput> = 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)
)
}

View File

@@ -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
]

View File

@@ -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 │
└──────────────────────┴──────────────────────┴────────────┘
`)
})

View File

@@ -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,

View File

@@ -53,3 +53,8 @@ export const DEFAULT_OPTS = {
workspaceConcurrency: 4,
virtualStoreDirMaxLength: 120,
}
export const DEFAULT_OUTDATED_OPTS = {
...DEFAULT_OPTS,
sortBy: 'name' as const,
}