diff --git a/.changeset/little-kings-invent.md b/.changeset/little-kings-invent.md new file mode 100644 index 0000000000..98592c4285 --- /dev/null +++ b/.changeset/little-kings-invent.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-installation": patch +"pnpm": patch +--- + +Fixed `pnpm update --interactive` table breaking with long version strings (e.g., prerelease versions like `7.0.0-dev.20251209.1`) by dynamically calculating column widths instead of using hardcoded values [#10316](https://github.com/pnpm/pnpm/issues/10316). diff --git a/pkg-manager/plugin-commands-installation/src/update/getUpdateChoices.ts b/pkg-manager/plugin-commands-installation/src/update/getUpdateChoices.ts index 9d82a040a7..751165a3e1 100644 --- a/pkg-manager/plugin-commands-installation/src/update/getUpdateChoices.ts +++ b/pkg-manager/plugin-commands-installation/src/update/getUpdateChoices.ts @@ -1,3 +1,4 @@ +import { stripVTControlCharacters } from 'util' import colorizeSemverDiff from '@pnpm/colorize-semver-diff' import { type OutdatedPackage } from '@pnpm/outdated' import semverDiff from '@pnpm/semver-diff' @@ -147,8 +148,8 @@ function alignColumns (rows: string[][]): string[] { columns: { 0: { width: 50, truncate: 100 }, - 1: { width: 15, alignment: 'right' }, - 3: { width: 15 }, + 1: { width: getColumnWidth(rows, 1, 15), alignment: 'right' }, + 3: { width: getColumnWidth(rows, 3, 15) }, 4: { paddingLeft: 2 }, 5: { paddingLeft: 2 }, }, @@ -156,3 +157,10 @@ function alignColumns (rows: string[][]): string[] { } ).split('\n') } + +function getColumnWidth (rows: string[][], columnIndex: number, minWidth: number): number { + return rows.reduce((max, row) => { + if (row[columnIndex] == null) return max + return Math.max(max, stripVTControlCharacters(row[columnIndex]).length) + }, minWidth) +} diff --git a/pkg-manager/plugin-commands-installation/test/update/getUpdateChoices.test.ts b/pkg-manager/plugin-commands-installation/test/update/getUpdateChoices.test.ts index 457019b5e6..3e9fe8cb9d 100644 --- a/pkg-manager/plugin-commands-installation/test/update/getUpdateChoices.test.ts +++ b/pkg-manager/plugin-commands-installation/test/update/getUpdateChoices.test.ts @@ -139,3 +139,31 @@ test('getUpdateChoices()', () => { }, ]) }) + +test('getUpdateChoices() handles long version strings without wrapping', () => { + const choices = getUpdateChoices([ + { + alias: '@typescript/native-preview', + belongsTo: 'devDependencies' as const, + current: '7.0.0-dev.20251209.1', + latestManifest: { + name: '@typescript/native-preview', + version: '7.0.0-dev.20251214.1', + homepage: 'https://github.com/nicolo-ribaudo/tc39-proposal-structs', + }, + packageName: '@typescript/native-preview', + wanted: '7.0.0-dev.20251209.1', + }, + ], false) + + const dataRow = choices[0].choices[1] as { message: string; value: string; name: string } + expect(dataRow).toStrictEqual({ + message: expect.stringContaining('7.0.0-dev.20251209.1'), + value: '@typescript/native-preview', + name: '@typescript/native-preview', + }) + // The rendered message must be a single line (no wrapping) + expect(dataRow.message).not.toContain('\n') + // Both current and target versions must appear in the output + expect(dataRow.message).toContain('7.0.0-dev.20251214.1') +})