@nan0web/ui-cli 1.1.1 → 2.1.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 +153 -203
- package/bin/cli.js +11 -0
- package/bin/nan0cli.js +86 -0
- package/package.json +27 -13
- package/src/CLI.js +22 -30
- 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 +241 -312
- 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 +85 -40
- 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,345 @@ 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
|
+
* ## nan0cli — Universal CLI Runner
|
|
116
|
+
*
|
|
117
|
+
* The `nan0cli` binary provides a universal entry point for any nan0web application.
|
|
118
|
+
* It reads the app's `package.json`, resolves the CLI entry point, and runs commands.
|
|
119
|
+
*
|
|
120
|
+
* ### App Contract
|
|
121
|
+
*
|
|
122
|
+
* Your app must export Messages from its entry point:
|
|
123
|
+
*
|
|
124
|
+
* ```js
|
|
125
|
+
* // E1: Messages Array (recommended)
|
|
126
|
+
* export default [Serve, Dump]
|
|
127
|
+
*
|
|
128
|
+
* // E2: Single Message class (auto-wrapped to array)
|
|
129
|
+
* export default class MyApp { }
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* ### Entry Point Resolution
|
|
133
|
+
*
|
|
134
|
+
* `nan0cli` looks for the entry point in this order:
|
|
135
|
+
* 1. `nan0web.cli.entry` field in `package.json`
|
|
136
|
+
* 2. `src/cli.js` (convention)
|
|
137
|
+
* 3. `src/messages/index.js` (legacy)
|
|
138
|
+
*
|
|
139
|
+
* ### Configuration
|
|
140
|
+
*
|
|
141
|
+
* ```json
|
|
142
|
+
* {
|
|
143
|
+
* "nan0web": {
|
|
144
|
+
* "cli": { "entry": "src/cli.js" }
|
|
145
|
+
* }
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
110
148
|
*/
|
|
111
|
-
it(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* pnpm add @nan0web/ui-cli
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
assert.equal(pkg.name, "@nan0web/ui-cli")
|
|
149
|
+
it('nan0cli binary is registered', () => {
|
|
150
|
+
assert.ok(pkg.bin?.nan0cli, 'bin.nan0cli must be defined')
|
|
151
|
+
assert.equal(pkg.bin.nan0cli, './bin/nan0cli.js')
|
|
118
152
|
})
|
|
153
|
+
|
|
119
154
|
/**
|
|
120
155
|
* @docs
|
|
156
|
+
*
|
|
157
|
+
* ### Error Handling
|
|
158
|
+
*
|
|
159
|
+
* When no entry point is found, `nan0cli` displays a styled `Alert` error and exits with code 1.
|
|
160
|
+
* All errors are displayed via `Logger` + `Alert` components — never raw `console.log`.
|
|
121
161
|
*/
|
|
122
|
-
it(
|
|
123
|
-
|
|
124
|
-
* ```bash
|
|
125
|
-
* yarn add @nan0web/ui-cli
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
assert.equal(pkg.name, "@nan0web/ui-cli")
|
|
162
|
+
it('nan0cli is included in package files', () => {
|
|
163
|
+
assert.ok(pkg.files?.includes('bin/**/*.js'), 'bin/**/*.js must be in files array')
|
|
129
164
|
})
|
|
130
165
|
|
|
131
166
|
/**
|
|
132
167
|
* @docs
|
|
133
|
-
* ## Usage
|
|
168
|
+
* ## Usage (V2 Architecture)
|
|
134
169
|
*
|
|
135
|
-
*
|
|
170
|
+
* Starting from v2.0, we recommend using the `render()` function with Composable Components.
|
|
136
171
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* #### requestForm(form, options)
|
|
140
|
-
*
|
|
141
|
-
* Displays a form and collects user input field-by-field with validation.
|
|
172
|
+
* ### Interactive Prompts
|
|
142
173
|
*/
|
|
143
|
-
it("How to request form input via CLiInputAdapter?", async () => {
|
|
144
|
-
//import { CLiInputAdapter } from '@nan0web/ui-cli'
|
|
145
|
-
const adapter = new CLiInputAdapter()
|
|
146
|
-
const fields = [
|
|
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
|
-
})
|
|
170
174
|
|
|
171
|
-
|
|
175
|
+
/**
|
|
176
|
+
* @docs
|
|
177
|
+
* #### Input & Password
|
|
178
|
+
*/
|
|
179
|
+
it('How to use Input and Password components?', async () => {
|
|
180
|
+
//import { render, Input, Password } from '@nan0web/ui-cli'
|
|
181
|
+
const user = await ask('Username')
|
|
182
|
+
console.info(`User: ${user}`) // -> User: Alice
|
|
172
183
|
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
const pass = await ask('Enter Secret:')
|
|
185
|
+
console.info(`Secret: ${pass}`) // -> Secret: secret-key
|
|
186
|
+
|
|
187
|
+
assert.deepStrictEqual(console.output().length, 2)
|
|
175
188
|
})
|
|
189
|
+
|
|
176
190
|
/**
|
|
177
191
|
* @docs
|
|
192
|
+
* #### Select & Multiselect
|
|
178
193
|
*/
|
|
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")
|
|
194
|
+
it('How to use Select component?', async () => {
|
|
195
|
+
//import { render, Select } from '@nan0web/ui-cli'
|
|
196
|
+
const lang = await select({ title: 'Choose Language:' })
|
|
197
|
+
console.info(`Selected: ${lang.value}`) // -> Selected: en
|
|
198
|
+
|
|
199
|
+
assert.deepStrictEqual(console.output()[0][1], 'Selected: en')
|
|
195
200
|
})
|
|
196
201
|
|
|
197
202
|
/**
|
|
198
203
|
* @docs
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* #### `Input` class
|
|
202
|
-
*
|
|
203
|
-
* Holds user input and tracks cancelation events.
|
|
204
|
+
* #### Multiselect
|
|
204
205
|
*/
|
|
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)
|
|
206
|
+
it('How to use Multiselect component?', async () => {
|
|
207
|
+
//import { render, Multiselect } from '@nan0web/ui-cli'
|
|
208
|
+
const roles = ['admin', 'user']
|
|
209
|
+
console.info(`Roles: ${roles.join(', ')}`) // -> Roles: admin, user
|
|
210
|
+
|
|
211
|
+
assert.deepStrictEqual(console.output()[0][1], 'Roles: admin, user')
|
|
218
212
|
})
|
|
219
213
|
|
|
220
214
|
/**
|
|
221
215
|
* @docs
|
|
222
|
-
* ####
|
|
223
|
-
*
|
|
224
|
-
* Prompts the user with a question and returns a promise with the answer.
|
|
216
|
+
* #### Masked Input
|
|
225
217
|
*/
|
|
226
|
-
it(
|
|
227
|
-
//import {
|
|
218
|
+
it('How to use Mask component?', async () => {
|
|
219
|
+
//import { render, Mask } from '@nan0web/ui-cli'
|
|
220
|
+
const phone = '123-456'
|
|
221
|
+
console.info(`Phone: ${phone}`) // -> Phone: 123-456
|
|
228
222
|
|
|
229
|
-
|
|
230
|
-
console.info(result)
|
|
231
|
-
assert.equal(console.output()[0][1], "Alice")
|
|
223
|
+
assert.deepStrictEqual(console.output()[0][1], 'Phone: 123-456')
|
|
232
224
|
})
|
|
233
225
|
|
|
234
226
|
/**
|
|
235
227
|
* @docs
|
|
236
|
-
* ####
|
|
237
|
-
|
|
238
|
-
|
|
228
|
+
* #### Autocomplete
|
|
229
|
+
*/
|
|
230
|
+
it('How to use Autocomplete component?', async () => {
|
|
231
|
+
//import { render, Autocomplete } from '@nan0web/ui-cli'
|
|
232
|
+
const model = 'gpt-4'
|
|
233
|
+
console.info(`Model: ${model}`) // -> Model: gpt-4
|
|
234
|
+
|
|
235
|
+
assert.deepStrictEqual(console.output()[0][1], 'Model: gpt-4')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @docs
|
|
240
|
+
* #### Slider, Toggle & DateTime
|
|
239
241
|
*/
|
|
240
|
-
it(
|
|
241
|
-
//import {
|
|
242
|
-
const
|
|
243
|
-
console.info(
|
|
244
|
-
|
|
242
|
+
it('How to use Slider and Toggle?', async () => {
|
|
243
|
+
//import { render, Slider, Toggle } from '@nan0web/ui-cli'
|
|
244
|
+
const volume = 50
|
|
245
|
+
console.info(`Volume: ${volume}`) // -> Volume: 50
|
|
246
|
+
const active = true
|
|
247
|
+
console.info(`Active: ${active}`) // -> Active: true
|
|
248
|
+
|
|
249
|
+
assert.deepStrictEqual(console.output().length, 2)
|
|
245
250
|
})
|
|
246
251
|
|
|
247
252
|
/**
|
|
248
253
|
* @docs
|
|
249
|
-
* ####
|
|
250
|
-
*
|
|
251
|
-
* Presents options to the user and returns a promise with selection.
|
|
254
|
+
* #### DateTime
|
|
252
255
|
*/
|
|
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")
|
|
256
|
+
it('How to use DateTime component?', async () => {
|
|
257
|
+
//import { render, DateTime } from '@nan0web/ui-cli'
|
|
258
|
+
const date = '2026-02-05'
|
|
259
|
+
console.info(`Date: ${date}`) // -> Date: 2026-02-05
|
|
260
|
+
|
|
261
|
+
assert.deepStrictEqual(console.output()[0][1], 'Date: 2026-02-05')
|
|
265
262
|
})
|
|
266
263
|
|
|
267
264
|
/**
|
|
268
265
|
* @docs
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* Waits for a keypress to continue the process.
|
|
266
|
+
* ### Static Views
|
|
272
267
|
*/
|
|
273
|
-
it(
|
|
274
|
-
//import {
|
|
268
|
+
it('How to render Alerts?', async () => {
|
|
269
|
+
//import { Alert } from '@nan0web/ui-cli'
|
|
270
|
+
console.info('Success Operation') // -> Success Operation
|
|
271
|
+
assert.deepStrictEqual(console.output()[0][1], 'Success Operation')
|
|
272
|
+
})
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
/**
|
|
275
|
+
* @docs
|
|
276
|
+
* #### Dynamic Tables
|
|
277
|
+
*/
|
|
278
|
+
it('How to render Tables?', async () => {
|
|
279
|
+
//import { Table } from '@nan0web/ui-cli'
|
|
280
|
+
const data = [{ id: 1, name: 'Alice' }]
|
|
281
|
+
console.info(data) // -> [ { id: 1, name: 'Alice' } ]
|
|
282
|
+
assert.deepStrictEqual(console.output()[0][1], data)
|
|
279
283
|
})
|
|
280
284
|
|
|
281
285
|
/**
|
|
282
286
|
* @docs
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* Returns a promise that resolves after a given delay.
|
|
287
|
+
* ### Feedback & Progress
|
|
286
288
|
*/
|
|
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)
|
|
289
|
+
it('How to use Spinner?', async () => {
|
|
290
|
+
//import { render, Spinner } from '@nan0web/ui-cli'
|
|
291
|
+
console.info('Loading...') // -> Loading...
|
|
292
|
+
assert.deepStrictEqual(console.output()[0][1], 'Loading...')
|
|
294
293
|
})
|
|
295
294
|
|
|
296
295
|
/**
|
|
297
296
|
* @docs
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* #### `CancelError`
|
|
301
|
-
*
|
|
302
|
-
* Thrown when a user interrupts a process.
|
|
297
|
+
* #### Progress Bars
|
|
303
298
|
*/
|
|
304
|
-
it(
|
|
305
|
-
//import {
|
|
306
|
-
|
|
307
|
-
console.
|
|
308
|
-
assert.equal(console.output()[0][1], "Operation cancelled by user")
|
|
299
|
+
it('How to use ProgressBar?', async () => {
|
|
300
|
+
//import { render, ProgressBar } from '@nan0web/ui-cli'
|
|
301
|
+
console.info('Progress: 100%') // -> Progress: 100%
|
|
302
|
+
assert.deepStrictEqual(console.output()[0][1], 'Progress: 100%')
|
|
309
303
|
})
|
|
310
304
|
|
|
311
305
|
/**
|
|
312
306
|
* @docs
|
|
313
|
-
* ## API
|
|
307
|
+
* ## Legacy API
|
|
314
308
|
*
|
|
315
309
|
* ### 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
310
|
*/
|
|
369
|
-
it(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
311
|
+
it('How to request form input via CLiInputAdapter?', async () => {
|
|
312
|
+
//import { CLiInputAdapter } from '@nan0web/ui-cli'
|
|
313
|
+
const adapter = new CLiInputAdapter()
|
|
314
|
+
const fields = [{ name: 'name', label: 'Full Name' }]
|
|
315
|
+
const form = UiForm.from({
|
|
316
|
+
fields,
|
|
317
|
+
state: {},
|
|
318
|
+
setData: (data) => {
|
|
319
|
+
form.state = data
|
|
320
|
+
return form
|
|
321
|
+
},
|
|
322
|
+
validateValue: () => ({ isValid: true, errors: {} }),
|
|
323
|
+
validate: () => ({ isValid: true, errors: {} }),
|
|
324
|
+
})
|
|
325
|
+
const result = await adapter.requestForm(form, { silent: true })
|
|
326
|
+
console.info(result.form.state) // -> { name: "John Doe" }
|
|
327
|
+
|
|
328
|
+
assert.deepStrictEqual(result.form.state.name, 'John Doe')
|
|
378
329
|
})
|
|
379
330
|
|
|
380
331
|
/**
|
|
381
332
|
* @docs
|
|
382
|
-
*
|
|
333
|
+
* ### Functional Utilities
|
|
383
334
|
*/
|
|
384
|
-
it(
|
|
385
|
-
|
|
335
|
+
it('How to ask a question with ask()?', async () => {
|
|
336
|
+
//import { ask } from "@nan0web/ui-cli"
|
|
337
|
+
const result = await ask('What is your name?')
|
|
338
|
+
console.info(result) // -> Alice
|
|
339
|
+
assert.deepStrictEqual(console.output()[0][1], 'Alice')
|
|
386
340
|
})
|
|
387
341
|
|
|
388
342
|
/**
|
|
389
343
|
* @docs
|
|
390
|
-
*
|
|
391
|
-
*
|
|
344
|
+
* #### Execution Control
|
|
392
345
|
*/
|
|
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
|
-
}
|
|
346
|
+
it('How to pause code execution?', async () => {
|
|
347
|
+
//import { pause } from '@nan0web/ui-cli'
|
|
348
|
+
await pause(10)
|
|
349
|
+
console.info('Done') // -> Done
|
|
350
|
+
assert.deepStrictEqual(console.output()[0][1], 'Done')
|
|
411
351
|
})
|
|
412
352
|
|
|
413
353
|
/**
|
|
414
354
|
* @docs
|
|
415
|
-
* ##
|
|
355
|
+
* ## Playground
|
|
356
|
+
*
|
|
357
|
+
* ```bash
|
|
358
|
+
* npm run play
|
|
359
|
+
* ```
|
|
416
360
|
*/
|
|
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
|
-
}
|
|
361
|
+
it('How to run the playground?', () => {
|
|
362
|
+
assert.ok(pkg.scripts?.play)
|
|
428
363
|
})
|
|
429
364
|
|
|
430
365
|
/**
|
|
431
366
|
* @docs
|
|
432
367
|
* ## License
|
|
368
|
+
*
|
|
369
|
+
* ISC © [Check here](./LICENSE)
|
|
433
370
|
*/
|
|
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
|
-
}
|
|
371
|
+
it('How to check the license?', () => {
|
|
372
|
+
assert.ok(pkg.license === 'ISC')
|
|
441
373
|
})
|
|
442
374
|
}
|
|
443
375
|
|
|
444
|
-
describe(
|
|
376
|
+
describe('README.md testing', testRender)
|
|
445
377
|
|
|
446
|
-
describe(
|
|
447
|
-
let text = ""
|
|
448
|
-
const format = new Intl.NumberFormat("en-US").format
|
|
378
|
+
describe('Rendering README.md', async () => {
|
|
449
379
|
const parser = new DocsParser()
|
|
450
|
-
|
|
451
|
-
await fs.
|
|
452
|
-
const
|
|
453
|
-
await fs.saveDocument(
|
|
454
|
-
|
|
455
|
-
it(
|
|
456
|
-
|
|
457
|
-
assert.ok(text.includes("## License"))
|
|
380
|
+
// Read source as string — Bun strips comments from Function.toString()
|
|
381
|
+
const source = await fs.loadDocument('src/README.md.js', '')
|
|
382
|
+
const text = String(parser.decode(source))
|
|
383
|
+
await fs.saveDocument('README.md', text)
|
|
384
|
+
|
|
385
|
+
it('document is rendered', async () => {
|
|
386
|
+
assert.ok(text.includes('## License'))
|
|
458
387
|
})
|
|
459
388
|
})
|