diff --git a/.changeset/dull-rats-cheer.md b/.changeset/dull-rats-cheer.md new file mode 100644 index 0000000000..b4faa9e05f --- /dev/null +++ b/.changeset/dull-rats-cheer.md @@ -0,0 +1,7 @@ +--- +"@pnpm/plugin-commands-outdated": patch +"@pnpm/outdated": patch +pnpm: patch +--- + +The `pnpm outdated` command now supports the [`catalog:` protocol](https://pnpm.io/catalogs). diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/.gitignore b/__fixtures__/has-outdated-deps-using-catalog-protocol/.gitignore new file mode 100644 index 0000000000..5867a0493e --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/.gitignore @@ -0,0 +1,2 @@ +!**/node_modules/**/* +!/node_modules/ diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/.npmrc b/__fixtures__/has-outdated-deps-using-catalog-protocol/.npmrc new file mode 100644 index 0000000000..135f7a0d5d --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/.npmrc @@ -0,0 +1 @@ +shared-workspace-lockfile=false diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.modules.yaml b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.modules.yaml new file mode 100644 index 0000000000..ba1c4477d8 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.modules.yaml @@ -0,0 +1,22 @@ +hoistPattern: + - '*' +hoistedDependencies: {} +included: + dependencies: true + devDependencies: true + optionalDependencies: true +injectedDeps: {} +layoutVersion: 5 +nodeLinker: isolated +packageManager: pnpm@9.5.0 +pendingBuilds: [] +prunedAt: Fri, 12 Jul 2024 05:02:19 GMT +publicHoistPattern: + - '*eslint*' + - '*prettier*' +registries: + default: https://registry.npmjs.org/ +skipped: [] +storeDir: /Users/gluxon/Library/pnpm/store/v3 +virtualStoreDir: .pnpm +virtualStoreDirMaxLength: 120 diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/index.js b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/index.js new file mode 100644 index 0000000000..342e43c190 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/index.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (n) { + if (typeof n !== 'number') { + throw new TypeError('Expected a number'); + } + + return n < 0; +}; diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/license b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/license new file mode 100644 index 0000000000..0f8cf79c3c --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Kevin Martensson (github.com/kevva) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/package.json b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/package.json new file mode 100644 index 0000000000..d1dbf34643 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/package.json @@ -0,0 +1,29 @@ +{ + "name": "is-negative", + "version": "1.0.0", + "description": "Test if a number is negative", + "license": "MIT", + "repository": "kevva/is-negative", + "author": { + "name": "Kevin Martensson", + "email": "kevinmartensson@gmail.com", + "url": "github.com/kevva" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "node test.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "negative", + "number", + "test" + ], + "devDependencies": { + "ava": "^0.0.4" + } +} diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/readme.md b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/readme.md new file mode 100644 index 0000000000..0b3aa99fda --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.0/node_modules/is-negative/readme.md @@ -0,0 +1,31 @@ +# is-negative [![Build Status](https://travis-ci.org/kevva/is-negative.svg?branch=master)](https://travis-ci.org/kevva/is-negative) + +> Test if a number is positive + + +## Install + +``` +$ npm install --save is-negative +``` + + +## Usage + +```js +var isNegative = require('is-negative'); + +isNegative(-1); +//=> true + +isNegative(1); +//=> false + +isNegative(0); +//=> false +``` + + +## License + +MIT © [Kevin Martensson](http://github.com/kevva) diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/index.js b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/index.js new file mode 100644 index 0000000000..342e43c190 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/index.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (n) { + if (typeof n !== 'number') { + throw new TypeError('Expected a number'); + } + + return n < 0; +}; diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/license b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/license new file mode 100644 index 0000000000..0f8cf79c3c --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Kevin Martensson (github.com/kevva) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/package.json b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/package.json new file mode 100644 index 0000000000..e3edadcdbe --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/package.json @@ -0,0 +1,29 @@ +{ + "name": "is-negative", + "version": "1.0.1", + "description": "Test if a number is negative", + "license": "MIT", + "repository": "kevva/is-negative", + "author": { + "name": "Kevin Martensson", + "email": "kevinmartensson@gmail.com", + "url": "github.com/kevva" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "node test.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "negative", + "number", + "test" + ], + "devDependencies": { + "ava": "^0.0.4" + } +} diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/readme.md b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/readme.md new file mode 100644 index 0000000000..8cabd5ea39 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/is-negative@1.0.1/node_modules/is-negative/readme.md @@ -0,0 +1,31 @@ +# is-negative [![Build Status](https://travis-ci.org/kevva/is-negative.svg?branch=master)](https://travis-ci.org/kevva/is-negative) + +> Test if a number is negative + + +## Install + +``` +$ npm install --save is-negative +``` + + +## Usage + +```js +var isNegative = require('is-negative'); + +isNegative(-1); +//=> true + +isNegative(1); +//=> false + +isNegative(0); +//=> false +``` + + +## License + +MIT © [Kevin Martensson](http://github.com/kevva) diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/lock.yaml b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/lock.yaml new file mode 100644 index 0000000000..7ea42699a9 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/.pnpm/lock.yaml @@ -0,0 +1,29 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +catalogs: + default: + is-negative: + specifier: ^1.0.0 + version: 1.0.0 + +importers: + + .: + dependencies: + is-negative: + specifier: 'catalog:' + version: 1.0.0 + +packages: + + is-negative@1.0.0: + resolution: {integrity: sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==} + engines: {node: '>=0.10.0'} + +snapshots: + + is-negative@1.0.0: {} diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/is-negative b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/is-negative new file mode 120000 index 0000000000..b9e7110b6c --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/node_modules/is-negative @@ -0,0 +1 @@ +.pnpm/is-negative@1.0.0/node_modules/is-negative \ No newline at end of file diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/package.json b/__fixtures__/has-outdated-deps-using-catalog-protocol/package.json new file mode 100644 index 0000000000..5e20d52ae6 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "is-negative": "catalog:" + } +} diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-lock.yaml b/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-lock.yaml new file mode 100644 index 0000000000..7ea42699a9 --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-lock.yaml @@ -0,0 +1,29 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +catalogs: + default: + is-negative: + specifier: ^1.0.0 + version: 1.0.0 + +importers: + + .: + dependencies: + is-negative: + specifier: 'catalog:' + version: 1.0.0 + +packages: + + is-negative@1.0.0: + resolution: {integrity: sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==} + engines: {node: '>=0.10.0'} + +snapshots: + + is-negative@1.0.0: {} diff --git a/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-workspace.yaml b/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-workspace.yaml new file mode 100644 index 0000000000..5724006c2d --- /dev/null +++ b/__fixtures__/has-outdated-deps-using-catalog-protocol/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +packages: + - '.' + +catalog: + is-negative: ^1.0.0 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52d1d6ba75..693be05dde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6228,6 +6228,12 @@ importers: reviewing/outdated: dependencies: + '@pnpm/catalogs.resolver': + specifier: workspace:* + version: link:../../catalogs/resolver + '@pnpm/catalogs.types': + specifier: workspace:* + version: link:../../catalogs/types '@pnpm/client': specifier: workspace:* version: link:../../pkg-manager/client diff --git a/reviewing/outdated/package.json b/reviewing/outdated/package.json index cca20a2e80..e23aef8fca 100644 --- a/reviewing/outdated/package.json +++ b/reviewing/outdated/package.json @@ -33,6 +33,8 @@ "@pnpm/logger": "^5.0.0" }, "dependencies": { + "@pnpm/catalogs.resolver": "workspace:*", + "@pnpm/catalogs.types": "workspace:*", "@pnpm/client": "workspace:*", "@pnpm/constants": "workspace:*", "@pnpm/dependency-path": "workspace:*", diff --git a/reviewing/outdated/src/outdated.ts b/reviewing/outdated/src/outdated.ts index 4544766a08..f2296fb8a1 100644 --- a/reviewing/outdated/src/outdated.ts +++ b/reviewing/outdated/src/outdated.ts @@ -1,3 +1,10 @@ +import { + matchCatalogResolveResult, + resolveFromCatalog, + type CatalogResolutionFound, + type WantedDependency, +} from '@pnpm/catalogs.resolver' +import { type Catalogs } from '@pnpm/catalogs.types' import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants' import { PnpmError } from '@pnpm/error' import { @@ -38,6 +45,7 @@ export interface OutdatedPackage { export async function outdated ( opts: { + catalogs?: Catalogs compatible?: boolean currentLockfile: Lockfile | null getLatestManifest: GetLatestManifestFunction @@ -91,6 +99,8 @@ export async function outdated ( pkgs = pkgs.filter((pkgName) => opts.match!(pkgName)) } + const _replaceCatalogProtocolIfNecessary = replaceCatalogProtocolIfNecessary.bind(null, opts.catalogs ?? {}) + await Promise.all( pkgs.map(async (alias) => { if (!allDeps[alias]) return @@ -121,11 +131,12 @@ export async function outdated ( const { name: packageName } = nameVerFromPkgSnapshot(relativeDepPath, pkgSnapshot) const name = dp.parse(relativeDepPath).name ?? packageName + const pref = _replaceCatalogProtocolIfNecessary({ alias, pref: allDeps[alias] }) // If the npm resolve parser cannot parse the spec of the dependency, // it means that the package is not from a npm-compatible registry. // In that case, we can't check whether the package is up-to-date if ( - parsePref(allDeps[alias], alias, 'latest', pickRegistryForPackage(opts.registries, name)) == null + parsePref(pref, alias, 'latest', pickRegistryForPackage(opts.registries, name)) == null ) { if (current !== wanted) { outdated.push({ @@ -190,3 +201,13 @@ function packageHasNoDeps (manifest: ProjectManifest): boolean { function isEmpty (obj: object): boolean { return Object.keys(obj).length === 0 } + +function replaceCatalogProtocolIfNecessary (catalogs: Catalogs, wantedDependency: WantedDependency) { + return matchCatalogResolveResult(resolveFromCatalog(catalogs, wantedDependency), { + unused: () => wantedDependency.pref, + found: (found: CatalogResolutionFound) => found.resolution.specifier, + misconfiguration: (misconfiguration) => { + throw misconfiguration.error + }, + }) +} diff --git a/reviewing/outdated/src/outdatedDepsOfProjects.ts b/reviewing/outdated/src/outdatedDepsOfProjects.ts index d8bfe83741..1539b29c4d 100644 --- a/reviewing/outdated/src/outdatedDepsOfProjects.ts +++ b/reviewing/outdated/src/outdatedDepsOfProjects.ts @@ -1,4 +1,5 @@ import path from 'path' +import { type Catalogs } from '@pnpm/catalogs.types' import { readCurrentLockfile, readWantedLockfile, @@ -18,6 +19,7 @@ export async function outdatedDepsOfProjects ( pkgs: Array<{ rootDir: ProjectRootDir, manifest: ProjectManifest }>, args: string[], opts: Omit & { + catalogs?: Catalogs compatible?: boolean ignoreDependencies?: string[] include: IncludedDependencies @@ -43,6 +45,7 @@ export async function outdatedDepsOfProjects ( return Promise.all(pkgs.map(async ({ rootDir, manifest }): Promise => { const match = (args.length > 0) && createMatcher(args) || undefined return outdated({ + catalogs: opts.catalogs, compatible: opts.compatible, currentLockfile, getLatestManifest, diff --git a/reviewing/outdated/tsconfig.json b/reviewing/outdated/tsconfig.json index 7b5b39d7e4..16afef937d 100644 --- a/reviewing/outdated/tsconfig.json +++ b/reviewing/outdated/tsconfig.json @@ -9,6 +9,12 @@ "../../__typings__/**/*.d.ts" ], "references": [ + { + "path": "../../catalogs/resolver" + }, + { + "path": "../../catalogs/types" + }, { "path": "../../config/matcher" }, diff --git a/reviewing/plugin-commands-outdated/src/outdated.ts b/reviewing/plugin-commands-outdated/src/outdated.ts index fde1ff456e..6454508523 100644 --- a/reviewing/plugin-commands-outdated/src/outdated.ts +++ b/reviewing/plugin-commands-outdated/src/outdated.ts @@ -129,6 +129,7 @@ export type OutdatedCommandOptions = { | 'allProjects' | 'ca' | 'cacheDir' +| 'catalogs' | 'cert' | 'dev' | 'dir' diff --git a/reviewing/plugin-commands-outdated/test/index.ts b/reviewing/plugin-commands-outdated/test/index.ts index 5b5a0eff3e..84a5b1eaed 100644 --- a/reviewing/plugin-commands-outdated/test/index.ts +++ b/reviewing/plugin-commands-outdated/test/index.ts @@ -17,6 +17,7 @@ const hasNotOutdatedDepsFixture = f.find('has-not-outdated-deps') const hasMajorOutdatedDepsFixture = f.find('has-major-outdated-deps') const hasNoLockfileFixture = f.find('has-no-lockfile') const withPnpmUpdateIgnore = f.find('with-pnpm-update-ignore') +const hasOutdatedDepsUsingCatalogProtocol = f.find('has-outdated-deps-using-catalog-protocol') const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}` @@ -386,3 +387,24 @@ test('ignore packages in package.json > pnpm.updateConfig.ignoreDependencies in └─────────────┴─────────┴────────┘ `) }) + +test('pnpm outdated: catalog protocol', async () => { + const { output, exitCode } = await outdated.handler({ + ...OUTDATED_OPTIONS, + catalogs: { + // Duplicating the catalog config in the pnpm-workspace.yaml inline to + // avoid an async read and catalog config normalization call. + default: { 'is-negative': '^1.0.0' }, + }, + dir: hasOutdatedDepsUsingCatalogProtocol, + }) + + expect(exitCode).toBe(1) + expect(stripAnsi(output)).toBe(`\ +┌─────────────┬─────────┬────────┐ +│ Package │ Current │ Latest │ +├─────────────┼─────────┼────────┤ +│ is-negative │ 1.0.0 │ 2.1.0 │ +└─────────────┴─────────┴────────┘ +`) +})