Skip to content

Commit ac25863

Browse files
nlfwraithgar
authored andcommitted
1 parent c88c53b commit ac25863

23 files changed

+553
-270
lines changed

DEPENDENCIES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,6 @@ graph LR;
527527
npm-->npmcli-run-script["@npmcli/run-script"];
528528
npm-->npmcli-template-oss["@npmcli/template-oss"];
529529
npm-->npmlog;
530-
npm-->opener;
531530
npm-->p-map;
532531
npm-->pacote;
533532
npm-->parse-conflict-json;
@@ -658,6 +657,7 @@ graph LR;
658657
npmcli-move-file-->mkdirp;
659658
npmcli-move-file-->rimraf;
660659
npmcli-package-json-->json-parse-even-better-errors;
660+
npmcli-promise-spawn-->which;
661661
npmcli-query-->postcss-selector-parser;
662662
npmcli-run-script-->node-gyp;
663663
npmcli-run-script-->npmcli-node-gyp["@npmcli/node-gyp"];

node_modules/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
!/node-gyp/node_modules/ssri
176176
!/node-gyp/node_modules/unique-filename
177177
!/node-gyp/node_modules/unique-slug
178+
!/node-gyp/node_modules/which
178179
!/nopt
179180
!/normalize-package-data
180181
!/npm-audit-report
@@ -189,7 +190,6 @@
189190
!/npm-user-validate
190191
!/npmlog
191192
!/once
192-
!/opener
193193
!/p-map
194194
!/pacote
195195
!/parse-conflict-json

node_modules/@npmcli/git/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@npmcli/git",
3-
"version": "4.0.2",
3+
"version": "4.0.3",
44
"main": "lib/index.js",
55
"files": [
66
"bin/",
@@ -32,29 +32,29 @@
3232
},
3333
"devDependencies": {
3434
"@npmcli/eslint-config": "^4.0.0",
35-
"@npmcli/template-oss": "4.7.1",
35+
"@npmcli/template-oss": "4.8.0",
3636
"npm-package-arg": "^10.0.0",
3737
"rimraf": "^3.0.2",
3838
"slash": "^3.0.0",
3939
"tap": "^16.0.1"
4040
},
4141
"dependencies": {
42-
"@npmcli/promise-spawn": "^5.0.0",
42+
"@npmcli/promise-spawn": "^6.0.0",
4343
"lru-cache": "^7.4.4",
4444
"mkdirp": "^1.0.4",
4545
"npm-pick-manifest": "^8.0.0",
4646
"proc-log": "^3.0.0",
4747
"promise-inflight": "^1.0.1",
4848
"promise-retry": "^2.0.1",
4949
"semver": "^7.3.5",
50-
"which": "^2.0.2"
50+
"which": "^3.0.0"
5151
},
5252
"engines": {
5353
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
5454
},
5555
"templateOSS": {
5656
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
5757
"windowsCI": false,
58-
"version": "4.7.1"
58+
"version": "4.8.0"
5959
}
6060
}
+144-11
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,44 @@
1+
'use strict'
2+
13
const { spawn } = require('child_process')
4+
const os = require('os')
5+
const which = require('which')
26

3-
const isPipe = (stdio = 'pipe', fd) =>
4-
stdio === 'pipe' || stdio === null ? true
5-
: Array.isArray(stdio) ? isPipe(stdio[fd], fd)
6-
: false
7+
const escape = require('./escape.js')
78

89
// 'extra' object is for decorating the error a bit more
910
const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
11+
if (opts.shell) {
12+
return spawnWithShell(cmd, args, opts, extra)
13+
}
14+
1015
let proc
16+
1117
const p = new Promise((res, rej) => {
1218
proc = spawn(cmd, args, opts)
19+
1320
const stdout = []
1421
const stderr = []
22+
1523
const reject = er => rej(Object.assign(er, {
1624
cmd,
1725
args,
1826
...stdioResult(stdout, stderr, opts),
1927
...extra,
2028
}))
29+
2130
proc.on('error', reject)
31+
2232
if (proc.stdout) {
2333
proc.stdout.on('data', c => stdout.push(c)).on('error', reject)
2434
proc.stdout.on('error', er => reject(er))
2535
}
36+
2637
if (proc.stderr) {
2738
proc.stderr.on('data', c => stderr.push(c)).on('error', reject)
2839
proc.stderr.on('error', er => reject(er))
2940
}
41+
3042
proc.on('close', (code, signal) => {
3143
const result = {
3244
cmd,
@@ -36,6 +48,7 @@ const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
3648
...stdioResult(stdout, stderr, opts),
3749
...extra,
3850
}
51+
3952
if (code || signal) {
4053
rej(Object.assign(new Error('command failed'), result))
4154
} else {
@@ -49,14 +62,134 @@ const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
4962
return p
5063
}
5164

52-
const stdioResult = (stdout, stderr, { stdioString, stdio }) =>
53-
stdioString ? {
54-
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout).toString().trim() : null,
55-
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr).toString().trim() : null,
65+
const spawnWithShell = (cmd, args, opts, extra) => {
66+
let command = opts.shell
67+
// if shell is set to true, we use a platform default. we can't let the core
68+
// spawn method decide this for us because we need to know what shell is in use
69+
// ahead of time so that we can escape arguments properly. we don't need coverage here.
70+
if (command === true) {
71+
// istanbul ignore next
72+
command = process.platform === 'win32' ? process.env.ComSpec : 'sh'
5673
}
57-
: {
58-
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout) : null,
59-
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr) : null,
74+
75+
const options = { ...opts, shell: false }
76+
const realArgs = []
77+
let script = cmd
78+
79+
// first, determine if we're in windows because if we are we need to know if we're
80+
// running an .exe or a .cmd/.bat since the latter requires extra escaping
81+
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(command)
82+
if (isCmd) {
83+
let doubleEscape = false
84+
85+
// find the actual command we're running
86+
let initialCmd = ''
87+
let insideQuotes = false
88+
for (let i = 0; i < cmd.length; ++i) {
89+
const char = cmd.charAt(i)
90+
if (char === ' ' && !insideQuotes) {
91+
break
92+
}
93+
94+
initialCmd += char
95+
if (char === '"' || char === "'") {
96+
insideQuotes = !insideQuotes
97+
}
98+
}
99+
100+
let pathToInitial
101+
try {
102+
pathToInitial = which.sync(initialCmd, {
103+
path: (options.env && options.env.PATH) || process.env.PATH,
104+
pathext: (options.env && options.env.PATHEXT) || process.env.PATHEXT,
105+
}).toLowerCase()
106+
} catch (err) {
107+
pathToInitial = initialCmd.toLowerCase()
108+
}
109+
110+
doubleEscape = pathToInitial.endsWith('.cmd') || pathToInitial.endsWith('.bat')
111+
for (const arg of args) {
112+
script += ` ${escape.cmd(arg, doubleEscape)}`
113+
}
114+
realArgs.push('/d', '/s', '/c', script)
115+
options.windowsVerbatimArguments = true
116+
} else {
117+
for (const arg of args) {
118+
script += ` ${escape.sh(arg)}`
119+
}
120+
realArgs.push('-c', script)
60121
}
61122

123+
return promiseSpawn(command, realArgs, options, extra)
124+
}
125+
126+
// open a file with the default application as defined by the user's OS
127+
const open = (_args, opts = {}, extra = {}) => {
128+
const options = { ...opts, shell: true }
129+
const args = [].concat(_args)
130+
131+
let platform = process.platform
132+
// process.platform === 'linux' may actually indicate WSL, if that's the case
133+
// we want to treat things as win32 anyway so the host can open the argument
134+
if (platform === 'linux' && os.release().includes('Microsoft')) {
135+
platform = 'win32'
136+
}
137+
138+
let command = options.command
139+
if (!command) {
140+
if (platform === 'win32') {
141+
// spawnWithShell does not do the additional os.release() check, so we
142+
// have to force the shell here to make sure we treat WSL as windows.
143+
options.shell = process.env.ComSpec
144+
// also, the start command accepts a title so to make sure that we don't
145+
// accidentally interpret the first arg as the title, we stick an empty
146+
// string immediately after the start command
147+
command = 'start ""'
148+
} else if (platform === 'darwin') {
149+
command = 'open'
150+
} else {
151+
command = 'xdg-open'
152+
}
153+
}
154+
155+
return spawnWithShell(command, args, options, extra)
156+
}
157+
promiseSpawn.open = open
158+
159+
const isPipe = (stdio = 'pipe', fd) => {
160+
if (stdio === 'pipe' || stdio === null) {
161+
return true
162+
}
163+
164+
if (Array.isArray(stdio)) {
165+
return isPipe(stdio[fd], fd)
166+
}
167+
168+
return false
169+
}
170+
171+
const stdioResult = (stdout, stderr, { stdioString = true, stdio }) => {
172+
const result = {
173+
stdout: null,
174+
stderr: null,
175+
}
176+
177+
// stdio is [stdin, stdout, stderr]
178+
if (isPipe(stdio, 1)) {
179+
result.stdout = Buffer.concat(stdout)
180+
if (stdioString) {
181+
result.stdout = result.stdout.toString().trim()
182+
}
183+
}
184+
185+
if (isPipe(stdio, 2)) {
186+
result.stderr = Buffer.concat(stderr)
187+
if (stdioString) {
188+
result.stderr = result.stderr.toString().trim()
189+
}
190+
}
191+
192+
return result
193+
}
194+
62195
module.exports = promiseSpawn

node_modules/@npmcli/promise-spawn/package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@npmcli/promise-spawn",
3-
"version": "5.0.0",
3+
"version": "6.0.1",
44
"files": [
55
"bin/",
66
"lib/"
@@ -32,15 +32,19 @@
3232
},
3333
"devDependencies": {
3434
"@npmcli/eslint-config": "^4.0.0",
35-
"@npmcli/template-oss": "4.7.1",
35+
"@npmcli/template-oss": "4.8.0",
3636
"minipass": "^3.1.1",
37+
"spawk": "^1.7.1",
3738
"tap": "^16.0.1"
3839
},
3940
"engines": {
4041
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
4142
},
4243
"templateOSS": {
4344
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
44-
"version": "4.7.1"
45+
"version": "4.8.0"
46+
},
47+
"dependencies": {
48+
"which": "^3.0.0"
4549
}
4650
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
/* eslint camelcase: "off" */
2-
const isWindows = require('./is-windows.js')
32
const setPATH = require('./set-path.js')
43
const { resolve } = require('path')
5-
const which = require('which')
64
const npm_config_node_gyp = require.resolve('node-gyp/bin/node-gyp.js')
7-
const escape = require('./escape.js')
85

96
const makeSpawnArgs = options => {
107
const {
118
event,
129
path,
13-
scriptShell = isWindows ? process.env.ComSpec || 'cmd' : 'sh',
10+
scriptShell = true,
1411
binPaths,
1512
env = {},
1613
stdio,
@@ -29,55 +26,15 @@ const makeSpawnArgs = options => {
2926
npm_config_node_gyp,
3027
})
3128

32-
let doubleEscape = false
33-
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(scriptShell)
34-
if (isCmd) {
35-
let initialCmd = ''
36-
let insideQuotes = false
37-
for (let i = 0; i < cmd.length; ++i) {
38-
const char = cmd.charAt(i)
39-
if (char === ' ' && !insideQuotes) {
40-
break
41-
}
42-
43-
initialCmd += char
44-
if (char === '"' || char === "'") {
45-
insideQuotes = !insideQuotes
46-
}
47-
}
48-
49-
let pathToInitial
50-
try {
51-
pathToInitial = which.sync(initialCmd, {
52-
path: spawnEnv.path,
53-
pathext: spawnEnv.pathext,
54-
}).toLowerCase()
55-
} catch (err) {
56-
pathToInitial = initialCmd.toLowerCase()
57-
}
58-
59-
doubleEscape = pathToInitial.endsWith('.cmd') || pathToInitial.endsWith('.bat')
60-
}
61-
62-
let script = cmd
63-
for (const arg of args) {
64-
script += isCmd
65-
? ` ${escape.cmd(arg, doubleEscape)}`
66-
: ` ${escape.sh(arg)}`
67-
}
68-
const spawnArgs = isCmd
69-
? ['/d', '/s', '/c', script]
70-
: ['-c', '--', script]
71-
7229
const spawnOpts = {
7330
env: spawnEnv,
7431
stdioString,
7532
stdio,
7633
cwd: path,
77-
...(isCmd ? { windowsVerbatimArguments: true } : {}),
34+
shell: scriptShell,
7835
}
7936

80-
return [scriptShell, spawnArgs, spawnOpts]
37+
return [cmd, args, spawnOpts]
8138
}
8239

8340
module.exports = makeSpawnArgs

0 commit comments

Comments
 (0)