Skip to content
This repository was archived by the owner on Feb 1, 2022. It is now read-only.

Commit 67eee41

Browse files
authored
feat: anykey (#39)
1 parent f9c22df commit 67eee41

File tree

10 files changed

+158
-53
lines changed

10 files changed

+158
-53
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- v1-yarn-{{checksum ".circleci/config.yml"}}-{{ checksum "yarn.lock"}}
1515
- v1-yarn-{{checksum ".circleci/config.yml"}}
1616
- run: .circleci/greenkeeper
17-
- run: yarn add -D nyc@11 @oclif/nyc-config@0
17+
- run: yarn add -D nyc@13 @oclif/nyc-config@1
1818
- run: |
1919
$NYC yarn test
2020
$NYC report --reporter text-lcov > coverage.lcov

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ await cli.prompt('What is your two-factor token?', {type: 'mask'})
3434

3535
// mask input on keypress (before enter is pressed)
3636
await cli.prompt('What is your password?', {type: 'hide'})
37+
38+
// yes/no confirmation
39+
await cli.confirm('Continue?')
40+
41+
// "press any key to continue"
42+
await cli.anykey()
3743
```
3844

3945
![prompt demo](assets/prompt.gif)

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
environment:
2-
nodejs_version: "9"
2+
nodejs_version: "10"
33
cache:
44
- '%LOCALAPPDATA%\Yarn -> appveyor.yml'
55
- node_modules -> yarn.lock
66

77
install:
88
- ps: Install-Product node $env:nodejs_version x64
9-
- yarn add -D nyc@11 @oclif/nyc-config@0
9+
- yarn add -D nyc@13 @oclif/nyc-config@1
1010
test_script:
1111
- .\node_modules\.bin\nyc --nycrc-path node_modules/@oclif/nyc-config/.nycrc yarn test
1212
after_test:

examples/prompt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ async function run() {
88
let input = await cli.prompt('your name (normal)')
99
cli.action.start('working')
1010
await wait()
11+
input = await cli.anykey()
12+
await wait()
1113
cli.log(`you entered: ${input}`)
1214
input = await cli.prompt('your name (mask)', {type: 'mask'})
1315
await wait()

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
"@oclif/errors": "^1.1.2",
2828
"@oclif/tslint": "^1.1.2",
2929
"@types/ansi-styles": "^3.2.0",
30-
"@types/chai": "^4.1.3",
30+
"@types/chai": "^4.1.4",
3131
"@types/chai-as-promised": "^7.1.0",
3232
"@types/clean-stack": "^1.3.0",
3333
"@types/extract-stack": "^1.0.0",
34-
"@types/fs-extra": "^5.0.2",
34+
"@types/fs-extra": "^5.0.3",
3535
"@types/indent-string": "^3.0.0",
3636
"@types/lodash": "^4.14.109",
37-
"@types/mocha": "^5.2.0",
38-
"@types/node": "^10.3.0",
37+
"@types/mocha": "^5.2.2",
38+
"@types/node": "^10.3.3",
3939
"@types/semver": "^5.5.0",
4040
"@types/strip-ansi": "^3.0.0",
4141
"@types/supports-color": "^5.3.0",
@@ -44,12 +44,12 @@
4444
"concurrently": "^3.5.1",
4545
"eslint": "^4.19.1",
4646
"eslint-config-oclif": "^1.5.1",
47-
"fancy-test": "^1.0.9",
47+
"fancy-test": "^1.2.0",
4848
"husky": "^0.14.3",
4949
"mocha": "^5.2.0",
50-
"ts-node": "^6.0.5",
50+
"ts-node": "^6.1.1",
5151
"tslint": "^5.10.0",
52-
"typescript": "^2.9.1"
52+
"typescript": "^2.9.2"
5353
},
5454
"engines": {
5555
"node": ">=8.0.0"

src/deps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const deps = {
66
get screen(): typeof import ('@oclif/screen') { return fetch('@oclif/screen') },
77

88
get open(): typeof import ('./open').default { return fetch('./open').default },
9-
get prompt(): typeof import ('./prompt').default { return fetch('./prompt').default },
9+
get prompt(): typeof import ('./prompt') { return fetch('./prompt') },
1010
get styledObject(): typeof import ('./styled/object').default { return fetch('./styled/object').default },
1111
get styledHeader(): typeof import ('./styled/header').default { return fetch('./styled/header').default },
1212
get styledJSON(): typeof import ('./styled/json').default { return fetch('./styled/json').default },

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export const ux = {
1616
exit: Errors.exit,
1717

1818
get prompt() { return deps.prompt.prompt },
19+
/**
20+
* "press anykey to continue"
21+
*/
22+
get anykey() { return deps.prompt.anykey },
1923
get confirm() { return deps.prompt.confirm },
2024
get action() { return config.action },
2125
styledObject(obj: any, keys?: string[]) { ux.info(deps.styledObject(obj, keys)) },

src/prompt.ts

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import {error} from '@oclif/errors'
12
import chalk from 'chalk'
23

34
import config from './config'
45
import deps from './deps'
56

67
export interface IPromptOptions {
78
prompt?: string
8-
type?: 'normal' | 'mask' | 'hide'
9+
type?: 'normal' | 'mask' | 'hide' | 'single'
910
timeout?: number
1011
/**
1112
* Requires user input if true, otherwise allows empty input
@@ -17,30 +18,50 @@ export interface IPromptOptions {
1718
interface IPromptConfig {
1819
name: string
1920
prompt: string
20-
type: 'normal' | 'mask' | 'hide'
21+
type: 'normal' | 'mask' | 'hide' | 'single'
2122
isTTY: boolean
2223
required: boolean
2324
default?: string
2425
timeout?: number
2526
}
2627

27-
export default {
28-
prompt(name: string, options: IPromptOptions = {}) {
29-
return config.action.pauseAsync(() => {
30-
return _prompt(name, options)
31-
}, chalk.cyan('?'))
32-
},
33-
confirm(message: string): Promise<boolean> {
34-
return config.action.pauseAsync(async () => {
35-
const confirm = async (): Promise<boolean> => {
36-
let response = (await _prompt(message)).toLowerCase()
37-
if (['n', 'no'].includes(response)) return false
38-
if (['y', 'yes'].includes(response)) return true
39-
return confirm()
40-
}
28+
/**
29+
* prompt for input
30+
*/
31+
export function prompt(name: string, options: IPromptOptions = {}) {
32+
return config.action.pauseAsync(() => {
33+
return _prompt(name, options)
34+
}, chalk.cyan('?'))
35+
}
36+
37+
/**
38+
* confirmation prompt (yes/no)
39+
*/
40+
export function confirm(message: string): Promise<boolean> {
41+
return config.action.pauseAsync(async () => {
42+
const confirm = async (): Promise<boolean> => {
43+
let response = (await _prompt(message)).toLowerCase()
44+
if (['n', 'no'].includes(response)) return false
45+
if (['y', 'yes'].includes(response)) return true
4146
return confirm()
42-
}, chalk.cyan('?'))
47+
}
48+
return confirm()
49+
}, chalk.cyan('?'))
50+
}
51+
52+
/**
53+
* "press anykey to continue"
54+
*/
55+
export async function anykey(message?: string): Promise<void> {
56+
if (!message) {
57+
message = process.stdin.setRawMode
58+
? `Press any key to continue or ${chalk.yellow('q')} to exit`
59+
: `Press enter to continue or ${chalk.yellow('q')} to exit`
4360
}
61+
const char = await prompt(message, {type: 'single'})
62+
if (char === 'q') error('quit')
63+
if (char === '\u0003') error('ctrl-c')
64+
return char
4465
}
4566

4667
function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Promise<string> {
@@ -58,6 +79,8 @@ function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Prom
5879
switch (options.type) {
5980
case 'normal':
6081
return normal(options)
82+
case 'single':
83+
return single(options)
6184
case 'mask':
6285
case 'hide':
6386
return deps.passwordPrompt(options.prompt, {method: options.type})
@@ -66,6 +89,14 @@ function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Prom
6689
}
6790
}
6891

92+
async function single(options: IPromptConfig): Promise<string> {
93+
const raw = process.stdin.isRaw
94+
if (process.stdin.setRawMode) process.stdin.setRawMode(true)
95+
const response = await normal(options)
96+
if (process.stdin.setRawMode) process.stdin.setRawMode(!!raw)
97+
return response
98+
}
99+
69100
function normal(options: IPromptConfig, retries = 100): Promise<string> {
70101
if (retries < 0) throw new Error('no input')
71102
return new Promise((resolve, reject) => {

test/prompt.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,39 @@ describe('prompt', () => {
2121
expect(answer).to.equal('answer')
2222
})
2323

24+
fancy
25+
.stdout()
26+
.stderr()
27+
.stdin('y')
28+
.end('confirm', async () => {
29+
const promptPromise = cli.confirm('yes/no?')
30+
const answer = await promptPromise
31+
await cli.done()
32+
expect(answer).to.equal(true)
33+
})
34+
35+
fancy
36+
.stdout()
37+
.stderr()
38+
.stdin('n')
39+
.end('confirm', async () => {
40+
const promptPromise = cli.confirm('yes/no?')
41+
const answer = await promptPromise
42+
await cli.done()
43+
expect(answer).to.equal(false)
44+
})
45+
46+
fancy
47+
.stdout()
48+
.stderr()
49+
.stdin('x')
50+
.end('gets anykey', async () => {
51+
const promptPromise = cli.anykey()
52+
const answer = await promptPromise
53+
await cli.done()
54+
expect(answer).to.equal('x')
55+
})
56+
2457
fancy
2558
.stdout()
2659
.stderr()

0 commit comments

Comments
 (0)