-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: prompt before opening web-login URL when performing
login
/`ad…
…duser` (#4960) Prompt before opening web-login URL when performing login/adduser
- Loading branch information
Showing
5 changed files
with
255 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
const readline = require('readline') | ||
const opener = require('opener') | ||
|
||
function print (npm, title, url) { | ||
const json = npm.config.get('json') | ||
|
||
const message = json ? JSON.stringify({ title, url }) : `${title}:\n${url}` | ||
|
||
npm.output(message) | ||
} | ||
|
||
// Prompt to open URL in browser if possible | ||
const promptOpen = async (npm, url, title, prompt, emitter) => { | ||
const browser = npm.config.get('browser') | ||
const isInteractive = process.stdin.isTTY === true && process.stdout.isTTY === true | ||
|
||
try { | ||
if (!/^https?:$/.test(new URL(url).protocol)) { | ||
throw new Error() | ||
} | ||
} catch (_) { | ||
throw new Error('Invalid URL: ' + url) | ||
} | ||
|
||
print(npm, title, url) | ||
|
||
if (browser === false || !isInteractive) { | ||
return | ||
} | ||
|
||
const rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
}) | ||
|
||
const tryOpen = await new Promise(resolve => { | ||
rl.question(prompt, () => { | ||
resolve(true) | ||
}) | ||
|
||
if (emitter && emitter.addListener) { | ||
emitter.addListener('abort', () => { | ||
rl.close() | ||
|
||
// clear the prompt line | ||
npm.output('') | ||
|
||
resolve(false) | ||
}) | ||
} | ||
}) | ||
|
||
if (!tryOpen) { | ||
return | ||
} | ||
|
||
const command = browser === true ? null : browser | ||
await new Promise((resolve, reject) => { | ||
opener(url, { command }, err => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
|
||
return resolve() | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = promptOpen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* IMPORTANT | ||
* This snapshot file is auto-generated, but designed for humans. | ||
* It should be checked into source control and tracked carefully. | ||
* Re-generate by setting TAP_SNAPSHOT=1 and running tests. | ||
* Make sure to inspect the output below. Do not ignore changes! | ||
*/ | ||
'use strict' | ||
exports[`test/lib/utils/open-url-prompt.js TAP opens a url > must match snapshot 1`] = ` | ||
Array [ | ||
Array [ | ||
String( | ||
npm home: | ||
https://www.npmjs.com | ||
), | ||
], | ||
] | ||
` | ||
|
||
exports[`test/lib/utils/open-url-prompt.js TAP prints json output > must match snapshot 1`] = ` | ||
Array [ | ||
Array [ | ||
"{\\"title\\":\\"npm home\\",\\"url\\":\\"https://www.npmjs.com\\"}", | ||
], | ||
] | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
const t = require('tap') | ||
const mockGlobals = require('../../fixtures/mock-globals.js') | ||
const EventEmitter = require('events') | ||
|
||
const OUTPUT = [] | ||
const output = (...args) => OUTPUT.push(args) | ||
const npm = { | ||
_config: { | ||
json: false, | ||
browser: true, | ||
}, | ||
config: { | ||
get: k => npm._config[k], | ||
set: (k, v) => { | ||
npm._config[k] = v | ||
}, | ||
}, | ||
output, | ||
} | ||
|
||
let openerUrl = null | ||
let openerOpts = null | ||
let openerResult = null | ||
const opener = (url, opts, cb) => { | ||
openerUrl = url | ||
openerOpts = opts | ||
return cb(openerResult) | ||
} | ||
|
||
let questionShouldResolve = true | ||
const readline = { | ||
createInterface: () => ({ | ||
question: (_q, cb) => { | ||
if (questionShouldResolve === true) { | ||
cb() | ||
} | ||
}, | ||
close: () => {}, | ||
}), | ||
} | ||
|
||
const openUrlPrompt = t.mock('../../../lib/utils/open-url-prompt.js', { | ||
opener, | ||
readline, | ||
}) | ||
|
||
mockGlobals(t, { | ||
'process.stdin.isTTY': true, | ||
'process.stdout.isTTY': true, | ||
}) | ||
|
||
t.test('does not open a url in non-interactive environments', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
OUTPUT.length = 0 | ||
}) | ||
|
||
mockGlobals(t, { | ||
'process.stdin.isTTY': false, | ||
'process.stdout.isTTY': false, | ||
}) | ||
|
||
await openUrlPrompt(npm, 'https://www.npmjs.com', 'npm home', 'prompt') | ||
t.equal(openerUrl, null, 'did not open') | ||
t.same(openerOpts, null, 'did not open') | ||
}) | ||
|
||
t.test('opens a url', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
OUTPUT.length = 0 | ||
npm._config.browser = true | ||
}) | ||
|
||
npm._config.browser = 'browser' | ||
await openUrlPrompt(npm, 'https://www.npmjs.com', 'npm home', 'prompt') | ||
t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') | ||
t.same(openerOpts, { command: 'browser' }, 'passed command as null (the default)') | ||
t.matchSnapshot(OUTPUT) | ||
}) | ||
|
||
t.test('prints json output', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
OUTPUT.length = 0 | ||
npm._config.json = false | ||
}) | ||
|
||
npm._config.json = true | ||
await openUrlPrompt(npm, 'https://www.npmjs.com', 'npm home', 'prompt') | ||
t.matchSnapshot(OUTPUT) | ||
}) | ||
|
||
t.test('returns error for non-https url', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
OUTPUT.length = 0 | ||
}) | ||
await t.rejects( | ||
openUrlPrompt(npm, 'ftp://www.npmjs.com', 'npm home', 'prompt'), | ||
/Invalid URL/, | ||
'got the correct error' | ||
) | ||
t.equal(openerUrl, null, 'did not open') | ||
t.same(openerOpts, null, 'did not open') | ||
t.same(OUTPUT, [], 'printed no output') | ||
}) | ||
|
||
t.test('does not open url if canceled', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
OUTPUT.length = 0 | ||
questionShouldResolve = true | ||
}) | ||
|
||
questionShouldResolve = false | ||
const emitter = new EventEmitter() | ||
|
||
const open = openUrlPrompt(npm, 'https://www.npmjs.com', 'npm home', 'prompt', emitter) | ||
|
||
emitter.emit('abort') | ||
|
||
await open | ||
|
||
t.equal(openerUrl, null, 'did not open') | ||
t.same(openerOpts, null, 'did not open') | ||
}) | ||
|
||
t.test('returns error when opener errors', async t => { | ||
t.teardown(() => { | ||
openerUrl = null | ||
openerOpts = null | ||
openerResult = null | ||
OUTPUT.length = 0 | ||
}) | ||
|
||
openerResult = new Error('Opener failed') | ||
|
||
await t.rejects( | ||
openUrlPrompt(npm, 'https://www.npmjs.com', 'npm home', 'prompt'), | ||
/Opener failed/, | ||
'got the correct error' | ||
) | ||
t.equal(openerUrl, 'https://www.npmjs.com', 'did not open') | ||
}) |