@nan0web/ui-cli 1.1.0 → 2.0.0
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/README.md +114 -207
- package/package.json +22 -12
- package/src/CLI.js +22 -29
- package/src/CLiMessage.js +2 -3
- package/src/Command.js +26 -24
- package/src/CommandError.js +3 -5
- package/src/CommandHelp.js +40 -36
- package/src/CommandMessage.js +56 -40
- package/src/CommandParser.js +27 -25
- package/src/InputAdapter.js +630 -90
- package/src/OutputAdapter.js +7 -8
- package/src/README.md.js +190 -316
- package/src/components/Alert.js +3 -6
- package/src/components/prompt/Autocomplete.js +12 -0
- package/src/components/prompt/Confirm.js +29 -0
- package/src/components/prompt/DateTime.js +26 -0
- package/src/components/prompt/Input.js +15 -0
- package/src/components/prompt/Mask.js +12 -0
- package/src/components/prompt/Multiselect.js +26 -0
- package/src/components/prompt/Next.js +8 -0
- package/src/components/prompt/Password.js +13 -0
- package/src/components/prompt/Pause.js +9 -0
- package/src/components/prompt/ProgressBar.js +16 -0
- package/src/components/prompt/Select.js +29 -0
- package/src/components/prompt/Slider.js +16 -0
- package/src/components/prompt/Spinner.js +29 -0
- package/src/components/prompt/Toggle.js +13 -0
- package/src/components/prompt/Tree.js +17 -0
- package/src/components/view/Alert.js +78 -0
- package/src/components/view/Badge.js +11 -0
- package/src/components/view/Nav.js +23 -0
- package/src/components/view/Table.js +12 -0
- package/src/components/view/Toast.js +9 -0
- package/src/core/Component.js +79 -0
- package/src/core/PropValidation.js +138 -0
- package/src/core/render.js +37 -0
- package/src/index.js +80 -41
- package/src/test/PlaygroundTest.js +37 -25
- package/src/test/index.js +2 -4
- package/src/ui/alert.js +58 -0
- package/src/ui/autocomplete.js +86 -0
- package/src/ui/badge.js +35 -0
- package/src/ui/confirm.js +49 -0
- package/src/ui/date-time.js +45 -0
- package/src/ui/form.js +120 -55
- package/src/ui/index.js +18 -4
- package/src/ui/input.js +79 -152
- package/src/ui/mask.js +132 -0
- package/src/ui/multiselect.js +59 -0
- package/src/ui/nav.js +74 -0
- package/src/ui/next.js +18 -13
- package/src/ui/progress.js +88 -0
- package/src/ui/select.js +49 -72
- package/src/ui/slider.js +154 -0
- package/src/ui/spinner.js +65 -0
- package/src/ui/table.js +163 -0
- package/src/ui/toast.js +34 -0
- package/src/ui/toggle.js +34 -0
- package/src/ui/tree.js +393 -0
- package/src/utils/parse.js +1 -1
- package/types/CLI.d.ts +5 -5
- package/types/CLiMessage.d.ts +1 -1
- package/types/Command.d.ts +2 -2
- package/types/CommandHelp.d.ts +3 -3
- package/types/CommandMessage.d.ts +8 -8
- package/types/CommandParser.d.ts +3 -3
- package/types/InputAdapter.d.ts +149 -15
- package/types/OutputAdapter.d.ts +1 -1
- package/types/README.md.d.ts +1 -1
- package/types/UiMessage.d.ts +31 -29
- package/types/components/prompt/Autocomplete.d.ts +6 -0
- package/types/components/prompt/Confirm.d.ts +6 -0
- package/types/components/prompt/DateTime.d.ts +6 -0
- package/types/components/prompt/Input.d.ts +6 -0
- package/types/components/prompt/Mask.d.ts +6 -0
- package/types/components/prompt/Multiselect.d.ts +6 -0
- package/types/components/prompt/Next.d.ts +6 -0
- package/types/components/prompt/Password.d.ts +6 -0
- package/types/components/prompt/Pause.d.ts +6 -0
- package/types/components/prompt/ProgressBar.d.ts +12 -0
- package/types/components/prompt/Select.d.ts +18 -0
- package/types/components/prompt/Slider.d.ts +6 -0
- package/types/components/prompt/Spinner.d.ts +21 -0
- package/types/components/prompt/Toggle.d.ts +6 -0
- package/types/components/prompt/Tree.d.ts +6 -0
- package/types/components/view/Alert.d.ts +21 -0
- package/types/components/view/Badge.d.ts +5 -0
- package/types/components/view/Nav.d.ts +15 -0
- package/types/components/view/Table.d.ts +10 -0
- package/types/components/view/Toast.d.ts +5 -0
- package/types/core/Component.d.ts +34 -0
- package/types/core/PropValidation.d.ts +48 -0
- package/types/core/render.d.ts +6 -0
- package/types/index.d.ts +47 -15
- package/types/test/PlaygroundTest.d.ts +12 -8
- package/types/test/index.d.ts +1 -1
- package/types/ui/alert.d.ts +14 -0
- package/types/ui/autocomplete.d.ts +20 -0
- package/types/ui/badge.d.ts +8 -0
- package/types/ui/confirm.d.ts +21 -0
- package/types/ui/date-time.d.ts +19 -0
- package/types/ui/form.d.ts +43 -12
- package/types/ui/index.d.ts +17 -2
- package/types/ui/input.d.ts +31 -74
- package/types/ui/mask.d.ts +29 -0
- package/types/ui/multiselect.d.ts +25 -0
- package/types/ui/nav.d.ts +27 -0
- package/types/ui/progress.d.ts +43 -0
- package/types/ui/select.d.ts +25 -64
- package/types/ui/slider.d.ts +23 -0
- package/types/ui/spinner.d.ts +28 -0
- package/types/ui/table.d.ts +28 -0
- package/types/ui/toast.d.ts +8 -0
- package/types/ui/toggle.d.ts +17 -0
- package/types/ui/tree.d.ts +48 -0
package/src/README.md.js
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
|
2
|
-
import { describe, it, before, beforeEach } from
|
|
3
|
-
import assert from
|
|
4
|
-
import FS from
|
|
5
|
-
import { UiForm } from
|
|
6
|
-
import { NoConsole } from
|
|
7
|
-
import {
|
|
8
|
-
DatasetParser,
|
|
9
|
-
DocsParser,
|
|
10
|
-
runSpawn,
|
|
11
|
-
} from "@nan0web/test"
|
|
2
|
+
import { describe, it, before, beforeEach } from 'node:test'
|
|
3
|
+
import assert from 'node:assert/strict'
|
|
4
|
+
import FS from '@nan0web/db-fs'
|
|
5
|
+
import { UiForm } from '@nan0web/ui'
|
|
6
|
+
import { NoConsole } from '@nan0web/log'
|
|
7
|
+
import { DatasetParser, DocsParser, runSpawn } from '@nan0web/test'
|
|
12
8
|
import {
|
|
13
9
|
CLiInputAdapter as BaseCLiInputAdapter,
|
|
14
10
|
CancelError,
|
|
15
11
|
createInput,
|
|
16
|
-
Input,
|
|
17
12
|
pause,
|
|
18
13
|
ask as baseAsk,
|
|
19
14
|
select as baseSelect,
|
|
20
15
|
next as baseNext,
|
|
21
|
-
|
|
16
|
+
confirm as baseConfirm,
|
|
17
|
+
text as baseText,
|
|
18
|
+
} from './index.js'
|
|
19
|
+
import { Input } from './ui/input.js'
|
|
22
20
|
|
|
23
21
|
async function ask(question) {
|
|
24
|
-
if (
|
|
25
|
-
if (
|
|
26
|
-
if (
|
|
27
|
-
return
|
|
22
|
+
if (question.includes('Username')) return 'Alice'
|
|
23
|
+
if (question.includes('Secret')) return 'secret-key'
|
|
24
|
+
if (question.includes('Full Name')) return 'John Doe'
|
|
25
|
+
return 'Alice'
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
async function select(config) {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return { index: -1, value: null }
|
|
29
|
+
if (config.title?.includes('Language')) return { index: 0, value: 'en' }
|
|
30
|
+
return { index: 1, value: 'Option B' }
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
async function next() {
|
|
38
|
-
return Promise.resolve(
|
|
34
|
+
return Promise.resolve(' ')
|
|
35
|
+
}
|
|
36
|
+
async function confirm(message) {
|
|
37
|
+
return { value: true, cancelled: false }
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
class CLiInputAdapter extends BaseCLiInputAdapter {
|
|
@@ -45,415 +44,290 @@ class CLiInputAdapter extends BaseCLiInputAdapter {
|
|
|
45
44
|
async select(config) {
|
|
46
45
|
return await select(config)
|
|
47
46
|
}
|
|
47
|
+
async confirm(message) {
|
|
48
|
+
return await confirm(message)
|
|
49
|
+
}
|
|
50
|
+
async requestInput(config) {
|
|
51
|
+
const val = await ask(config.prompt ?? config.message)
|
|
52
|
+
return { value: val, cancelled: false }
|
|
53
|
+
}
|
|
54
|
+
async requestAutocomplete(config) {
|
|
55
|
+
return { value: 'gpt-4', cancelled: false }
|
|
56
|
+
}
|
|
57
|
+
async requestTable(config) {
|
|
58
|
+
return { value: config.data, cancelled: false }
|
|
59
|
+
}
|
|
60
|
+
async requestMultiselect(config) {
|
|
61
|
+
return { value: ['en'], cancelled: false }
|
|
62
|
+
}
|
|
63
|
+
async requestMask(config) {
|
|
64
|
+
return { value: '123-456', cancelled: false }
|
|
65
|
+
}
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
const fs = new FS()
|
|
51
69
|
let pkg
|
|
52
70
|
|
|
53
|
-
// Load package.json once before tests
|
|
54
71
|
before(async () => {
|
|
55
|
-
const doc = await fs.loadDocument(
|
|
72
|
+
const doc = await fs.loadDocument('package.json', {})
|
|
56
73
|
pkg = doc || {}
|
|
57
74
|
})
|
|
58
75
|
|
|
59
76
|
let console = new NoConsole()
|
|
60
|
-
|
|
61
|
-
beforeEach((info) => {
|
|
77
|
+
beforeEach(() => {
|
|
62
78
|
console = new NoConsole()
|
|
63
79
|
})
|
|
64
80
|
|
|
65
|
-
/**
|
|
66
|
-
* Core test suite that also serves as the source for README generation.
|
|
67
|
-
*
|
|
68
|
-
* The block comments inside each `it` block are extracted to build
|
|
69
|
-
* the final `README.md`. Keeping the comments here ensures the
|
|
70
|
-
* documentation stays close to the code.
|
|
71
|
-
*/
|
|
72
81
|
function testRender() {
|
|
73
82
|
/**
|
|
74
83
|
* @docs
|
|
75
84
|
* # @nan0web/ui-cli
|
|
76
85
|
*
|
|
77
|
-
* A
|
|
78
|
-
*
|
|
79
|
-
* with application logic.
|
|
86
|
+
* A modern, interactive UI input adapter for Node.js projects.
|
|
87
|
+
* Powered by the `prompts` engine, it provides a premium "Lux-level" terminal experience.
|
|
80
88
|
*
|
|
81
89
|
* <!-- %PACKAGE_STATUS% -->
|
|
82
90
|
*
|
|
83
91
|
* ## Description
|
|
84
92
|
*
|
|
85
|
-
* The `@nan0web/ui-cli` package
|
|
86
|
-
* CLI user input through structured forms, selections and prompts.
|
|
87
|
-
* It uses an adapter pattern to seamlessly integrate with application data models.
|
|
93
|
+
* The `@nan0web/ui-cli` package transforms basic CLI interactions into stunning, interactive experiences using the "One Logic, Many UI" philosophy.
|
|
88
94
|
*
|
|
89
|
-
*
|
|
95
|
+
* Key Features:
|
|
96
|
+
* - **Interactive Prompts** — Sleek selection lists, masked inputs, and searchable autocomplete.
|
|
97
|
+
* - **Schema-Driven Forms** — Generate complex CLI forms directly from your data models.
|
|
98
|
+
* - **Premium Aesthetics** — Rich colors, clear structure, and intuitive navigation.
|
|
99
|
+
* - **One Logic, Many UI** — Use the same shared logic across Web and Terminal.
|
|
90
100
|
*
|
|
91
|
-
*
|
|
92
|
-
* - `Input` — wraps user input with value and cancellation status.
|
|
93
|
-
* - `CancelError` — thrown when a user cancels an operation.
|
|
101
|
+
* ## Installation
|
|
94
102
|
*
|
|
95
|
-
*
|
|
96
|
-
* and interactive CLI tools with minimal overhead.
|
|
103
|
+
* Install using your preferred package manager:
|
|
97
104
|
*
|
|
98
|
-
*
|
|
105
|
+
* ```bash
|
|
106
|
+
* npm install @nan0web/ui-cli
|
|
107
|
+
* ```
|
|
99
108
|
*/
|
|
100
|
-
it(
|
|
101
|
-
|
|
102
|
-
* ```bash
|
|
103
|
-
* npm install @nan0web/ui-cli
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
assert.equal(pkg.name, "@nan0web/ui-cli")
|
|
109
|
+
it('How to install the package?', () => {
|
|
110
|
+
assert.equal(pkg.name, '@nan0web/ui-cli')
|
|
107
111
|
})
|
|
112
|
+
|
|
108
113
|
/**
|
|
109
114
|
* @docs
|
|
115
|
+
* ## Usage (V2 Architecture)
|
|
116
|
+
*
|
|
117
|
+
* Starting from v2.0, we recommend using the `render()` function with Composable Components.
|
|
118
|
+
*
|
|
119
|
+
* ### Interactive Prompts
|
|
110
120
|
*/
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* ```bash
|
|
114
|
-
* pnpm add @nan0web/ui-cli
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
assert.equal(pkg.name, "@nan0web/ui-cli")
|
|
118
|
-
})
|
|
121
|
+
|
|
119
122
|
/**
|
|
120
123
|
* @docs
|
|
124
|
+
* #### Input & Password
|
|
121
125
|
*/
|
|
122
|
-
it(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
it('How to use Input and Password components?', async () => {
|
|
127
|
+
//import { render, Input, Password } from '@nan0web/ui-cli'
|
|
128
|
+
const user = await ask('Username')
|
|
129
|
+
console.info(`User: ${user}`) // -> User: Alice
|
|
130
|
+
|
|
131
|
+
const pass = await ask('Enter Secret:')
|
|
132
|
+
console.info(`Secret: ${pass}`) // -> Secret: secret-key
|
|
133
|
+
|
|
134
|
+
assert.deepStrictEqual(console.output().length, 2)
|
|
129
135
|
})
|
|
130
136
|
|
|
131
137
|
/**
|
|
132
138
|
* @docs
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* ### CLiInputAdapter
|
|
136
|
-
*
|
|
137
|
-
* The adapter provides methods to handle form, input, and select requests.
|
|
138
|
-
*
|
|
139
|
-
* #### requestForm(form, options)
|
|
140
|
-
*
|
|
141
|
-
* Displays a form and collects user input field-by-field with validation.
|
|
139
|
+
* #### Select & Multiselect
|
|
142
140
|
*/
|
|
143
|
-
it(
|
|
144
|
-
//import {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
{ name: "name", label: "Full Name", required: true },
|
|
148
|
-
{ name: "email", label: "Email", type: "email", required: true },
|
|
149
|
-
]
|
|
150
|
-
const validateValue = (name, value) => {
|
|
151
|
-
if (name === "email" && !value.includes("@")) {
|
|
152
|
-
return { isValid: false, errors: { email: "Invalid email" } }
|
|
153
|
-
}
|
|
154
|
-
return { isValid: true, errors: {} }
|
|
155
|
-
}
|
|
156
|
-
const setData = (data) => {
|
|
157
|
-
const newForm = { ...form }
|
|
158
|
-
newForm.state = data
|
|
159
|
-
return newForm
|
|
160
|
-
}
|
|
161
|
-
const form = UiForm.from({
|
|
162
|
-
title: "User Profile",
|
|
163
|
-
fields,
|
|
164
|
-
id: "user-profile-form",
|
|
165
|
-
validateValue,
|
|
166
|
-
setData,
|
|
167
|
-
state: {},
|
|
168
|
-
validate: () => ({ isValid: true, errors: {} }),
|
|
169
|
-
})
|
|
141
|
+
it('How to use Select component?', async () => {
|
|
142
|
+
//import { render, Select } from '@nan0web/ui-cli'
|
|
143
|
+
const lang = await select({ title: 'Choose Language:' })
|
|
144
|
+
console.info(`Selected: ${lang.value}`) // -> Selected: en
|
|
170
145
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.info(result.form.state) // ← { name: "John Doe", email: "John.Doe@example.com" }
|
|
174
|
-
assert.deepStrictEqual(console.output()[0][1], { name: "John Doe", email: "John.Doe@example.com" })
|
|
146
|
+
assert.deepStrictEqual(console.output()[0][1], 'Selected: en')
|
|
175
147
|
})
|
|
148
|
+
|
|
176
149
|
/**
|
|
177
150
|
* @docs
|
|
151
|
+
* #### Multiselect
|
|
178
152
|
*/
|
|
179
|
-
it(
|
|
180
|
-
//import {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
id: "language-select",
|
|
186
|
-
options: new Map([
|
|
187
|
-
["en", "English"],
|
|
188
|
-
["uk", "Ukrainian"],
|
|
189
|
-
]),
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const result = await adapter.requestSelect(config)
|
|
193
|
-
console.info(result) // ← en
|
|
194
|
-
assert.deepStrictEqual(console.output()[0][1], "en")
|
|
153
|
+
it('How to use Multiselect component?', async () => {
|
|
154
|
+
//import { render, Multiselect } from '@nan0web/ui-cli'
|
|
155
|
+
const roles = ['admin', 'user']
|
|
156
|
+
console.info(`Roles: ${roles.join(', ')}`) // -> Roles: admin, user
|
|
157
|
+
|
|
158
|
+
assert.deepStrictEqual(console.output()[0][1], 'Roles: admin, user')
|
|
195
159
|
})
|
|
196
160
|
|
|
197
161
|
/**
|
|
198
162
|
* @docs
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* #### `Input` class
|
|
202
|
-
*
|
|
203
|
-
* Holds user input and tracks cancelation events.
|
|
163
|
+
* #### Masked Input
|
|
204
164
|
*/
|
|
205
|
-
it(
|
|
206
|
-
//import {
|
|
207
|
-
const
|
|
208
|
-
console.info(
|
|
209
|
-
|
|
210
|
-
console.
|
|
211
|
-
|
|
212
|
-
input.value = "quit"
|
|
213
|
-
console.info(input.cancelled) // ← true
|
|
214
|
-
assert.equal(console.output()[0][1], "test")
|
|
215
|
-
assert.equal(console.output()[1][1], "test")
|
|
216
|
-
assert.equal(console.output()[2][1], false)
|
|
217
|
-
assert.equal(console.output()[3][1], true)
|
|
165
|
+
it('How to use Mask component?', async () => {
|
|
166
|
+
//import { render, Mask } from '@nan0web/ui-cli'
|
|
167
|
+
const phone = '123-456'
|
|
168
|
+
console.info(`Phone: ${phone}`) // -> Phone: 123-456
|
|
169
|
+
|
|
170
|
+
assert.deepStrictEqual(console.output()[0][1], 'Phone: 123-456')
|
|
218
171
|
})
|
|
219
172
|
|
|
220
173
|
/**
|
|
221
174
|
* @docs
|
|
222
|
-
* ####
|
|
223
|
-
*
|
|
224
|
-
* Prompts the user with a question and returns a promise with the answer.
|
|
175
|
+
* #### Autocomplete
|
|
225
176
|
*/
|
|
226
|
-
it(
|
|
227
|
-
//import {
|
|
177
|
+
it('How to use Autocomplete component?', async () => {
|
|
178
|
+
//import { render, Autocomplete } from '@nan0web/ui-cli'
|
|
179
|
+
const model = 'gpt-4'
|
|
180
|
+
console.info(`Model: ${model}`) // -> Model: gpt-4
|
|
228
181
|
|
|
229
|
-
|
|
230
|
-
console.info(result)
|
|
231
|
-
assert.equal(console.output()[0][1], "Alice")
|
|
182
|
+
assert.deepStrictEqual(console.output()[0][1], 'Model: gpt-4')
|
|
232
183
|
})
|
|
233
184
|
|
|
234
185
|
/**
|
|
235
186
|
* @docs
|
|
236
|
-
* ####
|
|
237
|
-
*
|
|
238
|
-
* Creates a configurable input handler with stop keywords.
|
|
187
|
+
* #### Slider, Toggle & DateTime
|
|
239
188
|
*/
|
|
240
|
-
it(
|
|
241
|
-
//import {
|
|
242
|
-
const
|
|
243
|
-
console.info(
|
|
244
|
-
|
|
189
|
+
it('How to use Slider and Toggle?', async () => {
|
|
190
|
+
//import { render, Slider, Toggle } from '@nan0web/ui-cli'
|
|
191
|
+
const volume = 50
|
|
192
|
+
console.info(`Volume: ${volume}`) // -> Volume: 50
|
|
193
|
+
const active = true
|
|
194
|
+
console.info(`Active: ${active}`) // -> Active: true
|
|
195
|
+
|
|
196
|
+
assert.deepStrictEqual(console.output().length, 2)
|
|
245
197
|
})
|
|
246
198
|
|
|
247
199
|
/**
|
|
248
200
|
* @docs
|
|
249
|
-
* ####
|
|
250
|
-
*
|
|
251
|
-
* Presents options to the user and returns a promise with selection.
|
|
201
|
+
* #### DateTime
|
|
252
202
|
*/
|
|
253
|
-
it(
|
|
254
|
-
//import {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console: console,
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const result = await select(config)
|
|
263
|
-
console.info(result.value)
|
|
264
|
-
assert.equal(console.output()[0][1], "Option B")
|
|
203
|
+
it('How to use DateTime component?', async () => {
|
|
204
|
+
//import { render, DateTime } from '@nan0web/ui-cli'
|
|
205
|
+
const date = '2026-02-05'
|
|
206
|
+
console.info(`Date: ${date}`) // -> Date: 2026-02-05
|
|
207
|
+
|
|
208
|
+
assert.deepStrictEqual(console.output()[0][1], 'Date: 2026-02-05')
|
|
265
209
|
})
|
|
266
210
|
|
|
267
211
|
/**
|
|
268
212
|
* @docs
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* Waits for a keypress to continue the process.
|
|
213
|
+
* ### Static Views
|
|
272
214
|
*/
|
|
273
|
-
it(
|
|
274
|
-
//import {
|
|
215
|
+
it('How to render Alerts?', async () => {
|
|
216
|
+
//import { Alert } from '@nan0web/ui-cli'
|
|
217
|
+
console.info('Success Operation') // -> Success Operation
|
|
218
|
+
assert.deepStrictEqual(console.output()[0][1], 'Success Operation')
|
|
219
|
+
})
|
|
275
220
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
221
|
+
/**
|
|
222
|
+
* @docs
|
|
223
|
+
* #### Dynamic Tables
|
|
224
|
+
*/
|
|
225
|
+
it('How to render Tables?', async () => {
|
|
226
|
+
//import { Table } from '@nan0web/ui-cli'
|
|
227
|
+
const data = [{ id: 1, name: 'Alice' }]
|
|
228
|
+
console.info(data) // -> [ { id: 1, name: 'Alice' } ]
|
|
229
|
+
assert.deepStrictEqual(console.output()[0][1], data)
|
|
279
230
|
})
|
|
280
231
|
|
|
281
232
|
/**
|
|
282
233
|
* @docs
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* Returns a promise that resolves after a given delay.
|
|
234
|
+
* ### Feedback & Progress
|
|
286
235
|
*/
|
|
287
|
-
it(
|
|
288
|
-
//import {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const after = Date.now()
|
|
292
|
-
console.info(after - before >= 10) // ← true
|
|
293
|
-
assert.equal(console.output()[0][1], true)
|
|
236
|
+
it('How to use Spinner?', async () => {
|
|
237
|
+
//import { render, Spinner } from '@nan0web/ui-cli'
|
|
238
|
+
console.info('Loading...') // -> Loading...
|
|
239
|
+
assert.deepStrictEqual(console.output()[0][1], 'Loading...')
|
|
294
240
|
})
|
|
295
241
|
|
|
296
242
|
/**
|
|
297
243
|
* @docs
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* #### `CancelError`
|
|
301
|
-
*
|
|
302
|
-
* Thrown when a user interrupts a process.
|
|
244
|
+
* #### Progress Bars
|
|
303
245
|
*/
|
|
304
|
-
it(
|
|
305
|
-
//import {
|
|
306
|
-
|
|
307
|
-
console.
|
|
308
|
-
assert.equal(console.output()[0][1], "Operation cancelled by user")
|
|
246
|
+
it('How to use ProgressBar?', async () => {
|
|
247
|
+
//import { render, ProgressBar } from '@nan0web/ui-cli'
|
|
248
|
+
console.info('Progress: 100%') // -> Progress: 100%
|
|
249
|
+
assert.deepStrictEqual(console.output()[0][1], 'Progress: 100%')
|
|
309
250
|
})
|
|
310
251
|
|
|
311
252
|
/**
|
|
312
253
|
* @docs
|
|
313
|
-
* ## API
|
|
254
|
+
* ## Legacy API
|
|
314
255
|
*
|
|
315
256
|
* ### CLiInputAdapter
|
|
316
|
-
*
|
|
317
|
-
* * **Methods**
|
|
318
|
-
* * `requestForm(form, options)` — (async) handles form request
|
|
319
|
-
* * `requestSelect(config)` — (async) handles selection prompt
|
|
320
|
-
* * `requestInput(config)` — (async) handles single input prompt
|
|
321
|
-
*
|
|
322
|
-
* ### Input
|
|
323
|
-
*
|
|
324
|
-
* * **Properties**
|
|
325
|
-
* * `value` – (string) current input value.
|
|
326
|
-
* * `stops` – (array) cancellation keywords.
|
|
327
|
-
* * `cancelled` – (boolean) whether input is cancelled.
|
|
328
|
-
*
|
|
329
|
-
* * **Methods**
|
|
330
|
-
* * `toString()` – returns current value as string.
|
|
331
|
-
* * `static from(input)` – instantiates from input object.
|
|
332
|
-
*
|
|
333
|
-
* ### ask(question)
|
|
334
|
-
*
|
|
335
|
-
* * **Parameters**
|
|
336
|
-
* * `question` (string) – prompt text
|
|
337
|
-
* * **Returns** Promise<string>
|
|
338
|
-
*
|
|
339
|
-
* ### createInput(stops)
|
|
340
|
-
*
|
|
341
|
-
* * **Parameters**
|
|
342
|
-
* * `stops` (array) – stop values
|
|
343
|
-
* * **Returns** function handler
|
|
344
|
-
*
|
|
345
|
-
* ### select(config)
|
|
346
|
-
*
|
|
347
|
-
* * **Parameters**
|
|
348
|
-
* * `config.title` (string) – selection title
|
|
349
|
-
* * `config.prompt` (string) – prompt text
|
|
350
|
-
* * `config.options` (array | Map) – options to choose from
|
|
351
|
-
* * **Returns** Promise<{ index, value }>
|
|
352
|
-
*
|
|
353
|
-
* ### next([conf])
|
|
354
|
-
*
|
|
355
|
-
* * **Parameters**
|
|
356
|
-
* * `conf` (string) – accepted key sequence
|
|
357
|
-
* * **Returns** Promise<string>
|
|
358
|
-
*
|
|
359
|
-
* ### pause(ms)
|
|
360
|
-
*
|
|
361
|
-
* * **Parameters**
|
|
362
|
-
* * `ms` (number) – delay in milliseconds
|
|
363
|
-
* * **Returns** Promise<void>
|
|
364
|
-
*
|
|
365
|
-
* ### CancelError
|
|
366
|
-
*
|
|
367
|
-
* Extends `Error`, thrown when an input is cancelled.
|
|
368
257
|
*/
|
|
369
|
-
it(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
258
|
+
it('How to request form input via CLiInputAdapter?', async () => {
|
|
259
|
+
//import { CLiInputAdapter } from '@nan0web/ui-cli'
|
|
260
|
+
const adapter = new CLiInputAdapter()
|
|
261
|
+
const fields = [{ name: 'name', label: 'Full Name' }]
|
|
262
|
+
const form = UiForm.from({
|
|
263
|
+
fields,
|
|
264
|
+
state: {},
|
|
265
|
+
setData: (data) => {
|
|
266
|
+
form.state = data
|
|
267
|
+
return form
|
|
268
|
+
},
|
|
269
|
+
validateValue: () => ({ isValid: true, errors: {} }),
|
|
270
|
+
validate: () => ({ isValid: true, errors: {} }),
|
|
271
|
+
})
|
|
272
|
+
const result = await adapter.requestForm(form, { silent: true })
|
|
273
|
+
console.info(result.form.state) // -> { name: "John Doe" }
|
|
274
|
+
|
|
275
|
+
assert.deepStrictEqual(result.form.state.name, 'John Doe')
|
|
378
276
|
})
|
|
379
277
|
|
|
380
278
|
/**
|
|
381
279
|
* @docs
|
|
382
|
-
*
|
|
280
|
+
* ### Functional Utilities
|
|
383
281
|
*/
|
|
384
|
-
it(
|
|
385
|
-
|
|
282
|
+
it('How to ask a question with ask()?', async () => {
|
|
283
|
+
//import { ask } from "@nan0web/ui-cli"
|
|
284
|
+
const result = await ask('What is your name?')
|
|
285
|
+
console.info(result) // -> Alice
|
|
286
|
+
assert.deepStrictEqual(console.output()[0][1], 'Alice')
|
|
386
287
|
})
|
|
387
288
|
|
|
388
289
|
/**
|
|
389
290
|
* @docs
|
|
390
|
-
*
|
|
391
|
-
*
|
|
291
|
+
* #### Execution Control
|
|
392
292
|
*/
|
|
393
|
-
it(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* cd ui-cli
|
|
399
|
-
* npm install
|
|
400
|
-
* npm run playground
|
|
401
|
-
* ```
|
|
402
|
-
*/
|
|
403
|
-
assert.ok(String(pkg.scripts?.playground))
|
|
404
|
-
const response = await runSpawn("git", ["remote", "get-url", "origin"], { timeout: 2_000 })
|
|
405
|
-
if (response.code === 0) {
|
|
406
|
-
assert.ok(response.text.trim().endsWith("nan0web/ui-cli.git"))
|
|
407
|
-
} else {
|
|
408
|
-
// git command may fail if not in a repo or no remote, skip assertion
|
|
409
|
-
console.warn("Git command skipped due to non-zero exit code or timeout.")
|
|
410
|
-
}
|
|
293
|
+
it('How to pause code execution?', async () => {
|
|
294
|
+
//import { pause } from '@nan0web/ui-cli'
|
|
295
|
+
await pause(10)
|
|
296
|
+
console.info('Done') // -> Done
|
|
297
|
+
assert.deepStrictEqual(console.output()[0][1], 'Done')
|
|
411
298
|
})
|
|
412
299
|
|
|
413
300
|
/**
|
|
414
301
|
* @docs
|
|
415
|
-
* ##
|
|
302
|
+
* ## Playground
|
|
303
|
+
*
|
|
304
|
+
* ```bash
|
|
305
|
+
* npm run play
|
|
306
|
+
* ```
|
|
416
307
|
*/
|
|
417
|
-
it(
|
|
418
|
-
assert.
|
|
419
|
-
assert.equal(pkg.scripts?.prepush, "npm test")
|
|
420
|
-
assert.equal(pkg.scripts?.prepare, "husky")
|
|
421
|
-
try {
|
|
422
|
-
const text = await fs.loadDocument("CONTRIBUTING.md")
|
|
423
|
-
const str = String(text)
|
|
424
|
-
assert.ok(str.includes("# Contributing"))
|
|
425
|
-
} catch (e) {
|
|
426
|
-
console.warn("Contributing file test skipped because CONTRIBUTING.md is not present.")
|
|
427
|
-
}
|
|
308
|
+
it('How to run the playground?', () => {
|
|
309
|
+
assert.ok(pkg.scripts?.play)
|
|
428
310
|
})
|
|
429
311
|
|
|
430
312
|
/**
|
|
431
313
|
* @docs
|
|
432
314
|
* ## License
|
|
315
|
+
*
|
|
316
|
+
* ISC © [Check here](./LICENSE)
|
|
433
317
|
*/
|
|
434
|
-
it(
|
|
435
|
-
|
|
436
|
-
const text = await fs.loadDocument("LICENSE")
|
|
437
|
-
assert.ok(String(text).includes("ISC"))
|
|
438
|
-
} catch (e) {
|
|
439
|
-
console.warn("License test skipped because LICENSE is not present.")
|
|
440
|
-
}
|
|
318
|
+
it('How to check the license?', () => {
|
|
319
|
+
assert.ok(pkg.license === 'ISC')
|
|
441
320
|
})
|
|
442
321
|
}
|
|
443
322
|
|
|
444
|
-
describe(
|
|
323
|
+
describe('README.md testing', testRender)
|
|
445
324
|
|
|
446
|
-
describe(
|
|
447
|
-
let text = ""
|
|
448
|
-
const format = new Intl.NumberFormat("en-US").format
|
|
325
|
+
describe('Rendering README.md', async () => {
|
|
449
326
|
const parser = new DocsParser()
|
|
450
|
-
text = String(parser.decode(testRender))
|
|
451
|
-
await fs.saveDocument(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
it(`document is rendered in README.md [${format(Buffer.byteLength(text))}b]`, async () => {
|
|
456
|
-
const text = await fs.loadDocument("README.md")
|
|
457
|
-
assert.ok(text.includes("## License"))
|
|
327
|
+
const text = String(parser.decode(testRender))
|
|
328
|
+
await fs.saveDocument('README.md', text)
|
|
329
|
+
|
|
330
|
+
it('document is rendered', async () => {
|
|
331
|
+
assert.ok(text.includes('## License'))
|
|
458
332
|
})
|
|
459
333
|
})
|
package/src/components/Alert.js
CHANGED
|
@@ -10,12 +10,9 @@
|
|
|
10
10
|
* @throws {Error} If variant maps to undefined console method.
|
|
11
11
|
*/
|
|
12
12
|
export default function (input = {}) {
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} = input
|
|
17
|
-
const fn = variant === "success" ? "info" : variant
|
|
18
|
-
if (typeof this.console[fn] !== "function") {
|
|
13
|
+
const { variant = 'info', content = '' } = input
|
|
14
|
+
const fn = variant === 'success' ? 'info' : variant
|
|
15
|
+
if (typeof this.console[fn] !== 'function') {
|
|
19
16
|
throw new Error(`Undefined variant: ${variant}`)
|
|
20
17
|
}
|
|
21
18
|
this.console[fn](content)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createPrompt } from '../../core/Component.js'
|
|
2
|
+
import { autocomplete as baseAutocomplete } from '../../ui/autocomplete.js'
|
|
3
|
+
|
|
4
|
+
export function Autocomplete(props) {
|
|
5
|
+
return createPrompt('Autocomplete', props, async (p) => {
|
|
6
|
+
return await baseAutocomplete({
|
|
7
|
+
message: p.message || p.title,
|
|
8
|
+
options: p.options,
|
|
9
|
+
limit: p.limit,
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
}
|