feat: add support for catalogs with dlx (#10434)

* feat: add support for catalogs with dlx

* fix: feedback

* Update .changeset/curly-dryers-jam.md

Co-authored-by: Brandon Cheng <gluxon@users.noreply.github.com>

* Update .changeset/curly-dryers-jam.md

Close #10249

Co-authored-by: Brandon Cheng <gluxon@users.noreply.github.com>

---------

Co-authored-by: Brandon Cheng <gluxon@users.noreply.github.com>
This commit is contained in:
Maikel van Dort
2026-01-26 07:06:36 +01:00
committed by GitHub
parent 0625e20442
commit 8eee41691c
6 changed files with 51 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-script-runners": minor
pnpm: minor
---
The `pnpm dlx` / `pnpx` command now supports the `catalog:` protocol. Example: `pnpm dlx shx@catalog:`.

View File

@@ -33,6 +33,7 @@
"compile": "tsgo --build && pnpm run lint --fix"
},
"dependencies": {
"@pnpm/catalogs.resolver": "workspace:*",
"@pnpm/cli-utils": "workspace:*",
"@pnpm/client": "workspace:*",
"@pnpm/command": "workspace:*",

View File

@@ -18,6 +18,10 @@ import { pick } from 'ramda'
import renderHelp from 'render-help'
import symlinkDir from 'symlink-dir'
import { makeEnv } from './makeEnv.js'
import {
type CatalogResolver,
resolveFromCatalog,
} from '@pnpm/catalogs.resolver'
export const skipPackageManagerCheck = true
@@ -91,6 +95,7 @@ export async function handler (
opts.trustPolicy === 'no-downgrade'
) && !opts.registrySupportsTimeField
)
const catalogResolver = resolveFromCatalog.bind(null, opts.catalogs ?? {})
const { resolve } = createResolver({
...opts,
authConfig: opts.rawConfig,
@@ -102,8 +107,11 @@ export async function handler (
const resolvedPkgs = await Promise.all(pkgs.map(async (pkg) => {
const { alias, bareSpecifier } = parseWantedDependency(pkg) || {}
if (alias == null) return pkg
const resolvedBareSpecifier = bareSpecifier != null
? resolveCatalogProtocol(catalogResolver, alias, bareSpecifier)
: bareSpecifier
resolvedPkgAliases.push(alias)
const resolved = await resolve({ alias, bareSpecifier }, {
const resolved = await resolve({ alias, bareSpecifier: resolvedBareSpecifier }, {
lockfileDir: opts.lockfileDir ?? opts.dir,
preferredVersions: {},
projectDir: opts.dir,
@@ -299,3 +307,13 @@ function getPrepareDir (cachePath: string): string {
const name = `${new Date().getTime().toString(16)}-${process.pid.toString(16)}`
return path.join(cachePath, name)
}
function resolveCatalogProtocol (catalogResolver: CatalogResolver, alias: string, bareSpecifier: string): string {
const result = catalogResolver({ alias, bareSpecifier })
switch (result.type) {
case 'found': return result.resolution.specifier
case 'unused': return bareSpecifier
case 'misconfiguration': throw result.error
}
}

View File

@@ -400,3 +400,22 @@ test('dlx should fail when the requested package does not meet the minimum age r
}, ['shx@0.3.4'])
).rejects.toThrow(/Version 0\.3\.4 \(released .+\) of shx does not meet the minimumReleaseAge constraint/)
})
test('dlx with catalog', async () => {
prepareEmpty()
await dlx.handler({
...DEFAULT_OPTS,
dir: path.resolve('project'),
storeDir: path.resolve('store'),
cacheDir: path.resolve('cache'),
dlxCacheMaxAge: Infinity,
catalogs: {
default: {
shx: '^0.3.4',
},
},
}, ['shx@catalog:'])
verifyDlxCache(createCacheKey('shx@0.3.4'))
})

View File

@@ -15,6 +15,9 @@
{
"path": "../../__utils__/test-ipc-server"
},
{
"path": "../../catalogs/resolver"
},
{
"path": "../../cli/cli-utils"
},

3
pnpm-lock.yaml generated
View File

@@ -2876,6 +2876,9 @@ importers:
exec/plugin-commands-script-runners:
dependencies:
'@pnpm/catalogs.resolver':
specifier: workspace:*
version: link:../../catalogs/resolver
'@pnpm/cli-utils':
specifier: workspace:*
version: link:../../cli/cli-utils