Skip to content

Commit 43139bc

Browse files
committed
fix(dlx): smart binary detection for packages with mismatched names
Fixes issue where dlxPackage couldn't find binaries when package name didn't match binary name (e.g., @socketsecurity/cli with bin: { socket }). Changes: - Auto-detects single binaries regardless of name - Added optional binaryName parameter for multi-binary packages - Improved fallback logic for binary name resolution Resolves socket-cli bootstrap issues with @socketsecurity/cli package.
1 parent 4b3b02f commit 43139bc

2 files changed

Lines changed: 68 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.9.1](https://github.com/SocketDev/socket-lib/releases/tag/v2.9.1) - 2025-10-30
9+
10+
### Added
11+
12+
- **Smart binary detection in dlxPackage**: Automatically finds the correct binary even when package name doesn't match binary name
13+
- If package has single binary, uses it automatically regardless of name
14+
- Resolves packages like `@socketsecurity/cli` (binary: `socket`) without manual configuration
15+
- Falls back to intelligent name matching for multi-binary packages
16+
- **Optional binaryName parameter**: Added `binaryName` option to `DlxPackageOptions` for explicit binary selection when auto-detection isn't sufficient
17+
18+
### Fixed
19+
20+
- **Binary resolution for scoped packages**: Fixed issue where `dlxPackage` couldn't find binaries when package name didn't match binary name (e.g., `@socketsecurity/cli` with `bin: { socket: '...' }`)
21+
822
## [2.9.0](https://github.com/SocketDev/socket-lib/releases/tag/v2.9.0) - 2025-10-30
923

1024
### Added

src/dlx-package.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ export interface DlxPackageOptions {
6464
* Package to install (e.g., '@cyclonedx/cdxgen@10.0.0').
6565
*/
6666
package: string
67+
/**
68+
* Binary name to execute (optional - auto-detected in most cases).
69+
*
70+
* Auto-detection logic:
71+
* 1. If package has only one binary, uses it automatically
72+
* 2. Tries user-provided binaryName
73+
* 3. Tries last segment of package name (e.g., 'cli' from '@socketsecurity/cli')
74+
* 4. Falls back to first binary
75+
*
76+
* Only needed when package has multiple binaries and auto-detection fails.
77+
*
78+
* @example
79+
* // Auto-detected (single binary)
80+
* { package: '@socketsecurity/cli' } // Finds 'socket' binary automatically
81+
*
82+
* // Explicit (multiple binaries)
83+
* { package: 'some-tool', binaryName: 'specific-tool' }
84+
*/
85+
binaryName?: string | undefined
6786
/**
6887
* Force reinstallation even if package exists.
6988
*/
@@ -231,6 +250,7 @@ async function ensurePackageInstalled(
231250

232251
/**
233252
* Find the binary path for an installed package.
253+
* Intelligently handles packages with single or multiple binaries.
234254
*/
235255
function findBinaryPath(
236256
packageDir: string,
@@ -249,12 +269,40 @@ function findBinaryPath(
249269
let binPath: string | undefined
250270

251271
if (typeof bin === 'string') {
252-
// Single binary.
272+
// Single binary - use it directly.
253273
binPath = bin
254274
} else if (typeof bin === 'object' && bin !== null) {
255-
// Multiple binaries - use binaryName or package name.
256-
const binName = binaryName || packageName.split('/').pop()
257-
binPath = (bin as Record<string, string>)[binName!]
275+
const binObj = bin as Record<string, string>
276+
const binKeys = Object.keys(binObj)
277+
278+
// If only one binary, use it regardless of name.
279+
if (binKeys.length === 1) {
280+
binPath = binObj[binKeys[0]!]
281+
} else {
282+
// Multiple binaries - try to find the right one:
283+
// 1. User-provided binaryName
284+
// 2. Last segment of package name (e.g., 'cli' from '@socketsecurity/cli')
285+
// 3. Full package name without scope (e.g., 'cli' from '@socketsecurity/cli')
286+
// 4. First binary as fallback
287+
const lastSegment = packageName.split('/').pop()
288+
const candidates = [
289+
binaryName,
290+
lastSegment,
291+
packageName.replace(/^@[^/]+\//, ''),
292+
].filter(Boolean)
293+
294+
for (const candidate of candidates) {
295+
if (candidate && binObj[candidate]) {
296+
binPath = binObj[candidate]
297+
break
298+
}
299+
}
300+
301+
// Fallback to first binary if nothing matched.
302+
if (!binPath && binKeys.length > 0) {
303+
binPath = binObj[binKeys[0]!]
304+
}
305+
}
258306
}
259307

260308
if (!binPath) {
@@ -323,7 +371,7 @@ export async function dlxPackage(
323371
export async function downloadPackage(
324372
options: DlxPackageOptions,
325373
): Promise<DownloadPackageResult> {
326-
const { force: userForce, package: packageSpec } = {
374+
const { binaryName, force: userForce, package: packageSpec } = {
327375
__proto__: null,
328376
...options,
329377
} as DlxPackageOptions
@@ -351,7 +399,7 @@ export async function downloadPackage(
351399
)
352400

353401
// Find binary path.
354-
const binaryPath = findBinaryPath(packageDir, packageName)
402+
const binaryPath = findBinaryPath(packageDir, packageName, binaryName)
355403

356404
// Make binary executable on Unix systems.
357405
if (!WIN32 && existsSync(binaryPath)) {

0 commit comments

Comments
 (0)