Skip to content
Open
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
19 changes: 19 additions & 0 deletions bin/npm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
# This is used by the Node.js installer, which expects the cygwin/mingw
# shell script to already be present in the npm dependency folder.

# Git Bash (msys) and Cygwin: start node once. npm-shim.js picks this npm or
# the one in the global prefix in the same node process, and msys converts the
# script path for node.exe, so this needs no subshells (Cygwin: one cygpath).
case "$OSTYPE" in
msys*|cygwin*)
set -o igncr 2>/dev/null # cygwin encoding fix
basedir=${0%/*}
[ "$basedir" = "$0" ] && basedir=.
case "$OSTYPE" in cygwin*) basedir=$(cygpath -w "$basedir");; esac
NODE_EXE="$basedir/node.exe"
[ -x "$NODE_EXE" ] || NODE_EXE="$basedir/node"
[ -x "$NODE_EXE" ] || NODE_EXE=node
"$NODE_EXE" "$basedir/node_modules/npm/bin/npm-shim.js" npm "$@"
exit $?
;;
esac

# Everything else, e.g. WSL running the Windows install: find the prefix first.

(set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix

basedir=`dirname "$0"`
Expand Down
18 changes: 4 additions & 14 deletions bin/npm-prefix.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,15 @@
// This is a single-use bin to help windows discover the proper prefix for npm
// without having to load all of npm first
// It does not accept argv params
// The Windows shims now make this decision in-process via npm-shim.js; this
// is still used by bin/npm and bin/npx outside Git Bash and Cygwin (WSL).

const path = require('node:path')
const Config = require('@npmcli/config')
const { definitions, flatten, shorthands } = require('@npmcli/config/lib/definitions')
const config = new Config({
npmPath: path.dirname(__dirname),
// argv is explicitly not looked at since prefix is not something that can be changed via argv
argv: [],
definitions,
flatten,
shorthands,
excludeNpmCwd: false,
})
const globalPrefix = require('../lib/cli/global-prefix.js')

async function main () {
try {
await config.load()
// eslint-disable-next-line no-console
console.log(config.globalPrefix)
console.log(await globalPrefix())
} catch (err) {
// eslint-disable-next-line no-console
console.error(err)
Expand Down
34 changes: 34 additions & 0 deletions bin/npm-shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env node
// Entry point for the Windows shims (npm, npx, npm.cmd, npx.cmd, npm.ps1,
// npx.ps1): node npm-shim.js <npm|npx> [args...]
//
// It runs the npm installed in the global prefix if there is one, else this
// one, like the shims did with npm-prefix.js, but decides in the node process
// that then runs npm instead of starting node a second time to print the
// prefix.

const { join } = require('node:path')
const { existsSync } = require('node:fs')
const globalPrefix = require('../lib/cli/global-prefix.js')

async function main () {
const [node, , bin, ...args] = process.argv
const cliName = bin === 'npx' ? 'npx-cli.js' : 'npm-cli.js'
let prefix
try {
prefix = await globalPrefix()
} catch (err) {
// eslint-disable-next-line no-console
console.error(err)
// eslint-disable-next-line no-console
console.error('Could not determine Node.js install directory')
process.exit(1)
return
}
const fromPrefix = join(prefix, 'node_modules', 'npm', 'bin', cliName)
const cli = existsSync(fromPrefix) ? fromPrefix : join(__dirname, cliName)
// the cli sees the same argv as when it is run directly
process.argv = [node, cli, ...args]
require(cli)
}
main()
11 changes: 1 addition & 10 deletions bin/npm.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,4 @@ IF NOT EXIST "%NODE_EXE%" (
SET "NODE_EXE=node"
)

SET "NPM_PREFIX_JS=%~dp0\node_modules\npm\bin\npm-prefix.js"
SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js"
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_PREFIX_JS%"') DO (
SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js"
)
IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" (
SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%"
)

"%NODE_EXE%" "%NPM_CLI_JS%" %*
"%NODE_EXE%" "%~dp0\node_modules\npm\bin\npm-shim.js" npm %*
23 changes: 6 additions & 17 deletions bin/npm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,13 @@ if (-not (Test-Path $NODE_EXE)) {
$NODE_EXE="node"
}

$NPM_PREFIX_JS="$PSScriptRoot/node_modules/npm/bin/npm-prefix.js"
$NPM_CLI_JS="$PSScriptRoot/node_modules/npm/bin/npm-cli.js"
$NPM_PREFIX=(& $NODE_EXE $NPM_PREFIX_JS)

if ($LASTEXITCODE -ne 0) {
Write-Host "Could not determine Node.js install directory"
exit 1
}

$NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js"
if (Test-Path $NPM_PREFIX_NPM_CLI_JS) {
$NPM_CLI_JS=$NPM_PREFIX_NPM_CLI_JS
}
# npm-shim.js picks this npm or the one in the global prefix, in the same node process.
$NPM_SHIM_JS="$PSScriptRoot/node_modules/npm/bin/npm-shim.js"

if ($MyInvocation.ExpectingInput) { # takes pipeline input
$input | & $NODE_EXE $NPM_CLI_JS $args
$input | & $NODE_EXE $NPM_SHIM_JS npm $args
} elseif (-not $MyInvocation.Line) { # used "-File" argument
& $NODE_EXE $NPM_CLI_JS $args
& $NODE_EXE $NPM_SHIM_JS npm $args
} else { # used "-Command" argument
if (($MyInvocation | Get-Member -Name 'Statement') -and $MyInvocation.Statement) {
$NPM_ORIGINAL_COMMAND = $MyInvocation.Statement
Expand All @@ -38,13 +27,13 @@ if ($MyInvocation.ExpectingInput) { # takes pipeline input
}

$NODE_EXE = $NODE_EXE.Replace("``", "````")
$NPM_CLI_JS = $NPM_CLI_JS.Replace("``", "````")
$NPM_SHIM_JS = $NPM_SHIM_JS.Replace("``", "````")

$NPM_COMMAND_ARRAY = [Management.Automation.Language.Parser]::ParseInput($NPM_ORIGINAL_COMMAND, [ref] $null, [ref] $null).
EndBlock.Statements.PipelineElements.CommandElements.Extent.Text
$NPM_ARGS = ($NPM_COMMAND_ARRAY | Select-Object -Skip 1) -join ' '

Invoke-Expression "& `"$NODE_EXE`" `"$NPM_CLI_JS`" $NPM_ARGS"
Invoke-Expression "& `"$NODE_EXE`" `"$NPM_SHIM_JS`" npm $NPM_ARGS"
}

exit $LASTEXITCODE
19 changes: 19 additions & 0 deletions bin/npx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
# This is used by the Node.js installer, which expects the cygwin/mingw
# shell script to already be present in the npm dependency folder.

# Git Bash (msys) and Cygwin: start node once. npm-shim.js picks this npm or
# the one in the global prefix in the same node process, and msys converts the
# script path for node.exe, so this needs no subshells (Cygwin: one cygpath).
case "$OSTYPE" in
msys*|cygwin*)
set -o igncr 2>/dev/null # cygwin encoding fix
basedir=${0%/*}
[ "$basedir" = "$0" ] && basedir=.
case "$OSTYPE" in cygwin*) basedir=$(cygpath -w "$basedir");; esac
NODE_EXE="$basedir/node.exe"
[ -x "$NODE_EXE" ] || NODE_EXE="$basedir/node"
[ -x "$NODE_EXE" ] || NODE_EXE=node
"$NODE_EXE" "$basedir/node_modules/npm/bin/npm-shim.js" npx "$@"
exit $?
;;
esac

# Everything else, e.g. WSL running the Windows install: find the prefix first.

(set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix

basedir=`dirname "$0"`
Expand Down
11 changes: 1 addition & 10 deletions bin/npx.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,4 @@ IF NOT EXIST "%NODE_EXE%" (
SET "NODE_EXE=node"
)

SET "NPM_PREFIX_JS=%~dp0\node_modules\npm\bin\npm-prefix.js"
SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js"
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_PREFIX_JS%"') DO (
SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js"
)
IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" (
SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%"
)

"%NODE_EXE%" "%NPX_CLI_JS%" %*
"%NODE_EXE%" "%~dp0\node_modules\npm\bin\npm-shim.js" npx %*
23 changes: 6 additions & 17 deletions bin/npx.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,13 @@ if (-not (Test-Path $NODE_EXE)) {
$NODE_EXE="node"
}

$NPM_PREFIX_JS="$PSScriptRoot/node_modules/npm/bin/npm-prefix.js"
$NPX_CLI_JS="$PSScriptRoot/node_modules/npm/bin/npx-cli.js"
$NPM_PREFIX=(& $NODE_EXE $NPM_PREFIX_JS)

if ($LASTEXITCODE -ne 0) {
Write-Host "Could not determine Node.js install directory"
exit 1
}

$NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js"
if (Test-Path $NPM_PREFIX_NPX_CLI_JS) {
$NPX_CLI_JS=$NPM_PREFIX_NPX_CLI_JS
}
# npm-shim.js picks this npx or the one in the global prefix, in the same node process.
$NPX_SHIM_JS="$PSScriptRoot/node_modules/npm/bin/npm-shim.js"

if ($MyInvocation.ExpectingInput) { # takes pipeline input
$input | & $NODE_EXE $NPX_CLI_JS $args
$input | & $NODE_EXE $NPX_SHIM_JS npx $args
} elseif (-not $MyInvocation.Line) { # used "-File" argument
& $NODE_EXE $NPX_CLI_JS $args
& $NODE_EXE $NPX_SHIM_JS npx $args
} else { # used "-Command" argument
if (($MyInvocation | Get-Member -Name 'Statement') -and $MyInvocation.Statement) {
$NPX_ORIGINAL_COMMAND = $MyInvocation.Statement
Expand All @@ -38,13 +27,13 @@ if ($MyInvocation.ExpectingInput) { # takes pipeline input
}

$NODE_EXE = $NODE_EXE.Replace("``", "````")
$NPX_CLI_JS = $NPX_CLI_JS.Replace("``", "````")
$NPX_SHIM_JS = $NPX_SHIM_JS.Replace("``", "````")

$NPX_COMMAND_ARRAY = [Management.Automation.Language.Parser]::ParseInput($NPX_ORIGINAL_COMMAND, [ref] $null, [ref] $null).
EndBlock.Statements.PipelineElements.CommandElements.Extent.Text
$NPX_ARGS = ($NPX_COMMAND_ARRAY | Select-Object -Skip 1) -join ' '

Invoke-Expression "& `"$NODE_EXE`" `"$NPX_CLI_JS`" $NPX_ARGS"
Invoke-Expression "& `"$NODE_EXE`" `"$NPX_SHIM_JS`" npx $NPX_ARGS"
}

exit $LASTEXITCODE
25 changes: 25 additions & 0 deletions lib/cli/global-prefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Finds the global prefix the way npm does, without loading the rest of npm.
// The Windows shims use it (through bin/npm-shim.js and bin/npm-prefix.js) to
// run the npm installed in the global prefix, if there is one, instead of the
// npm that came with Node.js.

const { resolve } = require('node:path')
const Config = require('@npmcli/config')
const { definitions, flatten, shorthands } = require('@npmcli/config/lib/definitions')

module.exports = async () => {
const config = new Config({
npmPath: resolve(__dirname, '../..'),
// argv is explicitly not looked at since prefix is not something that can be changed via argv
argv: [],
definitions,
flatten,
shorthands,
excludeNpmCwd: false,
// a copy: load() exports npm_config_* variables to env, and npm-shim.js
// then runs npm, maybe another version of it, in this same process
env: { ...process.env },
})
await config.load()
return config.globalPrefix
}
34 changes: 34 additions & 0 deletions test/bin/npm-prefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const t = require('tap')
const mockGlobals = require('@npmcli/mock-globals')
const tmock = require('../fixtures/tmock')

const mockPrefix = async (t, globalPrefix) => {
const logs = []
const errors = []
const exits = []
mockGlobals(t, {
'console.log': (...msg) => logs.push(msg),
'console.error': (...msg) => errors.push(msg),
'process.exit': (code) => exits.push(code),
})
tmock(t, '{BIN}/npm-prefix.js', { '{LIB}/cli/global-prefix.js': globalPrefix })
await new Promise(resolve => setImmediate(resolve))
return { logs, errors, exits }
}

t.test('prints the global prefix', async t => {
const { logs, errors, exits } = await mockPrefix(t, async () => '/global/prefix')
t.strictSame(logs, [['/global/prefix']])
t.strictSame(errors, [])
t.strictSame(exits, [])
})

t.test('exits 1 if the global prefix cannot be determined', async t => {
const err = new Error('bad config')
const { logs, errors, exits } = await mockPrefix(t, async () => {
throw err
})
t.strictSame(logs, [])
t.strictSame(errors, [[err]])
t.strictSame(exits, [1])
})
Loading