@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.
Files changed (115) hide show
  1. package/README.md +114 -207
  2. package/package.json +22 -12
  3. package/src/CLI.js +22 -29
  4. package/src/CLiMessage.js +2 -3
  5. package/src/Command.js +26 -24
  6. package/src/CommandError.js +3 -5
  7. package/src/CommandHelp.js +40 -36
  8. package/src/CommandMessage.js +56 -40
  9. package/src/CommandParser.js +27 -25
  10. package/src/InputAdapter.js +630 -90
  11. package/src/OutputAdapter.js +7 -8
  12. package/src/README.md.js +190 -316
  13. package/src/components/Alert.js +3 -6
  14. package/src/components/prompt/Autocomplete.js +12 -0
  15. package/src/components/prompt/Confirm.js +29 -0
  16. package/src/components/prompt/DateTime.js +26 -0
  17. package/src/components/prompt/Input.js +15 -0
  18. package/src/components/prompt/Mask.js +12 -0
  19. package/src/components/prompt/Multiselect.js +26 -0
  20. package/src/components/prompt/Next.js +8 -0
  21. package/src/components/prompt/Password.js +13 -0
  22. package/src/components/prompt/Pause.js +9 -0
  23. package/src/components/prompt/ProgressBar.js +16 -0
  24. package/src/components/prompt/Select.js +29 -0
  25. package/src/components/prompt/Slider.js +16 -0
  26. package/src/components/prompt/Spinner.js +29 -0
  27. package/src/components/prompt/Toggle.js +13 -0
  28. package/src/components/prompt/Tree.js +17 -0
  29. package/src/components/view/Alert.js +78 -0
  30. package/src/components/view/Badge.js +11 -0
  31. package/src/components/view/Nav.js +23 -0
  32. package/src/components/view/Table.js +12 -0
  33. package/src/components/view/Toast.js +9 -0
  34. package/src/core/Component.js +79 -0
  35. package/src/core/PropValidation.js +138 -0
  36. package/src/core/render.js +37 -0
  37. package/src/index.js +80 -41
  38. package/src/test/PlaygroundTest.js +37 -25
  39. package/src/test/index.js +2 -4
  40. package/src/ui/alert.js +58 -0
  41. package/src/ui/autocomplete.js +86 -0
  42. package/src/ui/badge.js +35 -0
  43. package/src/ui/confirm.js +49 -0
  44. package/src/ui/date-time.js +45 -0
  45. package/src/ui/form.js +120 -55
  46. package/src/ui/index.js +18 -4
  47. package/src/ui/input.js +79 -152
  48. package/src/ui/mask.js +132 -0
  49. package/src/ui/multiselect.js +59 -0
  50. package/src/ui/nav.js +74 -0
  51. package/src/ui/next.js +18 -13
  52. package/src/ui/progress.js +88 -0
  53. package/src/ui/select.js +49 -72
  54. package/src/ui/slider.js +154 -0
  55. package/src/ui/spinner.js +65 -0
  56. package/src/ui/table.js +163 -0
  57. package/src/ui/toast.js +34 -0
  58. package/src/ui/toggle.js +34 -0
  59. package/src/ui/tree.js +393 -0
  60. package/src/utils/parse.js +1 -1
  61. package/types/CLI.d.ts +5 -5
  62. package/types/CLiMessage.d.ts +1 -1
  63. package/types/Command.d.ts +2 -2
  64. package/types/CommandHelp.d.ts +3 -3
  65. package/types/CommandMessage.d.ts +8 -8
  66. package/types/CommandParser.d.ts +3 -3
  67. package/types/InputAdapter.d.ts +149 -15
  68. package/types/OutputAdapter.d.ts +1 -1
  69. package/types/README.md.d.ts +1 -1
  70. package/types/UiMessage.d.ts +31 -29
  71. package/types/components/prompt/Autocomplete.d.ts +6 -0
  72. package/types/components/prompt/Confirm.d.ts +6 -0
  73. package/types/components/prompt/DateTime.d.ts +6 -0
  74. package/types/components/prompt/Input.d.ts +6 -0
  75. package/types/components/prompt/Mask.d.ts +6 -0
  76. package/types/components/prompt/Multiselect.d.ts +6 -0
  77. package/types/components/prompt/Next.d.ts +6 -0
  78. package/types/components/prompt/Password.d.ts +6 -0
  79. package/types/components/prompt/Pause.d.ts +6 -0
  80. package/types/components/prompt/ProgressBar.d.ts +12 -0
  81. package/types/components/prompt/Select.d.ts +18 -0
  82. package/types/components/prompt/Slider.d.ts +6 -0
  83. package/types/components/prompt/Spinner.d.ts +21 -0
  84. package/types/components/prompt/Toggle.d.ts +6 -0
  85. package/types/components/prompt/Tree.d.ts +6 -0
  86. package/types/components/view/Alert.d.ts +21 -0
  87. package/types/components/view/Badge.d.ts +5 -0
  88. package/types/components/view/Nav.d.ts +15 -0
  89. package/types/components/view/Table.d.ts +10 -0
  90. package/types/components/view/Toast.d.ts +5 -0
  91. package/types/core/Component.d.ts +34 -0
  92. package/types/core/PropValidation.d.ts +48 -0
  93. package/types/core/render.d.ts +6 -0
  94. package/types/index.d.ts +47 -15
  95. package/types/test/PlaygroundTest.d.ts +12 -8
  96. package/types/test/index.d.ts +1 -1
  97. package/types/ui/alert.d.ts +14 -0
  98. package/types/ui/autocomplete.d.ts +20 -0
  99. package/types/ui/badge.d.ts +8 -0
  100. package/types/ui/confirm.d.ts +21 -0
  101. package/types/ui/date-time.d.ts +19 -0
  102. package/types/ui/form.d.ts +43 -12
  103. package/types/ui/index.d.ts +17 -2
  104. package/types/ui/input.d.ts +31 -74
  105. package/types/ui/mask.d.ts +29 -0
  106. package/types/ui/multiselect.d.ts +25 -0
  107. package/types/ui/nav.d.ts +27 -0
  108. package/types/ui/progress.d.ts +43 -0
  109. package/types/ui/select.d.ts +25 -64
  110. package/types/ui/slider.d.ts +23 -0
  111. package/types/ui/spinner.d.ts +28 -0
  112. package/types/ui/table.d.ts +28 -0
  113. package/types/ui/toast.d.ts +8 -0
  114. package/types/ui/toggle.d.ts +17 -0
  115. 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 "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 {
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
- } from "./index.js"
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 ("Full Name *: " === question) return "John Doe"
25
- if ("Email *: " === question) return "John.Doe@example.com"
26
- if ("What is your name?" === question) return "Alice"
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 ("Choose Language:" === config.title) return { index: 0, value: "en" }
32
- if ("Choose an option:" === config.title) return { index: 2, value: "Option B" }
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("package.json", {})
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 tiny, zero‑dependency UI input adapter for Java•Script projects.
78
- * It provides a CLI implementation that can be easily integrated
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 provides a set of tools for handling
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
- * Core classes:
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
- * - `CLiInputAdapter` — handles form, input, and select requests in CLI.
92
- * - `Input` — wraps user input with value and cancellation status.
93
- * - `CancelError` — thrown when a user cancels an operation.
101
+ * ## Installation
94
102
  *
95
- * These classes are perfect for building prompts, wizards, forms,
96
- * and interactive CLI tools with minimal overhead.
103
+ * Install using your preferred package manager:
97
104
  *
98
- * ## Installation
105
+ * ```bash
106
+ * npm install @nan0web/ui-cli
107
+ * ```
99
108
  */
100
- it("How to install with npm?", () => {
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
- it("How to install with pnpm?", () => {
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("How to install with yarn?", () => {
123
- /**
124
- * ```bash
125
- * yarn add @nan0web/ui-cli
126
- * ```
127
- */
128
- assert.equal(pkg.name, "@nan0web/ui-cli")
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
- * ## Usage
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("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
- })
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
- const result = await adapter.requestForm(form, { silent: true })
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("How to request select input via CLiInputAdapter?", async () => {
180
- //import { CLiInputAdapter } from '@nan0web/ui-cli'
181
- const adapter = new CLiInputAdapter()
182
- const config = {
183
- title: "Choose Language:",
184
- prompt: "Language (1-2): ",
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
- * ### Input Utilities
200
- *
201
- * #### `Input` class
202
- *
203
- * Holds user input and tracks cancelation events.
163
+ * #### Masked Input
204
164
  */
205
- it("How to use the Input class?", () => {
206
- //import { Input } from '@nan0web/ui-cli'
207
- const input = new Input({ value: "test", stops: ["quit"] })
208
- console.info(String(input)) // test
209
- console.info(input.value) // ← test
210
- console.info(input.cancelled) // ← false
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
- * #### `ask(question)`
223
- *
224
- * Prompts the user with a question and returns a promise with the answer.
175
+ * #### Autocomplete
225
176
  */
226
- it("How to ask a question with ask()?", async () => {
227
- //import { ask } from "@nan0web/ui-cli"
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
- const result = await ask("What is your name?")
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
- * #### `createInput(stops)`
237
- *
238
- * Creates a configurable input handler with stop keywords.
187
+ * #### Slider, Toggle & DateTime
239
188
  */
240
- it("How to use createInput handler?", () => {
241
- //import { createInput } from '@nan0web/ui-cli'
242
- const handler = createInput(["cancel"])
243
- console.info(typeof handler === "function") // true
244
- assert.equal(console.output()[0][1], true)
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
- * #### `select(config)`
250
- *
251
- * Presents options to the user and returns a promise with selection.
201
+ * #### DateTime
252
202
  */
253
- it("How to prompt user with select()?", async () => {
254
- //import { select } from '@nan0web/ui-cli'
255
- const config = {
256
- title: "Choose an option:",
257
- prompt: "Selection (1-3): ",
258
- options: ["Option A", "Option B", "Option C"],
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
- * #### `next(conf)`
270
- *
271
- * Waits for a keypress to continue the process.
213
+ * ### Static Views
272
214
  */
273
- it("How to pause and wait for keypress with next()?", async () => {
274
- //import { next } from '@nan0web/ui-cli'
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
- const result = await next()
277
- console.info(typeof result === "string")
278
- assert.equal(console.output()[0][1], true)
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
- * #### `pause(ms)`
284
- *
285
- * Returns a promise that resolves after a given delay.
234
+ * ### Feedback & Progress
286
235
  */
287
- it("How to delay execution with pause()?", async () => {
288
- //import { pause } from '@nan0web/ui-cli'
289
- const before = Date.now()
290
- await pause(10)
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
- * ### Errors
299
- *
300
- * #### `CancelError`
301
- *
302
- * Thrown when a user interrupts a process.
244
+ * #### Progress Bars
303
245
  */
304
- it("How to handle CancelError?", () => {
305
- //import { CancelError } from '@nan0web/ui-cli'
306
- const error = new CancelError()
307
- console.error(error.message) // ← Operation cancelled by user
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("All exported classes and functions should pass basic tests", () => {
370
- assert.ok(CLiInputAdapter)
371
- assert.ok(CancelError)
372
- assert.ok(createInput)
373
- assert.ok(baseAsk)
374
- assert.ok(Input)
375
- assert.ok(baseSelect)
376
- assert.ok(baseNext)
377
- assert.ok(pause)
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
- * ## Java•Script
280
+ * ### Functional Utilities
383
281
  */
384
- it("Uses `d.ts` files for autocompletion", () => {
385
- assert.equal(pkg.types, "types/index.d.ts")
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
- * ## Playground
391
- *
291
+ * #### Execution Control
392
292
  */
393
- it("How to run playground script?", async () => {
394
- /**
395
- * ```bash
396
- * # Clone the repository and run the CLI playground
397
- * git clone https://github.com/nan0web/ui-cli.git
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
- * ## Contributing
302
+ * ## Playground
303
+ *
304
+ * ```bash
305
+ * npm run play
306
+ * ```
416
307
  */
417
- it("How to contribute? - [check here](./CONTRIBUTING.md)", async () => {
418
- assert.equal(pkg.scripts?.precommit, "npm test")
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("How to license ISC? - [check here](./LICENSE)", async () => {
435
- try {
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("README.md testing", testRender)
323
+ describe('README.md testing', testRender)
445
324
 
446
- describe("Rendering README.md", async () => {
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("README.md", text)
452
- const dataset = DatasetParser.parse(text, pkg.name)
453
- await fs.saveDocument(".datasets/README.dataset.jsonl", dataset)
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
  })
@@ -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
- variant = "info",
15
- content = "",
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
+ }