Skip to content

Commit b7ccf9d

Browse files
committed
more robustly handle npm version changes
1 parent 4f62d63 commit b7ccf9d

File tree

4 files changed

+68
-39
lines changed

4 files changed

+68
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818

1919
# Library specific ones
2020
!/.vscode/extensions.json
21+
/test/socket-npm-fixtures/**/node_modules/

lib/shadow/npm-injection.cjs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,29 +254,44 @@ const ttyServerPromise = chalkPromise.then(async (chalk) => {
254254
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
255255
/**
256256
* @param {string} filepath
257-
* @returns {string}
257+
* @returns {string | null}
258258
*/
259259
function findRoot (filepath) {
260260
if (path.basename(filepath) === 'npm') {
261261
return filepath
262262
}
263263
const parent = path.dirname(filepath)
264264
if (parent === filepath) {
265-
process.exit(127)
265+
return null
266266
}
267267
return findRoot(parent)
268268
}
269269
const npmDir = findRoot(path.dirname(npmEntrypoint))
270-
const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
270+
if (npmDir === null) {
271+
console.error('Unable to find npm cli install directory, this is potentiall a bug with socket-npm caused by changes to npm cli.')
272+
console.error(`Searched parent directories of ${npmEntrypoint}`)
273+
process.exit(127)
274+
}
275+
let arboristLibClassPath
276+
try {
277+
arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
278+
} catch (e) {
279+
console.error('Unable to integrate with npm cli internals, this is potentially a bug with socket-npm caused by changes to npm cli.')
280+
process.exit(127);
281+
}
271282

272-
const npmVersion = process.env.NPM_VERSION.split('.')
273283
let npmlog
274284

275-
if(npmVersion[0] === '10' && npmVersion[1] >= '6'){
276-
const { log } = require(path.join(npmDir, 'node_modules', 'proc-log', 'lib', 'index.js'))
277-
npmlog = log
278-
} else {
285+
try {
279286
npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
287+
} catch {
288+
try {
289+
const { log } = require(path.join(npmDir, 'node_modules', 'proc-log', 'lib', 'index.js'))
290+
npmlog = log
291+
} catch {
292+
console.error('Unable to integrate with npm cli logging infrastructure, this is potentially a bug with socket-npm caused by changes to npm cli.')
293+
process.exit(127);
294+
}
280295
}
281296

282297
/**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
},
104104

105105
"scripts": {
106-
"check:dependency-check": "dependency-check '*.js' 'lib/shadow/*.cjs' '*.mjs' 'test/**/*.js' --no-dev --ignore-module node:* --ignore-module @cyclonedx/* --ignore-module synp",
106+
"check:dependency-check": "dependency-check '*.js' 'lib/shadow/*.cjs' '*.mjs' 'test/*.js' --no-dev --ignore-module node:* --ignore-module @cyclonedx/* --ignore-module synp",
107107
"check:installed-check": "installed-check -i eslint-plugin-jsdoc",
108108
"check:lint": "eslint --report-unused-disable-directives .",
109109
"check:tsc": "tsc",

test/socket-npm.test.js

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,52 @@
11
import assert from 'node:assert/strict'
22
import { spawnSync } from 'node:child_process'
3+
import path from 'node:path'
34
import { describe, it } from 'node:test'
45
import { fileURLToPath } from 'node:url'
56

6-
const entryPath = fileURLToPath(new URL('../cli.js', import.meta.url))
7+
// these aliases are defined in package.json
8+
const npms = ['npm8', 'npm10']
79

8-
/**
9-
* Run relative to current file
10-
* @param {object} param0
11-
* @param {string} param0.cwd
12-
* @param {string[]} [param0.args]
13-
* @param {import('node:child_process').ProcessEnvOptions['env'] | undefined} [param0.env]
14-
* @returns {import('node:child_process').SpawnSyncReturns<string>}
15-
*/
16-
function spawnNPM ({ cwd, args = [], env }) {
17-
const pwd = fileURLToPath(new URL(cwd, import.meta.url))
18-
return spawnSync(process.execPath, [entryPath, 'npm', ...args], {
19-
cwd: pwd,
20-
encoding: 'utf-8',
21-
env: {
22-
...(env ?? process.env),
23-
// make sure we don't borrow TTY from parent
24-
SOCKET_SECURITY_TTY_IPC: undefined
25-
},
26-
stdio: ['pipe', 'pipe', 'pipe']
27-
})
28-
}
10+
const cli = fileURLToPath(new URL('../cli.js', import.meta.url))
2911

30-
describe('Socket npm wrapper', () => {
31-
it('should bail on new typosquat', () => {
32-
const ret = spawnNPM({
33-
cwd: './socket-npm-fixtures/lacking-typosquat',
34-
args: ['i', 'bowserify']
12+
for (const npm of npms) {
13+
const installDir = fileURLToPath(new URL(`./socket-npm-fixtures/${npm}`, import.meta.url))
14+
spawnSync('npm', ['install'], {
15+
cwd: installDir,
16+
stdio: 'inherit'
17+
})
18+
console.error(process.execPath)
19+
describe(`Socket npm wrapper for ${npm}`, () => {
20+
/**
21+
* Run relative to current file
22+
* @param {object} param0
23+
* @param {string} param0.cwd
24+
* @param {string[]} [param0.args]
25+
* @param {import('node:child_process').ProcessEnvOptions['env'] | undefined} [param0.env]
26+
* @returns {import('node:child_process').SpawnSyncReturns<string>}
27+
*/
28+
function spawnNPM ({ cwd, args = [], env }) {
29+
const pwd = fileURLToPath(new URL(cwd, import.meta.url))
30+
return spawnSync(process.execPath, [cli, 'npm', ...args], {
31+
cwd: pwd,
32+
encoding: 'utf-8',
33+
env: {
34+
...(env ?? process.env),
35+
// make sure we don't borrow TTY from parent
36+
SOCKET_SECURITY_TTY_IPC: undefined,
37+
// @ts-ignore
38+
PATH: `${path.join(installDir, 'node_modules', '.bin')}:${process.env.PATH}`
39+
},
40+
stdio: ['pipe', 'pipe', 'pipe']
41+
})
42+
}
43+
it('should bail on new typosquat', () => {
44+
const ret = spawnNPM({
45+
cwd: fileURLToPath(new URL('./socket-npm-fixtures/lacking-typosquat', import.meta.url)),
46+
args: ['i', 'bowserify']
47+
})
48+
assert.equal(ret.status, 1)
49+
assert.match(ret.stderr, /Unable to prompt/)
3550
})
36-
assert.equal(ret.status, 1)
37-
assert.equal(/Unable to prompt/.test(ret.stderr), true)
3851
})
39-
})
52+
}

0 commit comments

Comments
 (0)