mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-11 00:18:32 -05:00
feat(dlx): add an option to dlx for providing a list of deps that are allowed to run install scripts (#9026)
This commit is contained in:
14
.changeset/wild-experts-hug.md
Normal file
14
.changeset/wild-experts-hug.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-script-runners": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Packages executed via `pnpm dlx` and `pnpm create` are allowed to be built (run postinstall scripts) by default.
|
||||
|
||||
If the packages executed by `dlx` or `create` have dependencies that have to be built, they should be listed via the `--allow-build` flag. For instance, if you want to run a package called `bundle` that has `esbuild` in dependencies and want to allow `esbuild` to run postinstall scripts, run:
|
||||
|
||||
```
|
||||
pnpm --allow-build=esbuild dlx bundle
|
||||
```
|
||||
|
||||
Related PR: [#9026](https://github.com/pnpm/pnpm/pull/9026).
|
||||
@@ -33,12 +33,24 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
||||
export function cliOptionsTypes (): Record<string, unknown> {
|
||||
return {
|
||||
...rcOptionsTypes(),
|
||||
'allow-build': [String, Array],
|
||||
}
|
||||
}
|
||||
|
||||
export function help (): string {
|
||||
return renderHelp({
|
||||
description: 'Creates a project from a `create-*` starter kit.',
|
||||
descriptionLists: [
|
||||
{
|
||||
title: 'Options',
|
||||
list: [
|
||||
{
|
||||
description: 'A list of package names that are allowed to run postinstall scripts during installation',
|
||||
name: '--allow-build',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
url: docsUrl('create'),
|
||||
usages: [
|
||||
'pnpm create <name>',
|
||||
|
||||
@@ -39,6 +39,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
||||
export const cliOptionsTypes = (): Record<string, unknown> => ({
|
||||
...rcOptionsTypes(),
|
||||
package: [String, Array],
|
||||
'allow-build': [String, Array],
|
||||
})
|
||||
|
||||
export function help (): string {
|
||||
@@ -52,6 +53,10 @@ export function help (): string {
|
||||
description: 'The package to install before running the command',
|
||||
name: '--package',
|
||||
},
|
||||
{
|
||||
description: 'A list of package names that are allowed to run postinstall scripts during installation',
|
||||
name: '--allow-build',
|
||||
},
|
||||
{
|
||||
description: 'Runs the script inside of a shell. Uses /bin/sh on UNIX and \\cmd.exe on Windows.',
|
||||
name: '--shell-mode',
|
||||
@@ -69,6 +74,7 @@ export function help (): string {
|
||||
export type DlxCommandOptions = {
|
||||
package?: string[]
|
||||
shellMode?: boolean
|
||||
allowBuild?: string[]
|
||||
} & Pick<Config, 'extraBinPaths' | 'registries' | 'reporter' | 'userAgent' | 'cacheDir' | 'dlxCacheMaxAge' | 'useNodeVersion' | 'symlink'> & add.AddCommandOptions
|
||||
|
||||
export async function handler (
|
||||
@@ -80,9 +86,11 @@ export async function handler (
|
||||
...opts,
|
||||
authConfig: opts.rawConfig,
|
||||
})
|
||||
const resolvedPkgAliases: string[] = []
|
||||
const resolvedPkgs = await Promise.all(pkgs.map(async (pkg) => {
|
||||
const { alias, pref } = parseWantedDependency(pkg) || {}
|
||||
if (alias == null) return pkg
|
||||
resolvedPkgAliases.push(alias)
|
||||
const resolved = await resolve({ alias, pref }, {
|
||||
lockfileDir: opts.lockfileDir ?? opts.dir,
|
||||
preferredVersions: {},
|
||||
@@ -95,6 +103,7 @@ export async function handler (
|
||||
dlxCacheMaxAge: opts.dlxCacheMaxAge,
|
||||
cacheDir: opts.cacheDir,
|
||||
registries: opts.registries,
|
||||
allowBuild: opts.allowBuild ?? [],
|
||||
})
|
||||
if (!cacheExists) {
|
||||
fs.mkdirSync(cachedDir, { recursive: true })
|
||||
@@ -106,6 +115,11 @@ export async function handler (
|
||||
dir: cachedDir,
|
||||
lockfileDir: cachedDir,
|
||||
rootProjectManifestDir: cachedDir, // This property won't be used as rootProjectManifest will be undefined
|
||||
rootProjectManifest: {
|
||||
pnpm: {
|
||||
onlyBuiltDependencies: [...resolvedPkgAliases, ...(opts.allowBuild ?? [])],
|
||||
},
|
||||
},
|
||||
saveProd: true, // dlx will be looking for the package in the "dependencies" field!
|
||||
saveDev: false,
|
||||
saveOptional: false,
|
||||
@@ -192,6 +206,7 @@ function findCache (pkgs: string[], opts: {
|
||||
cacheDir: string
|
||||
dlxCacheMaxAge: number
|
||||
registries: Record<string, string>
|
||||
allowBuild: string[]
|
||||
}): { cacheLink: string, cacheExists: boolean, cachedDir: string } {
|
||||
const dlxCommandCacheDir = createDlxCommandCacheDir(pkgs, opts)
|
||||
const cacheLink = path.join(dlxCommandCacheDir, 'pkg')
|
||||
@@ -208,19 +223,24 @@ function createDlxCommandCacheDir (
|
||||
opts: {
|
||||
registries: Record<string, string>
|
||||
cacheDir: string
|
||||
allowBuild: string[]
|
||||
}
|
||||
): string {
|
||||
const dlxCacheDir = path.resolve(opts.cacheDir, 'dlx')
|
||||
const cacheKey = createCacheKey(pkgs, opts.registries)
|
||||
const cacheKey = createCacheKey(pkgs, opts.registries, opts.allowBuild)
|
||||
const cachePath = path.join(dlxCacheDir, cacheKey)
|
||||
fs.mkdirSync(cachePath, { recursive: true })
|
||||
return cachePath
|
||||
}
|
||||
|
||||
export function createCacheKey (pkgs: string[], registries: Record<string, string>): string {
|
||||
export function createCacheKey (pkgs: string[], registries: Record<string, string>, allowBuild?: string[]): string {
|
||||
const sortedPkgs = [...pkgs].sort((a, b) => a.localeCompare(b))
|
||||
const sortedRegistries = Object.entries(registries).sort(([k1], [k2]) => k1.localeCompare(k2))
|
||||
const hashStr = JSON.stringify([sortedPkgs, sortedRegistries])
|
||||
const args: unknown[] = [sortedPkgs, sortedRegistries]
|
||||
if (allowBuild?.length) {
|
||||
args.push({ allowBuild: allowBuild.sort((pkg1, pkg2) => pkg1.localeCompare(pkg2)) })
|
||||
}
|
||||
const hashStr = JSON.stringify(args)
|
||||
return createHexHash(hashStr)
|
||||
}
|
||||
|
||||
|
||||
@@ -296,3 +296,52 @@ test('dlx still saves cache even if execution fails', async () => {
|
||||
expect(fs.readFileSync(path.resolve('not-a-dir'), 'utf-8')).toEqual(expect.anything())
|
||||
verifyDlxCache(createCacheKey('shx@0.3.4'))
|
||||
})
|
||||
|
||||
test('dlx builds the package that is executed', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
await dlx.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: path.resolve('project'),
|
||||
storeDir: path.resolve('store'),
|
||||
cacheDir: path.resolve('cache'),
|
||||
dlxCacheMaxAge: Infinity,
|
||||
}, ['@pnpm.e2e/has-bin-and-needs-build'])
|
||||
|
||||
// The command file of the above package is created by a postinstall script
|
||||
// so if it doesn't fail it means that it was built.
|
||||
|
||||
const dlxCacheDir = path.resolve('cache', 'dlx', createCacheKey('@pnpm.e2e/has-bin-and-needs-build@1.0.0'), 'pkg')
|
||||
const builtPkg1Path = path.join(dlxCacheDir, 'node_modules/.pnpm/@pnpm.e2e+pre-and-postinstall-scripts-example@1.0.0/node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example')
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'package.json'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'generated-by-preinstall.js'))).toBeFalsy()
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'generated-by-postinstall.js'))).toBeFalsy()
|
||||
|
||||
const builtPkg2Path = path.join(dlxCacheDir, 'node_modules/.pnpm/@pnpm.e2e+install-script-example@1.0.0/node_modules/@pnpm.e2e/install-script-example')
|
||||
expect(fs.existsSync(path.join(builtPkg2Path, 'package.json'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(builtPkg2Path, 'generated-by-install.js'))).toBeFalsy()
|
||||
})
|
||||
|
||||
test('dlx builds the packages passed via --allow-build', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
const allowBuild = ['@pnpm.e2e/install-script-example']
|
||||
await dlx.handler({
|
||||
...DEFAULT_OPTS,
|
||||
allowBuild,
|
||||
dir: path.resolve('project'),
|
||||
storeDir: path.resolve('store'),
|
||||
cacheDir: path.resolve('cache'),
|
||||
dlxCacheMaxAge: Infinity,
|
||||
}, ['@pnpm.e2e/has-bin-and-needs-build'])
|
||||
|
||||
const dlxCacheDir = path.resolve('cache', 'dlx', dlx.createCacheKey(['@pnpm.e2e/has-bin-and-needs-build@1.0.0'], DEFAULT_OPTS.registries, allowBuild), 'pkg')
|
||||
const builtPkg1Path = path.join(dlxCacheDir, 'node_modules/.pnpm/@pnpm.e2e+pre-and-postinstall-scripts-example@1.0.0/node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example')
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'package.json'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'generated-by-preinstall.js'))).toBeFalsy()
|
||||
expect(fs.existsSync(path.join(builtPkg1Path, 'generated-by-postinstall.js'))).toBeFalsy()
|
||||
|
||||
const builtPkg2Path = path.join(dlxCacheDir, 'node_modules/.pnpm/@pnpm.e2e+install-script-example@1.0.0/node_modules/@pnpm.e2e/install-script-example')
|
||||
expect(fs.existsSync(path.join(builtPkg2Path, 'package.json'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(builtPkg2Path, 'generated-by-install.js'))).toBeTruthy()
|
||||
})
|
||||
|
||||
@@ -268,7 +268,7 @@ when running add/update with the --workspace option')
|
||||
|
||||
const installOpts: Omit<MutateModulesOptions, 'allProjects'> = {
|
||||
...opts,
|
||||
...getOptionsFromRootManifest(opts.dir, manifest),
|
||||
...getOptionsFromRootManifest(opts.dir, (opts.dir === opts.rootProjectManifestDir ? opts.rootProjectManifest ?? manifest : manifest)),
|
||||
forceHoistPattern,
|
||||
forcePublicHoistPattern,
|
||||
// In case installation is done in a multi-package repository
|
||||
|
||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
@@ -55,8 +55,8 @@ catalogs:
|
||||
specifier: 0.0.0
|
||||
version: 0.0.0
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 3.48.0
|
||||
version: 3.48.0
|
||||
specifier: 3.50.0
|
||||
version: 3.50.0
|
||||
'@pnpm/semver-diff':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
@@ -846,7 +846,7 @@ importers:
|
||||
version: link:../../pkg-manager/modules-yaml
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -880,7 +880,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store.cafs':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/cafs
|
||||
@@ -955,7 +955,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/worker':
|
||||
specifier: workspace:*
|
||||
version: link:../../worker
|
||||
@@ -1138,7 +1138,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@types/ramda':
|
||||
specifier: 'catalog:'
|
||||
version: 0.29.12
|
||||
@@ -2116,7 +2116,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -2387,7 +2387,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -2532,7 +2532,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-ipc-server':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-ipc-server
|
||||
@@ -4181,7 +4181,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -4471,7 +4471,7 @@ importers:
|
||||
version: link:../../pkg-manifest/read-package-json
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-path
|
||||
@@ -4747,7 +4747,7 @@ importers:
|
||||
version: link:../read-projects-context
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-path
|
||||
@@ -5107,7 +5107,7 @@ importers:
|
||||
version: 'link:'
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -5321,7 +5321,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -5925,7 +5925,7 @@ importers:
|
||||
version: link:../pkg-manifest/read-project-manifest
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/run-npm':
|
||||
specifier: workspace:*
|
||||
version: link:../exec/run-npm
|
||||
@@ -6241,7 +6241,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -6365,7 +6365,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-ipc-server':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-ipc-server
|
||||
@@ -6964,7 +6964,7 @@ importers:
|
||||
version: link:../../pkg-manifest/read-package-json
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -7028,7 +7028,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/workspace.filter-packages-from-dir':
|
||||
specifier: workspace:*
|
||||
version: link:../../workspace/filter-packages-from-dir
|
||||
@@ -7113,7 +7113,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -7470,7 +7470,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 3.48.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 3.50.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@types/archy':
|
||||
specifier: 'catalog:'
|
||||
version: 0.0.33
|
||||
@@ -9187,8 +9187,8 @@ packages:
|
||||
resolution: {integrity: sha512-LFTWzfJbu6+l86bw/uUAsPU05n1oTqg6jzqyTXYDJPfVclqTfPnHiZoC1nvVvQlE7iVg3bhJ7SXg9IyzK7RWDQ==}
|
||||
engines: {node: '>=18.12'}
|
||||
|
||||
'@pnpm/registry-mock@3.48.0':
|
||||
resolution: {integrity: sha512-5d5GFyz+DlGe50SLJjMWcEKkzr7EvbKQq71hwcjOUAr3+mPEz7+Ul+wFkw0gJ/3mYjk4pmFZerP8qLdd8SBZPQ==}
|
||||
'@pnpm/registry-mock@3.50.0':
|
||||
resolution: {integrity: sha512-/YGi3OCMWXkk8JwUbVOQo5Ws89yA9ub+jnYboAgxUJ84K6a2m3xZiRn1im0zxC7L6F63Xx++ZuGrlwKEWHqf+A==}
|
||||
engines: {node: '>=10.13'}
|
||||
hasBin: true
|
||||
|
||||
@@ -16116,7 +16116,7 @@ snapshots:
|
||||
sort-keys: 4.2.0
|
||||
strip-bom: 4.0.0
|
||||
|
||||
'@pnpm/registry-mock@3.48.0(encoding@0.1.13)(typanion@3.14.0)':
|
||||
'@pnpm/registry-mock@3.50.0(encoding@0.1.13)(typanion@3.14.0)':
|
||||
dependencies:
|
||||
anonymous-npm-registry-client: 0.2.0
|
||||
execa: 5.1.1
|
||||
|
||||
@@ -56,7 +56,7 @@ catalog:
|
||||
"@pnpm/npm-package-arg": ^1.0.0
|
||||
"@pnpm/os.env.path-extender": ^2.0.0
|
||||
"@pnpm/patch-package": 0.0.0
|
||||
"@pnpm/registry-mock": 3.48.0
|
||||
"@pnpm/registry-mock": 3.50.0
|
||||
"@pnpm/semver-diff": ^1.1.0
|
||||
"@pnpm/tabtab": ^0.5.4
|
||||
"@pnpm/util.lex-comparator": 3.0.0
|
||||
|
||||
Reference in New Issue
Block a user