mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-12 18:49:41 -04:00
`pnpm dlx` (and `pnpx`/`pnx`/`pnpm create`) now mirrors the `pnpm add -g` flow when the launched package's transitive deps have install scripts: - dlx overrides `strictDepBuilds: false` for its install so the v11 default no longer turns ignored builds into an `ERR_PNPM_IGNORED_BUILDS` error. Without this, `pnpx @google/gemini-cli` (and similar — `node-pty`, `@github/keytar`) failed outright and forced users to retry with `--allow-build=<pkg>` for every offending dependency. - After install, dlx detects skipped builds via `getAutomaticallyIgnoredBuilds` and runs the same interactive `approve-builds` prompt as `pnpm add -g`. In non-interactive mode the install is committed with builds skipped, matching `pnpm add -g` in CI; users who need those scripts can re-invoke with `--allow-build=<pkg>` to force a fresh cache key. - If the install errors for unrelated reasons (network, etc.) the partially-populated prepare directory is removed so the next dlx run starts clean. Closes #11444. ### Plumbing - Exports `getAutomaticallyIgnoredBuilds` from `@pnpm/building.commands` so dlx can detect skipped builds without re-implementing modules-yaml reading. - Adds `strictDepBuilds` (optional) to `InstallCommandOptions` — already accepted at runtime via the spread, this just makes it explicit at the type level so callers can override it.
145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
import { beforeEach, expect, it, jest } from '@jest/globals'
|
|
import { PnpmError } from '@pnpm/error'
|
|
|
|
import { DLX_DEFAULT_OPTS as DEFAULT_OPTS } from './utils/index.js'
|
|
|
|
jest.unstable_mockModule('../src/dlx.js', () => ({ handler: jest.fn() }))
|
|
|
|
const { create, dlx } = await import('../src/index.js')
|
|
|
|
beforeEach(() => {
|
|
jest.mocked(dlx.handler).mockClear()
|
|
})
|
|
|
|
it('throws an error if called without arguments', async () => {
|
|
await expect(create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, [])).rejects.toThrow(PnpmError)
|
|
expect(dlx.handler).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it(
|
|
'appends `create-` to an unscoped package that doesn\'t start with `create-`',
|
|
async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['some-app'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-some-app'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['create_no_dash'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-create_no_dash'], undefined)
|
|
}
|
|
)
|
|
|
|
it(
|
|
'does not append `create-` to an unscoped package that starts with `create-`',
|
|
async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['create-some-app'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-some-app'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['create-'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-'], undefined)
|
|
}
|
|
)
|
|
|
|
it(
|
|
'appends `create-` to a scoped package that doesn\'t start with `create-`',
|
|
async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/some-app'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-some-app'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/create_no_dash'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-create_no_dash'], undefined)
|
|
}
|
|
)
|
|
|
|
it(
|
|
'does not append `create-` to a scoped package that starts with `create-`',
|
|
async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/create-some-app'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-some-app'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/create-'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-'], undefined)
|
|
}
|
|
)
|
|
|
|
it('infers a package name from a plain scope', async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create'], undefined)
|
|
})
|
|
|
|
it('passes the remaining arguments to `dlx`', async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['some-app', 'directory/', '--silent'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-some-app', 'directory/', '--silent'], undefined)
|
|
})
|
|
|
|
it(
|
|
'appends `create` to package with preferred version`',
|
|
async () => {
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['foo@2.0.0'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-foo@2.0.0'], undefined)
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['foo@latest'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['create-foo@latest'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope@2.0.0'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create@2.0.0'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope@next'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create@next'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/foo@2.0.0'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-foo@2.0.0'], undefined)
|
|
|
|
await create.handler({
|
|
...DEFAULT_OPTS,
|
|
dir: process.cwd(),
|
|
}, ['@scope/create-a@2.0.0'])
|
|
expect(dlx.handler).toHaveBeenCalledWith(expect.anything(), ['@scope/create-a@2.0.0'], undefined)
|
|
}
|
|
)
|