From 8eee41691cab07696997e5119350d4e5c8a3e69f Mon Sep 17 00:00:00 2001 From: Maikel van Dort Date: Mon, 26 Jan 2026 07:06:36 +0100 Subject: [PATCH] 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 * Update .changeset/curly-dryers-jam.md Close #10249 Co-authored-by: Brandon Cheng --------- Co-authored-by: Brandon Cheng --- .changeset/curly-dryers-jam.md | 6 ++++++ .../package.json | 1 + .../plugin-commands-script-runners/src/dlx.ts | 20 ++++++++++++++++++- .../test/dlx.e2e.ts | 19 ++++++++++++++++++ .../tsconfig.json | 3 +++ pnpm-lock.yaml | 3 +++ 6 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .changeset/curly-dryers-jam.md diff --git a/.changeset/curly-dryers-jam.md b/.changeset/curly-dryers-jam.md new file mode 100644 index 0000000000..6caff3073d --- /dev/null +++ b/.changeset/curly-dryers-jam.md @@ -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:`. diff --git a/exec/plugin-commands-script-runners/package.json b/exec/plugin-commands-script-runners/package.json index 120663d6b9..26fc441071 100644 --- a/exec/plugin-commands-script-runners/package.json +++ b/exec/plugin-commands-script-runners/package.json @@ -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:*", diff --git a/exec/plugin-commands-script-runners/src/dlx.ts b/exec/plugin-commands-script-runners/src/dlx.ts index e58e291deb..f755d84583 100644 --- a/exec/plugin-commands-script-runners/src/dlx.ts +++ b/exec/plugin-commands-script-runners/src/dlx.ts @@ -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 + } +} diff --git a/exec/plugin-commands-script-runners/test/dlx.e2e.ts b/exec/plugin-commands-script-runners/test/dlx.e2e.ts index 8680ce79f1..a2404b6688 100644 --- a/exec/plugin-commands-script-runners/test/dlx.e2e.ts +++ b/exec/plugin-commands-script-runners/test/dlx.e2e.ts @@ -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')) +}) diff --git a/exec/plugin-commands-script-runners/tsconfig.json b/exec/plugin-commands-script-runners/tsconfig.json index 3a0fc56cac..fa6b92debb 100644 --- a/exec/plugin-commands-script-runners/tsconfig.json +++ b/exec/plugin-commands-script-runners/tsconfig.json @@ -15,6 +15,9 @@ { "path": "../../__utils__/test-ipc-server" }, + { + "path": "../../catalogs/resolver" + }, { "path": "../../cli/cli-utils" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa85460714..da3d72c9c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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