Skip to content

Commit 2155bc1

Browse files
committed
Support bespoke npm manager
1 parent a94e63f commit 2155bc1

4 files changed

Lines changed: 29 additions & 22 deletions

File tree

src/utils/fs.mts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { promises as fs, readFileSync, statSync } from 'node:fs'
1+
import { existsSync, promises as fs, readFileSync, statSync } from 'node:fs'
22
import path from 'node:path'
33

44
import { remove } from '@socketsecurity/registry/lib/fs'
@@ -65,6 +65,10 @@ export async function findUp(
6565
return undefined
6666
}
6767

68+
export function isDirectorySync(filepath: string): boolean {
69+
return existsSync(filepath) && !!safeStatsSync(filepath)?.isDirectory()
70+
}
71+
6872
export type ReadFileOptions = Remap<
6973
ObjectEncodingOptions &
7074
Abortable & {

src/utils/npm-config.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
// @ts-ignore: TypeScript types unavailable.
77
} from '@npmcli/config/lib/definitions'
88

9-
import { getNpmPath } from './npm-paths.mts'
9+
import { getNpmDirPath } from './npm-paths.mts'
1010

1111
import type { ArboristOptions } from '../shadow/npm/arborist/types.mts'
1212
import type { SemVer } from 'semver'
@@ -31,7 +31,7 @@ export async function getNpmConfig(
3131
execPath = process.execPath,
3232
nodeVersion = process.version,
3333
npmCommand = 'install',
34-
npmPath = getNpmPath(),
34+
npmPath = getNpmDirPath(),
3535
npmVersion,
3636
platform = process.platform,
3737
} = { __proto__: null, ...options } as NpmConfigOptions

src/utils/npm-paths.mts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from 'node:path'
55
import { logger } from '@socketsecurity/registry/lib/logger'
66

77
import constants from '../constants.mts'
8-
import { findBinPathDetailsSync, findNpmPathSync } from './path-resolve.mts'
8+
import { findBinPathDetailsSync, findNpmDirPathSync } from './path-resolve.mts'
99

1010
const { NODE_MODULES, NPM, NPX, SOCKET_CLI_ISSUES_URL } = constants
1111

@@ -38,12 +38,16 @@ function getNpmBinPathDetails(): ReturnType<typeof findBinPathDetailsSync> {
3838
return _npmBinPathDetails
3939
}
4040

41-
let _npmPath: string | undefined
42-
export function getNpmPath() {
43-
if (_npmPath === undefined) {
41+
let _npmDirPath: string | undefined
42+
export function getNpmDirPath() {
43+
if (_npmDirPath === undefined) {
4444
const npmBinPath = getNpmBinPath()
45-
_npmPath = npmBinPath ? findNpmPathSync(npmBinPath) : undefined
46-
if (!_npmPath) {
45+
_npmDirPath = npmBinPath ? findNpmDirPathSync(npmBinPath) : undefined
46+
if (!_npmDirPath) {
47+
// Lazily access constants.ENV.SOCKET_CLI_NPM_PATH.
48+
_npmDirPath = constants.ENV.SOCKET_CLI_NPM_PATH || undefined
49+
}
50+
if (!_npmDirPath) {
4751
let message = 'Unable to find npm CLI install directory.'
4852
if (npmBinPath) {
4953
message += `\nSearched parent directories of ${path.dirname(npmBinPath)}.`
@@ -56,17 +60,17 @@ export function getNpmPath() {
5660
process.exit(127)
5761
}
5862
}
59-
return _npmPath
63+
return _npmDirPath
6064
}
6165

6266
let _npmRequire: NodeJS.Require | undefined
6367
export function getNpmRequire(): NodeJS.Require {
6468
if (_npmRequire === undefined) {
65-
const npmPath = getNpmPath()
66-
const npmNmPath = path.join(npmPath, NODE_MODULES, NPM)
69+
const npmDirPath = getNpmDirPath()
70+
const npmNmPath = path.join(npmDirPath, NODE_MODULES, NPM)
6771
_npmRequire = Module.createRequire(
6872
path.join(
69-
existsSync(npmNmPath) ? npmNmPath : npmPath,
73+
existsSync(npmNmPath) ? npmNmPath : npmDirPath,
7074
'<dummy-basename>',
7175
),
7276
)

src/utils/path-resolve.mts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import which from 'which'
66
import { resolveBinPathSync } from '@socketsecurity/registry/lib/npm'
77

88
import constants from '../constants.mts'
9-
import { safeStatsSync } from './fs.mts'
9+
import { isDirectorySync, safeStatsSync } from './fs.mts'
1010
import {
1111
filterBySupportedScanFiles,
1212
globWithGitIgnore,
@@ -43,26 +43,24 @@ export function findBinPathDetailsSync(binName: string): {
4343
return { name: binName, path: theBinPath, shadowed: shadowIndex !== -1 }
4444
}
4545

46-
export function findNpmPathSync(npmBinPath: string): string | undefined {
46+
export function findNpmDirPathSync(npmBinPath: string): string | undefined {
4747
// Lazily access constants.WIN32.
4848
const { WIN32 } = constants
4949
let thePath = npmBinPath
5050
while (true) {
5151
const libNmNpmPath = path.join(thePath, 'lib/node_modules/npm')
52-
// mise puts its npm bin in a path like:
52+
// mise, which uses opaque binaries, puts its npm bin in a path like:
5353
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/bin/npm.
5454
// HOWEVER, the location of the npm install is:
5555
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/lib/node_modules/npm.
5656
if (
5757
// Use existsSync here because statsSync, even with { throwIfNoEntry: false },
5858
// will throw an ENOTDIR error for paths like ./a-file-that-exists/a-directory-that-does-not.
5959
// See https://github.com/nodejs/node/issues/56993.
60-
existsSync(libNmNpmPath) &&
61-
safeStatsSync(libNmNpmPath)?.isDirectory()
60+
isDirectorySync(libNmNpmPath)
6261
) {
63-
thePath = path.join(libNmNpmPath, 'npm')
62+
thePath = libNmNpmPath
6463
}
65-
const nmPath = path.join(thePath, 'node_modules')
6664
if (
6765
// npm bin paths may look like:
6866
// /usr/local/share/npm/bin/npm
@@ -74,8 +72,9 @@ export function findNpmPathSync(npmBinPath: string): string | undefined {
7472
// In practically all cases the npm path contains a node_modules folder:
7573
// /usr/local/share/npm/bin/npm/node_modules
7674
// C:\Program Files\nodejs\node_modules
77-
existsSync(nmPath) &&
78-
safeStatsSync(nmPath)?.isDirectory() &&
75+
(isDirectorySync(path.join(thePath, 'node_modules')) ||
76+
// In some bespoke cases the node_modules folder is one level back.
77+
isDirectorySync(path.join(thePath, '../node_modules'))) &&
7978
// Optimistically look for the default location.
8079
(path.basename(thePath) === 'npm' ||
8180
// Chocolatey installs npm bins in the same directory as node bins.

0 commit comments

Comments
 (0)