feat: check the engine when excute the create command (#9775)

* feat: check the engine when excute the create command

* chore: test case

* test: update

* fix: update

* fix: update

* refactor: use readProjectManifestOnly to do engine checks on dependencies executed by dlx

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
btea
2025-08-04 19:38:32 +08:00
committed by GitHub
parent 3ebc0ce8b3
commit 9df09dd38e
6 changed files with 35 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-script-runners": patch
---
When executing the `pnpm create` command, must verify whether the node version is supported even if a cache already exists.

View File

@@ -73,6 +73,7 @@
"@pnpm/logger": "catalog:"
},
"devDependencies": {
"@pnpm/env.system-node-version": "workspace:*",
"@pnpm/filter-workspace-packages": "workspace:*",
"@pnpm/logger": "workspace:*",
"@pnpm/plugin-commands-script-runners": "workspace:*",

View File

@@ -1,7 +1,7 @@
import fs, { type Stats } from 'fs'
import path from 'path'
import util from 'util'
import { docsUrl } from '@pnpm/cli-utils'
import { docsUrl, readProjectManifestOnly } from '@pnpm/cli-utils'
import { createResolver } from '@pnpm/client'
import { parseWantedDependency } from '@pnpm/parse-wanted-dependency'
import { OUTPUT_OPTIONS } from '@pnpm/common-cli-options-help'
@@ -11,7 +11,7 @@ import { PnpmError } from '@pnpm/error'
import { add } from '@pnpm/plugin-commands-installation'
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
import { getBinsFromPackageManifest } from '@pnpm/package-bins'
import { type PnpmSettings, type SupportedArchitectures } from '@pnpm/types'
import { type PackageManifest, type PnpmSettings, type SupportedArchitectures } from '@pnpm/types'
import { lexCompare } from '@pnpm/util.lex-comparator'
import execa from 'execa'
import pick from 'ramda/src/pick'
@@ -138,15 +138,14 @@ export async function handler (
}
}
}
const modulesDir = path.join(cachedDir, 'node_modules')
const binsDir = path.join(modulesDir, '.bin')
const binsDir = path.join(cachedDir, 'node_modules/.bin')
const env = makeEnv({
userAgent: opts.userAgent,
prependPaths: [binsDir, ...opts.extraBinPaths],
})
const binName = opts.package
? command
: await getBinName(modulesDir, await getPkgName(cachedDir))
: await getBinName(cachedDir, opts)
try {
await execa(binName, args, {
cwd: process.cwd(),
@@ -174,9 +173,10 @@ async function getPkgName (pkgDir: string): Promise<string> {
return dependencyNames[0]
}
async function getBinName (modulesDir: string, pkgName: string): Promise<string> {
const pkgDir = path.join(modulesDir, pkgName)
const manifest = await readPackageJsonFromDir(pkgDir)
async function getBinName (cachedDir: string, opts: Pick<DlxCommandOptions, 'engineStrict'>): Promise<string> {
const pkgName = await getPkgName(cachedDir)
const pkgDir = path.join(cachedDir, 'node_modules', pkgName)
const manifest = await readProjectManifestOnly(pkgDir, opts) as PackageManifest
const bins = await getBinsFromPackageManifest(manifest, pkgDir)
if (bins.length === 0) {
throw new PnpmError('DLX_NO_BIN', `No binaries found in ${pkgName}`)

View File

@@ -2,6 +2,7 @@ import fs from 'fs'
import path from 'path'
import { add } from '@pnpm/plugin-commands-installation'
import { dlx } from '@pnpm/plugin-commands-script-runners'
import * as systemNodeVersion from '@pnpm/env.system-node-version'
import { prepareEmpty } from '@pnpm/prepare'
import { DLX_DEFAULT_OPTS as DEFAULT_OPTS } from './utils'
@@ -235,6 +236,20 @@ test('dlx with cache', async () => {
expect(spy).not.toHaveBeenCalled()
spy.mockRestore()
// Specify a node version that shx@0.3.4 does not support. Currently supported versions are >= 6.
const spySystemNodeVersion = jest.spyOn(systemNodeVersion, 'getSystemNodeVersion').mockReturnValue('v4.0.0')
await expect(dlx.handler({
...DEFAULT_OPTS,
engineStrict: true,
dir: path.resolve('project'),
storeDir: path.resolve('store'),
cacheDir: path.resolve('cache'),
dlxCacheMaxAge: Infinity,
}, ['shx@0.3.4', 'touch', 'foo'])).rejects.toThrow('Unsupported engine for')
spySystemNodeVersion.mockRestore()
})
test('dlx does not reuse expired cache', async () => {

View File

@@ -39,6 +39,9 @@
{
"path": "../../env/plugin-commands-env"
},
{
"path": "../../env/system-node-version"
},
{
"path": "../../packages/core-loggers"
},

3
pnpm-lock.yaml generated
View File

@@ -2747,6 +2747,9 @@ importers:
specifier: 'catalog:'
version: 4.3.0
devDependencies:
'@pnpm/env.system-node-version':
specifier: workspace:*
version: link:../../env/system-node-version
'@pnpm/filter-workspace-packages':
specifier: workspace:*
version: link:../../workspace/filter-workspace-packages