Skip to content

Commit 4456526

Browse files
committed
Add and fix bin methods
1 parent e867c6e commit 4456526

8 files changed

Lines changed: 256 additions & 78 deletions

File tree

registry/lib/bin.d.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,31 @@ declare const Bin: {
77
args: string[] | readonly string[],
88
options?: SpawnOptions | undefined
99
): Promise<{ stdout: string; stderr: string }>
10+
findRealBin(binName: string, commonPaths?: string[]): string
11+
findRealNpm(): string
12+
findRealPnpm(): string
13+
findRealYarn(): string
14+
isShadowBinPath(dirPath: string): boolean
1015
resolveBinPathSync(binPath: string): string
1116
whichBin<T extends WhichOptions>(
1217
binName: string,
1318
options: T
14-
): T extends { nothrow: true } ? Promise<string | null> : Promise<string>
19+
): T extends { all: true; nothrow: true }
20+
? Promise<string[] | null>
21+
: T extends { all: true }
22+
? Promise<string[]>
23+
: T extends { nothrow: true }
24+
? Promise<string | null>
25+
: Promise<string>
1526
whichBinSync<T extends WhichOptions>(
1627
binName: string,
1728
options: T
18-
): T extends { nothrow: true } ? string | null : string
29+
): T extends { all: true; nothrow: true }
30+
? string[] | null
31+
: T extends { all: true }
32+
? string[]
33+
: T extends { nothrow: true }
34+
? string | null
35+
: string
1936
}
2037
export = Bin

registry/lib/bin.js

Lines changed: 213 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const { readJsonSync } = /*@__PURE__*/ require('./fs')
4+
const { getOwn } = /*@__PURE__*/ require('./objects')
45
const { isPath, normalizePath } = /*@__PURE__*/ require('./path')
56
const { spawn } = /*@__PURE__*/ require('./spawn')
67

@@ -66,6 +67,213 @@ function getNotResolvedError(binPath, source = '') {
6667
return error
6768
}
6869

70+
/*@__NO_SIDE_EFFECTS__*/
71+
/**
72+
* Execute a binary with the given arguments.
73+
* @param {string} binPath - Path or name of the binary to execute.
74+
* @param {string[] | readonly string[]} args - Arguments to pass to the binary.
75+
* @param {import('./spawn').SpawnOptions} [options] - Spawn options.
76+
* @returns {Promise<{ stdout: string; stderr: string }>} Command output.
77+
*/
78+
function execBin(binPath, args, options) {
79+
return spawn(
80+
/*@__PURE__*/ require('./constants/exec-path'),
81+
[
82+
.../*@__PURE__*/ require('./constants/node-no-warnings-flags'),
83+
isPath(binPath) ? resolveBinPathSync(binPath) : whichBinSync(binPath),
84+
...args
85+
],
86+
options
87+
)
88+
}
89+
90+
/**
91+
* Find and resolve a binary in the system PATH asynchronously.
92+
* @template {import('which').Options} T
93+
* @param {string} binName - Name of the binary to find.
94+
* @param {T} options - Options for the which module.
95+
* @returns {T extends {all: true, nothrow: true} ? Promise<string[] | null> : T extends {all: true} ? Promise<string[]> : T extends {nothrow: true} ? Promise<string | null> : Promise<string>} The resolved binary path(s).
96+
* @throws {Error} If the binary is not found and nothrow is false.
97+
*/
98+
async function whichBin(binName, options) {
99+
const which = getWhich()
100+
// Depending on options `which` may throw if `binName` is not found.
101+
// The default behavior is to throw when `binName` is not found.
102+
const result = await which(binName, options)
103+
104+
// When 'all: true' is specified, ensure we always return an array.
105+
if (options?.all) {
106+
const paths = Array.isArray(result)
107+
? result
108+
: result != null
109+
? [result]
110+
: result
111+
// If all is true and we have paths, resolve each one.
112+
if (paths && paths.length > 0) {
113+
return paths.map(p => resolveBinPathSync(p))
114+
}
115+
return paths
116+
}
117+
118+
return resolveBinPathSync(result)
119+
}
120+
121+
/**
122+
* Find and resolve a binary in the system PATH synchronously.
123+
* @template {import('which').Options} T
124+
* @param {string} binName - Name of the binary to find.
125+
* @param {T} options - Options for the which module.
126+
* @returns {T extends {all: true, nothrow: true} ? string[] | null : T extends {all: true} ? string[] : T extends {nothrow: true} ? string | null : string} The resolved binary path(s).
127+
* @throws {Error} If the binary is not found and nothrow is false.
128+
*/
129+
function whichBinSync(binName, options) {
130+
// Depending on options `which` may throw if `binName` is not found.
131+
// The default behavior is to throw when `binName` is not found.
132+
const result = getWhich().sync(binName, options)
133+
134+
// When 'all: true' is specified, ensure we always return an array.
135+
if (getOwn(options, 'all')) {
136+
const paths = Array.isArray(result)
137+
? result
138+
: typeof result === 'string'
139+
? [result]
140+
: result
141+
// If all is true and we have paths, resolve each one.
142+
if (paths && paths.length) {
143+
return paths.map(p => resolveBinPathSync(p))
144+
}
145+
return paths
146+
}
147+
148+
return resolveBinPathSync(result)
149+
}
150+
151+
/**
152+
* Check if a directory path contains any shadow bin patterns.
153+
* @param {string} dirPath - Directory path to check.
154+
* @returns {boolean} True if the path contains shadow bin patterns.
155+
*/
156+
function isShadowBinPath(dirPath) {
157+
return (
158+
dirPath.includes('shadow-bin') ||
159+
dirPath.includes('shadow-npm-bin') ||
160+
dirPath.includes('shadow-pnpm-bin') ||
161+
dirPath.includes('shadow-yarn-bin')
162+
)
163+
}
164+
165+
/**
166+
* Find the real executable for a binary, bypassing shadow bins.
167+
* @param {string} binName - Name of the binary to find.
168+
* @param {string[]} commonPaths - Common paths to check first.
169+
* @returns {string} The path to the real binary.
170+
*/
171+
function findRealBin(binName, commonPaths = []) {
172+
const fs = getFs()
173+
const path = getPath()
174+
const which = getWhich()
175+
176+
// Try common locations first.
177+
for (const binPath of commonPaths) {
178+
if (fs.existsSync(binPath)) {
179+
return binPath
180+
}
181+
}
182+
183+
// Fall back to which.sync if no direct path found.
184+
try {
185+
const binPath = which.sync(binName, { nothrow: true })
186+
if (binPath) {
187+
const binDir = path.dirname(binPath)
188+
189+
if (isShadowBinPath(binDir)) {
190+
// This is likely a shadowed binary, try to find the real one.
191+
const allPaths = which.sync(binName, { all: true, nothrow: true }) || []
192+
// Ensure allPaths is an array.
193+
const pathsArray = Array.isArray(allPaths)
194+
? allPaths
195+
: typeof allPaths === 'string'
196+
? [allPaths]
197+
: []
198+
199+
for (const altPath of pathsArray) {
200+
const altDir = path.dirname(altPath)
201+
if (!isShadowBinPath(altDir)) {
202+
return altPath
203+
}
204+
}
205+
}
206+
return binPath
207+
}
208+
} catch {
209+
// Ignore errors.
210+
}
211+
212+
// If all else fails, return the binary name and let the system resolve it.
213+
return binName
214+
}
215+
216+
/**
217+
* Find the real npm executable, bypassing any aliases and shadow bins.
218+
* @returns {string} The path to the real npm binary.
219+
*/
220+
function findRealNpm() {
221+
const fs = getFs()
222+
const path = getPath()
223+
224+
// Try to find npm in the same directory as the node executable.
225+
const nodeDir = path.dirname(process.execPath)
226+
const npmInNodeDir = path.join(nodeDir, 'npm')
227+
228+
if (fs.existsSync(npmInNodeDir)) {
229+
return npmInNodeDir
230+
}
231+
232+
// Try common npm locations.
233+
const commonPaths = ['/usr/local/bin/npm', '/usr/bin/npm']
234+
235+
return findRealBin('npm', commonPaths)
236+
}
237+
238+
/**
239+
* Find the real pnpm executable, bypassing any aliases and shadow bins.
240+
* @returns {string} The path to the real pnpm binary.
241+
*/
242+
function findRealPnpm() {
243+
const path = getPath()
244+
245+
// Try common pnpm locations.
246+
const commonPaths = [
247+
'/usr/local/bin/pnpm',
248+
'/usr/bin/pnpm',
249+
path.join(process.env.HOME || '', '.local/share/pnpm/pnpm'),
250+
path.join(process.env.HOME || '', '.pnpm/pnpm')
251+
].filter(Boolean)
252+
253+
return findRealBin('pnpm', commonPaths)
254+
}
255+
256+
/**
257+
* Find the real yarn executable, bypassing any aliases and shadow bins.
258+
* @returns {string} The path to the real yarn binary.
259+
*/
260+
function findRealYarn() {
261+
const path = getPath()
262+
263+
// Try common yarn locations.
264+
const commonPaths = [
265+
'/usr/local/bin/yarn',
266+
'/usr/bin/yarn',
267+
path.join(process.env.HOME || '', '.yarn/bin/yarn'),
268+
path.join(
269+
process.env.HOME || '',
270+
'.config/yarn/global/node_modules/.bin/yarn'
271+
)
272+
].filter(Boolean)
273+
274+
return findRealBin('yarn', commonPaths)
275+
}
276+
69277
/*@__NO_SIDE_EFFECTS__*/
70278
/**
71279
* Resolve a binary path to its actual executable file.
@@ -415,57 +623,13 @@ function resolveBinPathSync(binPath) {
415623
}
416624
}
417625

418-
/*@__NO_SIDE_EFFECTS__*/
419-
/**
420-
* Execute a binary with the given arguments.
421-
* @param {string} binPath - Path or name of the binary to execute.
422-
* @param {string[] | readonly string[]} args - Arguments to pass to the binary.
423-
* @param {import('./spawn').SpawnOptions} [options] - Spawn options.
424-
* @returns {Promise<{ stdout: string; stderr: string }>} Command output.
425-
*/
426-
function execBin(binPath, args, options) {
427-
return spawn(
428-
/*@__PURE__*/ require('./constants/exec-path'),
429-
[
430-
.../*@__PURE__*/ require('./constants/node-no-warnings-flags'),
431-
isPath(binPath) ? resolveBinPathSync(binPath) : whichBinSync(binPath),
432-
...args
433-
],
434-
options
435-
)
436-
}
437-
438-
/**
439-
* Find and resolve a binary in the system PATH asynchronously.
440-
* @template {import('which').Options} T
441-
* @param {string} binName - Name of the binary to find.
442-
* @param {T} options - Options for the which module.
443-
* @returns {T extends {nothrow: true} ? Promise<string | null> : Promise<string>} The resolved binary path.
444-
* @throws {Error} If the binary is not found and nothrow is false.
445-
*/
446-
async function whichBin(binName, options) {
447-
const which = getWhich()
448-
// Depending on options `which` may throw if `binName` is not found.
449-
// The default behavior is to throw when `binName` is not found.
450-
return resolveBinPathSync(await which(binName, options))
451-
}
452-
453-
/**
454-
* Find and resolve a binary in the system PATH synchronously.
455-
* @template {import('which').Options} T
456-
* @param {string} binName - Name of the binary to find.
457-
* @param {T} options - Options for the which module.
458-
* @returns {T extends {nothrow: true} ? string | null : string} The resolved binary path.
459-
* @throws {Error} If the binary is not found and nothrow is false.
460-
*/
461-
function whichBinSync(binName, options) {
462-
// Depending on options `which` may throw if `binName` is not found.
463-
// The default behavior is to throw when `binName` is not found.
464-
return resolveBinPathSync(getWhich().sync(binName, options))
465-
}
466-
467626
module.exports = {
468627
execBin,
628+
findRealBin,
629+
findRealNpm,
630+
findRealPnpm,
631+
findRealYarn,
632+
isShadowBinPath,
469633
resolveBinPathSync,
470634
whichBin,
471635
whichBinSync

registry/lib/constants/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import PACKAGE_LOCK_JSON from './package-lock-json'
6969
import packumentCache from './packument-cache'
7070
import pacoteCachePath from './pacote-cache-path'
7171
import PNPM from './pnpm'
72+
import pnpmExecPath from './pnpm-exec-path'
7273
import PNPM_LOCK_YAML from './pnpm-lock-yaml'
7374
import PRE_COMMIT from './pre-commit'
7475
import README_GLOB from './readme-glob'
@@ -109,6 +110,7 @@ import WIN32 from './win32'
109110
import YARN from './yarn'
110111
import YARN_BERRY from './yarn-berry'
111112
import YARN_CLASSIC from './yarn-classic'
113+
import yarnExecPath from './yarn-exec-path'
112114
import YARN_LOCK from './yarn-lock'
113115
import { createConstantsObject } from '../objects'
114116

@@ -234,9 +236,11 @@ declare const Constants: {
234236
readonly packageExtensions: typeof packageExtensions
235237
readonly packumentCache: typeof packumentCache
236238
readonly pacoteCachePath: typeof pacoteCachePath
239+
readonly pnpmExecPath: typeof pnpmExecPath
237240
readonly spinner: typeof spinner
238241
readonly tsLibsAvailable: typeof tsLibsAvailable
239242
readonly tsTypesAvailable: typeof tsTypesAvailable
243+
readonly yarnExecPath: typeof yarnExecPath
240244
}
241245
declare namespace Constants {
242246
export { Internals }

registry/lib/constants/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,13 @@ const props = {
112112
npmExecPath: undefined,
113113
npmRealExecPath: undefined,
114114
packageExtensions: undefined,
115+
pnpmExecPath: undefined,
115116
packumentCache: undefined,
116117
pacoteCachePath: undefined,
117118
spinner: undefined,
118119
tsLibsAvailable: undefined,
119-
tsTypesAvailable: undefined
120+
tsTypesAvailable: undefined,
121+
yarnExecPath: undefined
120122
}
121123

122124
module.exports = createConstantsObject(props, {
Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,5 @@
11
'use strict'
22

3-
const which = /*@__PURE__*/ require('../../external/which')
4-
const fs = /*@__PURE__*/ require('node:fs')
5-
const path = /*@__PURE__*/ require('node:path')
6-
7-
// Try to find the real npm executable, bypassing any aliases
8-
function findRealNpm() {
9-
// Try to find npm in the same directory as the node executable
10-
const nodeDir = path.dirname(process.execPath)
11-
const npmInNodeDir = path.join(nodeDir, 'npm')
12-
13-
if (fs.existsSync(npmInNodeDir)) {
14-
return npmInNodeDir
15-
}
16-
17-
// Try common npm locations
18-
const commonPaths = ['/usr/local/bin/npm', '/usr/bin/npm']
19-
20-
for (const npmPath of commonPaths) {
21-
if (fs.existsSync(npmPath)) {
22-
return npmPath
23-
}
24-
}
25-
26-
// Fall back to which.sync if no direct path found
27-
return which.sync('npm')
28-
}
3+
const { findRealNpm } = /*@__PURE__*/ require('../bin')
294

305
module.exports = findRealNpm()

0 commit comments

Comments
 (0)