Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use promiseSpawn.open instead of opener #5789

Merged
merged 6 commits into from Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEPENDENCIES.md
Expand Up @@ -527,7 +527,6 @@ graph LR;
npm-->npmcli-run-script["@npmcli/run-script"];
npm-->npmcli-template-oss["@npmcli/template-oss"];
npm-->npmlog;
npm-->opener;
npm-->p-map;
npm-->pacote;
npm-->parse-conflict-json;
Expand Down Expand Up @@ -658,6 +657,7 @@ graph LR;
npmcli-move-file-->mkdirp;
npmcli-move-file-->rimraf;
npmcli-package-json-->json-parse-even-better-errors;
npmcli-promise-spawn-->which;
npmcli-query-->postcss-selector-parser;
npmcli-run-script-->node-gyp;
npmcli-run-script-->npmcli-node-gyp["@npmcli/node-gyp"];
Expand Down
1 change: 0 additions & 1 deletion lib/commands/ci.js
Expand Up @@ -94,7 +94,6 @@ class CI extends ArboristWorkspaceCmd {
args: [],
scriptShell,
stdio: 'inherit',
stdioString: true,
banner: !this.npm.silent,
event,
})
Expand Down
1 change: 0 additions & 1 deletion lib/commands/explore.js
Expand Up @@ -59,7 +59,6 @@ class Explore extends BaseCommand {
pkg,
banner: false,
path,
stdioString: true,
event: '_explore',
stdio: 'inherit',
}).catch(er => {
Expand Down
1 change: 0 additions & 1 deletion lib/commands/install.js
Expand Up @@ -161,7 +161,6 @@ class Install extends ArboristWorkspaceCmd {
args: [],
scriptShell,
stdio: 'inherit',
stdioString: true,
banner: !this.npm.silent,
event,
})
Expand Down
1 change: 0 additions & 1 deletion lib/commands/run-script.js
Expand Up @@ -117,7 +117,6 @@ class RunScript extends BaseCommand {
args,
scriptShell,
stdio: 'inherit',
stdioString: true,
pkg,
banner: !this.npm.silent,
}
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/config/definitions.js
Expand Up @@ -60,7 +60,7 @@ const buildOmitList = obj => {

const editor = process.env.EDITOR ||
process.env.VISUAL ||
(isWindows ? 'notepad.exe' : 'vi')
(isWindows ? `${process.env.SYSTEMROOT}\\notepad.exe` : 'vi')

const shell = isWindows ? process.env.ComSpec || 'cmd'
: process.env.SHELL || 'sh'
Expand Down Expand Up @@ -628,8 +628,8 @@ define('dry-run', {
define('editor', {
default: editor,
defaultDescription: `
The EDITOR or VISUAL environment variables, or 'notepad.exe' on Windows,
or 'vim' on Unix systems
The EDITOR or VISUAL environment variables, or '%SYSTEMROOT%\\notepad.exe' on Windows,
or 'vi' on Unix systems
`,
type: String,
description: `
Expand Down
12 changes: 2 additions & 10 deletions lib/utils/open-url-prompt.js
@@ -1,5 +1,5 @@
const readline = require('readline')
const opener = require('opener')
const promiseSpawn = require('@npmcli/promise-spawn')

function print (npm, title, url) {
const json = npm.config.get('json')
Expand Down Expand Up @@ -64,15 +64,7 @@ const promptOpen = async (npm, url, title, prompt, emitter) => {
}

const command = browser === true ? null : browser
await new Promise((resolve, reject) => {
opener(url, { command }, err => {
if (err) {
return reject(err)
}

return resolve()
})
})
await promiseSpawn.open(url, { command })
}

module.exports = promptOpen
18 changes: 7 additions & 11 deletions lib/utils/open-url.js
@@ -1,4 +1,4 @@
const opener = require('opener')
const promiseSpawn = require('@npmcli/promise-spawn')

const { URL } = require('url')

Expand Down Expand Up @@ -37,18 +37,14 @@ const open = async (npm, url, errMsg, isFile) => {
}

const command = browser === true ? null : browser
await new Promise((resolve, reject) => {
opener(url, { command }, (err) => {
if (err) {
if (err.code === 'ENOENT') {
printAlternateMsg()
} else {
return reject(err)
}
await promiseSpawn.open(url, { command })
.catch((err) => {
if (err.code !== 'ENOENT') {
throw err
}
return resolve()

printAlternateMsg()
})
})
}

module.exports = open
2 changes: 1 addition & 1 deletion node_modules/.gitignore
Expand Up @@ -175,6 +175,7 @@
!/node-gyp/node_modules/ssri
!/node-gyp/node_modules/unique-filename
!/node-gyp/node_modules/unique-slug
!/node-gyp/node_modules/which
!/nopt
!/normalize-package-data
!/npm-audit-report
Expand All @@ -189,7 +190,6 @@
!/npm-user-validate
!/npmlog
!/once
!/opener
!/p-map
!/pacote
!/parse-conflict-json
Expand Down
10 changes: 5 additions & 5 deletions node_modules/@npmcli/git/package.json
@@ -1,6 +1,6 @@
{
"name": "@npmcli/git",
"version": "4.0.2",
"version": "4.0.3",
"main": "lib/index.js",
"files": [
"bin/",
Expand Down Expand Up @@ -32,29 +32,29 @@
},
"devDependencies": {
"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.7.1",
"@npmcli/template-oss": "4.8.0",
"npm-package-arg": "^10.0.0",
"rimraf": "^3.0.2",
"slash": "^3.0.0",
"tap": "^16.0.1"
},
"dependencies": {
"@npmcli/promise-spawn": "^5.0.0",
"@npmcli/promise-spawn": "^6.0.0",
"lru-cache": "^7.4.4",
"mkdirp": "^1.0.4",
"npm-pick-manifest": "^8.0.0",
"proc-log": "^3.0.0",
"promise-inflight": "^1.0.1",
"promise-retry": "^2.0.1",
"semver": "^7.3.5",
"which": "^2.0.2"
"which": "^3.0.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"windowsCI": false,
"version": "4.7.1"
"version": "4.8.0"
}
}
155 changes: 144 additions & 11 deletions node_modules/@npmcli/promise-spawn/lib/index.js
@@ -1,32 +1,44 @@
'use strict'

const { spawn } = require('child_process')
const os = require('os')
const which = require('which')

const isPipe = (stdio = 'pipe', fd) =>
stdio === 'pipe' || stdio === null ? true
: Array.isArray(stdio) ? isPipe(stdio[fd], fd)
: false
const escape = require('./escape.js')

// 'extra' object is for decorating the error a bit more
const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
if (opts.shell) {
return spawnWithShell(cmd, args, opts, extra)
}

let proc

const p = new Promise((res, rej) => {
proc = spawn(cmd, args, opts)

const stdout = []
const stderr = []

const reject = er => rej(Object.assign(er, {
cmd,
args,
...stdioResult(stdout, stderr, opts),
...extra,
}))

proc.on('error', reject)

if (proc.stdout) {
proc.stdout.on('data', c => stdout.push(c)).on('error', reject)
proc.stdout.on('error', er => reject(er))
}

if (proc.stderr) {
proc.stderr.on('data', c => stderr.push(c)).on('error', reject)
proc.stderr.on('error', er => reject(er))
}

proc.on('close', (code, signal) => {
const result = {
cmd,
Expand All @@ -36,6 +48,7 @@ const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
...stdioResult(stdout, stderr, opts),
...extra,
}

if (code || signal) {
rej(Object.assign(new Error('command failed'), result))
} else {
Expand All @@ -49,14 +62,134 @@ const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
return p
}

const stdioResult = (stdout, stderr, { stdioString, stdio }) =>
stdioString ? {
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout).toString().trim() : null,
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr).toString().trim() : null,
const spawnWithShell = (cmd, args, opts, extra) => {
let command = opts.shell
// if shell is set to true, we use a platform default. we can't let the core
// spawn method decide this for us because we need to know what shell is in use
// ahead of time so that we can escape arguments properly. we don't need coverage here.
if (command === true) {
// istanbul ignore next
command = process.platform === 'win32' ? process.env.ComSpec : 'sh'
}
: {
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout) : null,
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr) : null,

const options = { ...opts, shell: false }
const realArgs = []
let script = cmd

// first, determine if we're in windows because if we are we need to know if we're
// running an .exe or a .cmd/.bat since the latter requires extra escaping
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(command)
if (isCmd) {
let doubleEscape = false

// find the actual command we're running
let initialCmd = ''
let insideQuotes = false
for (let i = 0; i < cmd.length; ++i) {
const char = cmd.charAt(i)
if (char === ' ' && !insideQuotes) {
break
}

initialCmd += char
if (char === '"' || char === "'") {
insideQuotes = !insideQuotes
}
}

let pathToInitial
try {
pathToInitial = which.sync(initialCmd, {
path: (options.env && options.env.PATH) || process.env.PATH,
pathext: (options.env && options.env.PATHEXT) || process.env.PATHEXT,
}).toLowerCase()
} catch (err) {
pathToInitial = initialCmd.toLowerCase()
}

doubleEscape = pathToInitial.endsWith('.cmd') || pathToInitial.endsWith('.bat')
for (const arg of args) {
script += ` ${escape.cmd(arg, doubleEscape)}`
}
realArgs.push('/d', '/s', '/c', script)
options.windowsVerbatimArguments = true
} else {
for (const arg of args) {
script += ` ${escape.sh(arg)}`
}
realArgs.push('-c', script)
}

return promiseSpawn(command, realArgs, options, extra)
}

// open a file with the default application as defined by the user's OS
const open = (_args, opts = {}, extra = {}) => {
const options = { ...opts, shell: true }
const args = [].concat(_args)

let platform = process.platform
// process.platform === 'linux' may actually indicate WSL, if that's the case
// we want to treat things as win32 anyway so the host can open the argument
if (platform === 'linux' && os.release().includes('Microsoft')) {
platform = 'win32'
}

let command = options.command
if (!command) {
if (platform === 'win32') {
// spawnWithShell does not do the additional os.release() check, so we
// have to force the shell here to make sure we treat WSL as windows.
options.shell = process.env.ComSpec
// also, the start command accepts a title so to make sure that we don't
// accidentally interpret the first arg as the title, we stick an empty
// string immediately after the start command
command = 'start ""'
} else if (platform === 'darwin') {
command = 'open'
} else {
command = 'xdg-open'
}
}

return spawnWithShell(command, args, options, extra)
}
promiseSpawn.open = open

const isPipe = (stdio = 'pipe', fd) => {
if (stdio === 'pipe' || stdio === null) {
return true
}

if (Array.isArray(stdio)) {
return isPipe(stdio[fd], fd)
}

return false
}

const stdioResult = (stdout, stderr, { stdioString = true, stdio }) => {
const result = {
stdout: null,
stderr: null,
}

// stdio is [stdin, stdout, stderr]
if (isPipe(stdio, 1)) {
result.stdout = Buffer.concat(stdout)
if (stdioString) {
result.stdout = result.stdout.toString().trim()
}
}

if (isPipe(stdio, 2)) {
result.stderr = Buffer.concat(stderr)
if (stdioString) {
result.stderr = result.stderr.toString().trim()
}
}

return result
}

module.exports = promiseSpawn