@topcli/prompts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +68 -0
- package/index.d.ts +8 -0
- package/index.js +167 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2023 Pierre Demailly
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
4
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
|
5
|
+
and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
9
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
11
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
12
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
13
|
+
THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# prompts
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://github.com/TopCli/prompts/commit-activity)
|
|
5
|
+
[](https://github.com/TopCli/prompts/blob/main/LICENSE)
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Node.js user input library for command-line interfaces.
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
- [Node.js](https://nodejs.org/en/) v14 or higher
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
> **Note** This package is ESM only.
|
|
16
|
+
|
|
17
|
+
This package is available in the Node Package Repository and can be easily installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or [yarn](https://yarnpkg.com).
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
$ npm i @topcli/prompts
|
|
21
|
+
# or
|
|
22
|
+
$ yarn add @topcli/prompts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage exemple
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { prompt, select, confirm } from '@topcli/prompts'
|
|
29
|
+
|
|
30
|
+
const name = await prompt('What\'s your name ?')
|
|
31
|
+
const gender = await select('What\'s your gender ?', {
|
|
32
|
+
choices: [
|
|
33
|
+
{
|
|
34
|
+
value: 'M',
|
|
35
|
+
label: 'Male'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: 'F',
|
|
39
|
+
label: 'Female'
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
const isAdult = await confirm('Are you over 18 ?', { initial: true })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### `prompt(message: string): Promise<string>`
|
|
49
|
+
|
|
50
|
+
Simple prompt, similar to `rl.question()` with an improved UI.
|
|
51
|
+
|
|
52
|
+
### `select(message: string, options: { choices: (Choice | string)[], maxVisible?: number }): Promise<string>`
|
|
53
|
+
|
|
54
|
+
Scrollable select depending `maxVisible` (default `8`).
|
|
55
|
+
|
|
56
|
+
### `confirm(message: string, options?: { initial: boolean }): Promise<string>`
|
|
57
|
+
|
|
58
|
+
Boolean prompt.
|
|
59
|
+
|
|
60
|
+
## Interfaces
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
export interface Choice {
|
|
64
|
+
value: any,
|
|
65
|
+
label: string,
|
|
66
|
+
description?: string,
|
|
67
|
+
}
|
|
68
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface Choice {
|
|
2
|
+
value: any,
|
|
3
|
+
label: string,
|
|
4
|
+
description?: string,
|
|
5
|
+
}
|
|
6
|
+
export function prompt(message: string): Promise<string>;
|
|
7
|
+
export function select(message: string, options: { choices: (Choice | string)[], maxVisble?: number }): Promise<string>;
|
|
8
|
+
export function confirm(message: string, options?: { initial: boolean }): Promise<string>;
|
package/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises'
|
|
2
|
+
|
|
3
|
+
import ansi from 'ansi-styles'
|
|
4
|
+
|
|
5
|
+
const kQuestionMark = `${ansi.blue.open}?${ansi.blue.close}`
|
|
6
|
+
const kTick = `${ansi.green.open}✔${ansi.green.close}`
|
|
7
|
+
const kCross = `${ansi.red.open}✖${ansi.red.close}`
|
|
8
|
+
const kPointer = `${ansi.gray.open}›${ansi.gray.close}`
|
|
9
|
+
const kActive = `${ansi.reset.open}${kPointer} `
|
|
10
|
+
const kInactive = `${ansi.gray.open} `
|
|
11
|
+
const kPrevious = '⭡'
|
|
12
|
+
const kNext = '⭣'
|
|
13
|
+
const kShowCursor = '\x1B[?25h'
|
|
14
|
+
const kHideCursor = '\x1B[?25l'
|
|
15
|
+
|
|
16
|
+
function clearLastLine () {
|
|
17
|
+
process.stdout.moveCursor(0, -1)
|
|
18
|
+
process.stdout.clearLine()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function prompt (message) {
|
|
22
|
+
if (typeof message !== 'string') {
|
|
23
|
+
throw new TypeError('message must be a string')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { stdin: input, stdout: output } = process
|
|
27
|
+
const rl = createInterface({ input, output })
|
|
28
|
+
|
|
29
|
+
const answer = await rl.question(`${ansi.bold.open} ${kPointer} ${message} ${ansi.bold.close}`)
|
|
30
|
+
|
|
31
|
+
clearLastLine()
|
|
32
|
+
console.log(`${ansi.bold.open}${answer ? kTick : kCross} ${message} ${kPointer} ${ansi.yellow.open}${answer}${ansi.yellow.close}${ansi.bold.close}`)
|
|
33
|
+
|
|
34
|
+
rl.close()
|
|
35
|
+
|
|
36
|
+
return answer
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function select (message, options) {
|
|
40
|
+
if (typeof message !== 'string') {
|
|
41
|
+
throw new TypeError('message must be a string')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!options) {
|
|
45
|
+
throw new TypeError('Missing required options')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { choices } = options
|
|
49
|
+
|
|
50
|
+
if (!choices?.length) {
|
|
51
|
+
throw new TypeError('Missing required param: choices')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const longestChoice = Math.max(...choices.map(choice => {
|
|
55
|
+
if (typeof choice === 'string') {
|
|
56
|
+
return choice.length
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const kRequiredChoiceProperties = ['label', 'value']
|
|
60
|
+
|
|
61
|
+
for (const prop of kRequiredChoiceProperties) {
|
|
62
|
+
if (!choice[prop]) {
|
|
63
|
+
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return choice.label.length
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
process.stdout.write(kHideCursor)
|
|
71
|
+
const { stdin: input, stdout: output } = process
|
|
72
|
+
const rl = createInterface({ input, output })
|
|
73
|
+
|
|
74
|
+
let activeIndex = 0
|
|
75
|
+
|
|
76
|
+
console.log(`${ansi.bold.open}${kQuestionMark} ${message}${ansi.bold.close}`)
|
|
77
|
+
|
|
78
|
+
let lastRender = null
|
|
79
|
+
const render = (initialRender = false, { reset } = {}) => {
|
|
80
|
+
const getVisibleChoices = (currentIndex, total, maxVisible) => {
|
|
81
|
+
maxVisible = maxVisible || total
|
|
82
|
+
|
|
83
|
+
let startIndex = Math.min(total - maxVisible, currentIndex - Math.floor(maxVisible / 2))
|
|
84
|
+
if (startIndex < 0) {
|
|
85
|
+
startIndex = 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const endIndex = Math.min(startIndex + maxVisible, total)
|
|
89
|
+
|
|
90
|
+
return { startIndex, endIndex }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { startIndex, endIndex } = getVisibleChoices(activeIndex, choices.length, options.maxVisible || 8)
|
|
94
|
+
|
|
95
|
+
if (!initialRender) {
|
|
96
|
+
const linesToClear = lastRender.endIndex - lastRender.startIndex
|
|
97
|
+
process.stdout.clearLine(1)
|
|
98
|
+
process.stdout.moveCursor(0, -linesToClear)
|
|
99
|
+
process.stdout.clearLine(1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (reset) {
|
|
103
|
+
clearLastLine()
|
|
104
|
+
clearLastLine()
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
lastRender = { startIndex, endIndex }
|
|
109
|
+
|
|
110
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
111
|
+
const choice = typeof choices[i] === 'string' ? { value: choices[i], label: choices[i] } : choices[i]
|
|
112
|
+
const prefix = `${startIndex > 0 && i === startIndex ? kPrevious : endIndex < choices.length && i === endIndex - 1 ? kNext : ' '}${i === activeIndex ? kActive : kInactive}`
|
|
113
|
+
const str = `${prefix}${choice.label.padEnd(longestChoice < 10 ? longestChoice : 0)}${choice.description ? ` - ${choice.description}` : ''}${ansi.reset.open}`
|
|
114
|
+
console.log(str)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
render(true)
|
|
119
|
+
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const onKeypress = (value, key) => {
|
|
122
|
+
if (key.name === 'up') {
|
|
123
|
+
activeIndex = activeIndex === 0 ? choices.length - 1 : activeIndex - 1
|
|
124
|
+
render()
|
|
125
|
+
} else if (key.name === 'down') {
|
|
126
|
+
activeIndex = activeIndex === choices.length - 1 ? 0 : activeIndex + 1
|
|
127
|
+
render()
|
|
128
|
+
} else if (key.name === 'return') {
|
|
129
|
+
process.stdin.off('keypress', onKeypress)
|
|
130
|
+
render(false, { reset: true })
|
|
131
|
+
|
|
132
|
+
console.log(`${ansi.bold.open}${kTick} ${message} ${kPointer} ${ansi.yellow.open}${choices[activeIndex].value}${ansi.reset.open}`)
|
|
133
|
+
|
|
134
|
+
process.stderr.write(kShowCursor)
|
|
135
|
+
rl.close()
|
|
136
|
+
|
|
137
|
+
resolve(choices[activeIndex].value)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
process.stdin.on('keypress', onKeypress)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const kDefaultConfirmOptions = { initial: false }
|
|
146
|
+
|
|
147
|
+
export async function confirm (message, options = kDefaultConfirmOptions) {
|
|
148
|
+
if (typeof message !== 'string') {
|
|
149
|
+
throw new TypeError('message must be a string')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { initial } = { kDefaultConfirmOptions, ...options }
|
|
153
|
+
|
|
154
|
+
const { stdin: input, stdout: output } = process
|
|
155
|
+
const rl = createInterface({ input, output })
|
|
156
|
+
|
|
157
|
+
const tip = initial ? `${ansi.bold.open}Yes${ansi.bold.close}/no` : `yes/${ansi.bold.open}No${ansi.bold.close}`
|
|
158
|
+
const answer = await rl.question(`${kQuestionMark} ${message} ${ansi.grey.open}(${tip})${ansi.grey.close} ${kPointer} `)
|
|
159
|
+
const result = (!answer && initial) || ['y', 'yes'].includes(answer.toLocaleLowerCase())
|
|
160
|
+
|
|
161
|
+
clearLastLine()
|
|
162
|
+
console.log(`${result ? kTick : kCross} ${message}`)
|
|
163
|
+
|
|
164
|
+
rl.close()
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@topcli/prompts",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Node.js user input library for command-line interfaces.",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test ./test/**.test.js",
|
|
7
|
+
"lint": "standard --fix | snazzy"
|
|
8
|
+
},
|
|
9
|
+
"main": "./index.js",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"node.js",
|
|
12
|
+
"cli",
|
|
13
|
+
"prompt"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"index.d.ts"
|
|
18
|
+
],
|
|
19
|
+
"author": "PierreDemailly <pierredemailly.pro@gmail.com>",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"snazzy": "^9.0.0",
|
|
24
|
+
"standard": "^17.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"ansi-styles": "^6.2.1"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=14"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/TopCli/prompts/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/TopCli/prompts#readme",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|