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

Commit

Permalink
feat: anykey (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx authored Jun 15, 2018
1 parent f9c22df commit 67eee41
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- v1-yarn-{{checksum ".circleci/config.yml"}}-{{ checksum "yarn.lock"}}
- v1-yarn-{{checksum ".circleci/config.yml"}}
- run: .circleci/greenkeeper
- run: yarn add -D nyc@11 @oclif/nyc-config@0
- run: yarn add -D nyc@13 @oclif/nyc-config@1
- run: |
$NYC yarn test
$NYC report --reporter text-lcov > coverage.lcov
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ await cli.prompt('What is your two-factor token?', {type: 'mask'})

// mask input on keypress (before enter is pressed)
await cli.prompt('What is your password?', {type: 'hide'})

// yes/no confirmation
await cli.confirm('Continue?')

// "press any key to continue"
await cli.anykey()
```

![prompt demo](assets/prompt.gif)
Expand Down
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
environment:
nodejs_version: "9"
nodejs_version: "10"
cache:
- '%LOCALAPPDATA%\Yarn -> appveyor.yml'
- node_modules -> yarn.lock

install:
- ps: Install-Product node $env:nodejs_version x64
- yarn add -D nyc@11 @oclif/nyc-config@0
- yarn add -D nyc@13 @oclif/nyc-config@1
test_script:
- .\node_modules\.bin\nyc --nycrc-path node_modules/@oclif/nyc-config/.nycrc yarn test
after_test:
Expand Down
2 changes: 2 additions & 0 deletions examples/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ async function run() {
let input = await cli.prompt('your name (normal)')
cli.action.start('working')
await wait()
input = await cli.anykey()
await wait()
cli.log(`you entered: ${input}`)
input = await cli.prompt('your name (mask)', {type: 'mask'})
await wait()
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
"@oclif/errors": "^1.1.2",
"@oclif/tslint": "^1.1.2",
"@types/ansi-styles": "^3.2.0",
"@types/chai": "^4.1.3",
"@types/chai": "^4.1.4",
"@types/chai-as-promised": "^7.1.0",
"@types/clean-stack": "^1.3.0",
"@types/extract-stack": "^1.0.0",
"@types/fs-extra": "^5.0.2",
"@types/fs-extra": "^5.0.3",
"@types/indent-string": "^3.0.0",
"@types/lodash": "^4.14.109",
"@types/mocha": "^5.2.0",
"@types/node": "^10.3.0",
"@types/mocha": "^5.2.2",
"@types/node": "^10.3.3",
"@types/semver": "^5.5.0",
"@types/strip-ansi": "^3.0.0",
"@types/supports-color": "^5.3.0",
Expand All @@ -44,12 +44,12 @@
"concurrently": "^3.5.1",
"eslint": "^4.19.1",
"eslint-config-oclif": "^1.5.1",
"fancy-test": "^1.0.9",
"fancy-test": "^1.2.0",
"husky": "^0.14.3",
"mocha": "^5.2.0",
"ts-node": "^6.0.5",
"ts-node": "^6.1.1",
"tslint": "^5.10.0",
"typescript": "^2.9.1"
"typescript": "^2.9.2"
},
"engines": {
"node": ">=8.0.0"
Expand Down
2 changes: 1 addition & 1 deletion src/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const deps = {
get screen(): typeof import ('@oclif/screen') { return fetch('@oclif/screen') },

get open(): typeof import ('./open').default { return fetch('./open').default },
get prompt(): typeof import ('./prompt').default { return fetch('./prompt').default },
get prompt(): typeof import ('./prompt') { return fetch('./prompt') },
get styledObject(): typeof import ('./styled/object').default { return fetch('./styled/object').default },
get styledHeader(): typeof import ('./styled/header').default { return fetch('./styled/header').default },
get styledJSON(): typeof import ('./styled/json').default { return fetch('./styled/json').default },
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const ux = {
exit: Errors.exit,

get prompt() { return deps.prompt.prompt },
/**
* "press anykey to continue"
*/
get anykey() { return deps.prompt.anykey },
get confirm() { return deps.prompt.confirm },
get action() { return config.action },
styledObject(obj: any, keys?: string[]) { ux.info(deps.styledObject(obj, keys)) },
Expand Down
65 changes: 48 additions & 17 deletions src/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {error} from '@oclif/errors'
import chalk from 'chalk'

import config from './config'
import deps from './deps'

export interface IPromptOptions {
prompt?: string
type?: 'normal' | 'mask' | 'hide'
type?: 'normal' | 'mask' | 'hide' | 'single'
timeout?: number
/**
* Requires user input if true, otherwise allows empty input
Expand All @@ -17,30 +18,50 @@ export interface IPromptOptions {
interface IPromptConfig {
name: string
prompt: string
type: 'normal' | 'mask' | 'hide'
type: 'normal' | 'mask' | 'hide' | 'single'
isTTY: boolean
required: boolean
default?: string
timeout?: number
}

export default {
prompt(name: string, options: IPromptOptions = {}) {
return config.action.pauseAsync(() => {
return _prompt(name, options)
}, chalk.cyan('?'))
},
confirm(message: string): Promise<boolean> {
return config.action.pauseAsync(async () => {
const confirm = async (): Promise<boolean> => {
let response = (await _prompt(message)).toLowerCase()
if (['n', 'no'].includes(response)) return false
if (['y', 'yes'].includes(response)) return true
return confirm()
}
/**
* prompt for input
*/
export function prompt(name: string, options: IPromptOptions = {}) {
return config.action.pauseAsync(() => {
return _prompt(name, options)
}, chalk.cyan('?'))
}

/**
* confirmation prompt (yes/no)
*/
export function confirm(message: string): Promise<boolean> {
return config.action.pauseAsync(async () => {
const confirm = async (): Promise<boolean> => {
let response = (await _prompt(message)).toLowerCase()
if (['n', 'no'].includes(response)) return false
if (['y', 'yes'].includes(response)) return true
return confirm()
}, chalk.cyan('?'))
}
return confirm()
}, chalk.cyan('?'))
}

/**
* "press anykey to continue"
*/
export async function anykey(message?: string): Promise<void> {
if (!message) {
message = process.stdin.setRawMode
? `Press any key to continue or ${chalk.yellow('q')} to exit`
: `Press enter to continue or ${chalk.yellow('q')} to exit`
}
const char = await prompt(message, {type: 'single'})
if (char === 'q') error('quit')
if (char === '\u0003') error('ctrl-c')
return char
}

function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Promise<string> {
Expand All @@ -58,6 +79,8 @@ function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Prom
switch (options.type) {
case 'normal':
return normal(options)
case 'single':
return single(options)
case 'mask':
case 'hide':
return deps.passwordPrompt(options.prompt, {method: options.type})
Expand All @@ -66,6 +89,14 @@ function _prompt(name: string, inputOptions: Partial<IPromptOptions> = {}): Prom
}
}

async function single(options: IPromptConfig): Promise<string> {
const raw = process.stdin.isRaw
if (process.stdin.setRawMode) process.stdin.setRawMode(true)
const response = await normal(options)
if (process.stdin.setRawMode) process.stdin.setRawMode(!!raw)
return response
}

function normal(options: IPromptConfig, retries = 100): Promise<string> {
if (retries < 0) throw new Error('no input')
return new Promise((resolve, reject) => {
Expand Down
33 changes: 33 additions & 0 deletions test/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@ describe('prompt', () => {
expect(answer).to.equal('answer')
})

fancy
.stdout()
.stderr()
.stdin('y')
.end('confirm', async () => {
const promptPromise = cli.confirm('yes/no?')
const answer = await promptPromise
await cli.done()
expect(answer).to.equal(true)
})

fancy
.stdout()
.stderr()
.stdin('n')
.end('confirm', async () => {
const promptPromise = cli.confirm('yes/no?')
const answer = await promptPromise
await cli.done()
expect(answer).to.equal(false)
})

fancy
.stdout()
.stderr()
.stdin('x')
.end('gets anykey', async () => {
const promptPromise = cli.anykey()
const answer = await promptPromise
await cli.done()
expect(answer).to.equal('x')
})

fancy
.stdout()
.stderr()
Expand Down
Loading

0 comments on commit 67eee41

Please sign in to comment.