Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 8 additions & 34 deletions src/utils/dlx.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
* Package Manager Detection:
* - Auto-detects npm, pnpm, or yarn based on lockfiles
* - Supports force-refresh and silent execution modes
* - Supports silent execution mode
*
* Integration:
* - Works with shadow binaries for security scanning
Expand Down Expand Up @@ -39,7 +39,6 @@ const require = createRequire(import.meta.url)
const { PACKAGE_LOCK_JSON, PNPM_LOCK_YAML, YARN_LOCK } = constants

export type DlxOptions = ShadowBinOptions & {
force?: boolean | undefined
agent?: 'npm' | 'pnpm' | 'yarn' | undefined
silent?: boolean | undefined
}
Expand All @@ -49,32 +48,17 @@ export type DlxPackageSpec = {
version: string
}

/**
* Regex to check if a version string contains range operators.
* Matches any version with range operators: ~, ^, >, <, =, x, X, *, spaces, or ||.
*/
const rangeOperatorsRegExp = /[~^><=xX* ]|\|\|/

/**
* Spawns a package using dlx-style execution (npx/pnpm dlx/yarn dlx).
* Automatically detects the appropriate package manager if not specified.
* Uses force/update flags to ensure the latest version within the range is fetched.
*/
export async function spawnDlx(
packageSpec: DlxPackageSpec,
args: string[] | readonly string[],
options?: DlxOptions | undefined,
spawnExtra?: SpawnExtra | undefined,
): Promise<ShadowBinResult> {
// If version is not pinned exactly, default to force and silent for better UX.
const isNotPinned = rangeOperatorsRegExp.test(packageSpec.version)

const {
agent,
force = false,
silent = isNotPinned,
...shadowOptions
} = options ?? {}
const { agent, silent = false, ...shadowOptions } = options ?? {}

let finalShadowOptions = shadowOptions

Expand Down Expand Up @@ -123,18 +107,12 @@ export async function spawnDlx(
// store, so the project's install hooks are irrelevant to it. pnpm honors
// this only as a config setting, not as a `dlx` CLI flag, so it must be set
// via the npm_config_ env var. See: https://pnpm.io/npmrc#settings
const pnpmEnv: Record<string, string | undefined> = {
...getOwn(finalShadowOptions, 'env'),
npm_config_ignore_pnpmfile: 'true',
}
if (force) {
// Set dlx-cache-max-age to 0 minutes to bypass cache and force a fresh
// download. This ensures we always get the latest version within the range.
pnpmEnv['npm_config_dlx_cache_max_age'] = '0'
}
finalShadowOptions = {
...finalShadowOptions,
env: pnpmEnv,
env: {
...getOwn(finalShadowOptions, 'env'),
npm_config_ignore_pnpmfile: 'true',
},
}
spawnArgs.push(packageString, ...args)

Expand Down Expand Up @@ -164,10 +142,6 @@ export async function spawnDlx(
// Use npm exec/npx.
// For consistency, we'll use npx which is more commonly used for one-off execution.
spawnArgs = ['--yes']
if (force) {
// Use --force to bypass cache and get latest within range.
spawnArgs.push('--force')
}
if (silent) {
spawnArgs.push(FLAG_SILENT)
}
Expand All @@ -192,7 +166,7 @@ export async function spawnCdxgenDlx(
version: constants.ENV.INLINED_SOCKET_CLI_CYCLONEDX_CDXGEN_VERSION,
},
args,
{ force: false, silent: true, ...options },
{ silent: true, ...options },
spawnExtra,
)
}
Expand All @@ -211,7 +185,7 @@ export async function spawnSynpDlx(
version: `${constants.ENV.INLINED_SOCKET_CLI_SYNP_VERSION}`,
},
args,
{ force: false, silent: true, ...options },
{ silent: true, ...options },
spawnExtra,
)
}
41 changes: 2 additions & 39 deletions src/utils/dlx.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,6 @@ describe('utils/dlx', () => {
expect(spawnArgs[3]).toBe('/some/path')
})

it('should default silent to true for pnpm when version is not pinned', async () => {
const packageSpec: DlxPackageSpec = {
name: '@coana-tech/cli',
version: '~1.0.0',
}

await spawnDlx(packageSpec, ['run', '/some/path'], { agent: 'pnpm' })

expect(mockShadowPnpmBin).toHaveBeenCalledTimes(1)
const [spawnArgs] = mockShadowPnpmBin.mock.calls[0]

// Verify that --silent is automatically added for unpinned versions.
expect(spawnArgs[0]).toBe('--silent')
expect(spawnArgs[1]).toBe('dlx')
})

it('should place --silent after --yes for npm', async () => {
const packageSpec: DlxPackageSpec = {
name: '@coana-tech/cli',
Expand All @@ -144,32 +128,12 @@ describe('utils/dlx', () => {
expect(spawnArgs[4]).toBe('/some/path')
})

it('should set npm_config_dlx_cache_max_age env var for pnpm when force is true', async () => {
const packageSpec: DlxPackageSpec = {
name: '@coana-tech/cli',
version: '1.0.0',
}

await spawnDlx(packageSpec, ['run', '/some/path'], {
agent: 'pnpm',
force: true,
})

expect(mockShadowPnpmBin).toHaveBeenCalledTimes(1)
const [, options] = mockShadowPnpmBin.mock.calls[0]

// Verify that the env var is set to force cache bypass.
expect(options.env).toBeDefined()
expect(options.env.npm_config_dlx_cache_max_age).toBe('0')
})

it('should set npm_config_ignore_pnpmfile env var for pnpm regardless of force', async () => {
it('should set npm_config_ignore_pnpmfile env var for pnpm', async () => {
const packageSpec: DlxPackageSpec = {
name: '@coana-tech/cli',
version: '1.0.0',
}

// force defaults to false here, exercising the non-force path.
await spawnDlx(packageSpec, ['run', '/some/path'], { agent: 'pnpm' })

expect(mockShadowPnpmBin).toHaveBeenCalledTimes(1)
Expand All @@ -182,7 +146,7 @@ describe('utils/dlx', () => {
expect(options.env.npm_config_ignore_pnpmfile).toBe('true')
})

it('should handle pinned version without silent flag by default', async () => {
it('should not add --silent by default', async () => {
const packageSpec: DlxPackageSpec = {
name: '@coana-tech/cli',
version: '1.0.0',
Expand All @@ -193,7 +157,6 @@ describe('utils/dlx', () => {
expect(mockShadowPnpmBin).toHaveBeenCalledTimes(1)
const [spawnArgs] = mockShadowPnpmBin.mock.calls[0]

// For pinned versions, silent defaults to false.
expect(spawnArgs[0]).toBe('dlx')
expect(spawnArgs[1]).toBe('@coana-tech/cli@1.0.0')
})
Expand Down
Loading