-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat: prompt before opening web-login URL when performing login
/adduser
#4960
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1d16451
feat: Prompt before opening web-login URL when performing login/adduser
jumoel aae928c
Try to resolve tests
jumoel 2248f45
Re-add interactiveness test and add a linebreak
jumoel 479e893
Make API more similar to regular open-url and add tests
jumoel adc377c
Lint
jumoel 8df27c7
revert stylistic changes
jumoel 33ac7a5
Revert more stylistic changes
jumoel 0e14211
Move readline.createInterface to ensure tests complete
jumoel f90528c
Move readline a bit higher
jumoel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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') | ||
}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.