@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.
Files changed (117) hide show
  1. package/README.md +153 -203
  2. package/bin/cli.js +11 -0
  3. package/bin/nan0cli.js +86 -0
  4. package/package.json +27 -13
  5. package/src/CLI.js +22 -30
  6. package/src/CLiMessage.js +2 -3
  7. package/src/Command.js +26 -24
  8. package/src/CommandError.js +3 -5
  9. package/src/CommandHelp.js +40 -36
  10. package/src/CommandMessage.js +56 -40
  11. package/src/CommandParser.js +27 -25
  12. package/src/InputAdapter.js +630 -90
  13. package/src/OutputAdapter.js +7 -8
  14. package/src/README.md.js +241 -312
  15. package/src/components/Alert.js +3 -6
  16. package/src/components/prompt/Autocomplete.js +12 -0
  17. package/src/components/prompt/Confirm.js +29 -0
  18. package/src/components/prompt/DateTime.js +26 -0
  19. package/src/components/prompt/Input.js +15 -0
  20. package/src/components/prompt/Mask.js +12 -0
  21. package/src/components/prompt/Multiselect.js +26 -0
  22. package/src/components/prompt/Next.js +8 -0
  23. package/src/components/prompt/Password.js +13 -0
  24. package/src/components/prompt/Pause.js +9 -0
  25. package/src/components/prompt/ProgressBar.js +16 -0
  26. package/src/components/prompt/Select.js +29 -0
  27. package/src/components/prompt/Slider.js +16 -0
  28. package/src/components/prompt/Spinner.js +29 -0
  29. package/src/components/prompt/Toggle.js +13 -0
  30. package/src/components/prompt/Tree.js +17 -0
  31. package/src/components/view/Alert.js +78 -0
  32. package/src/components/view/Badge.js +11 -0
  33. package/src/components/view/Nav.js +23 -0
  34. package/src/components/view/Table.js +12 -0
  35. package/src/components/view/Toast.js +9 -0
  36. package/src/core/Component.js +79 -0
  37. package/src/core/PropValidation.js +138 -0
  38. package/src/core/render.js +37 -0
  39. package/src/index.js +85 -40
  40. package/src/test/PlaygroundTest.js +37 -25
  41. package/src/test/index.js +2 -4
  42. package/src/ui/alert.js +58 -0
  43. package/src/ui/autocomplete.js +86 -0
  44. package/src/ui/badge.js +35 -0
  45. package/src/ui/confirm.js +49 -0
  46. package/src/ui/date-time.js +45 -0
  47. package/src/ui/form.js +120 -55
  48. package/src/ui/index.js +18 -4
  49. package/src/ui/input.js +79 -152
  50. package/src/ui/mask.js +132 -0
  51. package/src/ui/multiselect.js +59 -0
  52. package/src/ui/nav.js +74 -0
  53. package/src/ui/next.js +18 -13
  54. package/src/ui/progress.js +88 -0
  55. package/src/ui/select.js +49 -72
  56. package/src/ui/slider.js +154 -0
  57. package/src/ui/spinner.js +65 -0
  58. package/src/ui/table.js +163 -0
  59. package/src/ui/toast.js +34 -0
  60. package/src/ui/toggle.js +34 -0
  61. package/src/ui/tree.js +393 -0
  62. package/src/utils/parse.js +1 -1
  63. package/types/CLI.d.ts +5 -5
  64. package/types/CLiMessage.d.ts +1 -1
  65. package/types/Command.d.ts +2 -2
  66. package/types/CommandHelp.d.ts +3 -3
  67. package/types/CommandMessage.d.ts +8 -8
  68. package/types/CommandParser.d.ts +3 -3
  69. package/types/InputAdapter.d.ts +149 -15
  70. package/types/OutputAdapter.d.ts +1 -1
  71. package/types/README.md.d.ts +1 -1
  72. package/types/UiMessage.d.ts +31 -29
  73. package/types/components/prompt/Autocomplete.d.ts +6 -0
  74. package/types/components/prompt/Confirm.d.ts +6 -0
  75. package/types/components/prompt/DateTime.d.ts +6 -0
  76. package/types/components/prompt/Input.d.ts +6 -0
  77. package/types/components/prompt/Mask.d.ts +6 -0
  78. package/types/components/prompt/Multiselect.d.ts +6 -0
  79. package/types/components/prompt/Next.d.ts +6 -0
  80. package/types/components/prompt/Password.d.ts +6 -0
  81. package/types/components/prompt/Pause.d.ts +6 -0
  82. package/types/components/prompt/ProgressBar.d.ts +12 -0
  83. package/types/components/prompt/Select.d.ts +18 -0
  84. package/types/components/prompt/Slider.d.ts +6 -0
  85. package/types/components/prompt/Spinner.d.ts +21 -0
  86. package/types/components/prompt/Toggle.d.ts +6 -0
  87. package/types/components/prompt/Tree.d.ts +6 -0
  88. package/types/components/view/Alert.d.ts +21 -0
  89. package/types/components/view/Badge.d.ts +5 -0
  90. package/types/components/view/Nav.d.ts +15 -0
  91. package/types/components/view/Table.d.ts +10 -0
  92. package/types/components/view/Toast.d.ts +5 -0
  93. package/types/core/Component.d.ts +34 -0
  94. package/types/core/PropValidation.d.ts +48 -0
  95. package/types/core/render.d.ts +6 -0
  96. package/types/index.d.ts +47 -15
  97. package/types/test/PlaygroundTest.d.ts +12 -8
  98. package/types/test/index.d.ts +1 -1
  99. package/types/ui/alert.d.ts +14 -0
  100. package/types/ui/autocomplete.d.ts +20 -0
  101. package/types/ui/badge.d.ts +8 -0
  102. package/types/ui/confirm.d.ts +21 -0
  103. package/types/ui/date-time.d.ts +19 -0
  104. package/types/ui/form.d.ts +43 -12
  105. package/types/ui/index.d.ts +17 -2
  106. package/types/ui/input.d.ts +31 -74
  107. package/types/ui/mask.d.ts +29 -0
  108. package/types/ui/multiselect.d.ts +25 -0
  109. package/types/ui/nav.d.ts +27 -0
  110. package/types/ui/progress.d.ts +43 -0
  111. package/types/ui/select.d.ts +25 -64
  112. package/types/ui/slider.d.ts +23 -0
  113. package/types/ui/spinner.d.ts +28 -0
  114. package/types/ui/table.d.ts +28 -0
  115. package/types/ui/toast.d.ts +8 -0
  116. package/types/ui/toggle.d.ts +17 -0
  117. 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,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("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
+ * ## 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("How to install with pnpm?", () => {
112
- /**
113
- * ```bash
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("How to install with yarn?", () => {
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
- * ### CLiInputAdapter
170
+ * Starting from v2.0, we recommend using the `render()` function with Composable Components.
136
171
  *
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.
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
- const result = await adapter.requestForm(form, { silent: true })
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
- 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" })
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("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")
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
- * ### Input Utilities
200
- *
201
- * #### `Input` class
202
- *
203
- * Holds user input and tracks cancelation events.
204
+ * #### Multiselect
204
205
  */
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)
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
- * #### `ask(question)`
223
- *
224
- * Prompts the user with a question and returns a promise with the answer.
216
+ * #### Masked Input
225
217
  */
226
- it("How to ask a question with ask()?", async () => {
227
- //import { ask } from "@nan0web/ui-cli"
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
- const result = await ask("What is your name?")
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
- * #### `createInput(stops)`
237
- *
238
- * Creates a configurable input handler with stop keywords.
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("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)
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
- * #### `select(config)`
250
- *
251
- * Presents options to the user and returns a promise with selection.
254
+ * #### DateTime
252
255
  */
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")
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
- * #### `next(conf)`
270
- *
271
- * Waits for a keypress to continue the process.
266
+ * ### Static Views
272
267
  */
273
- it("How to pause and wait for keypress with next()?", async () => {
274
- //import { next } from '@nan0web/ui-cli'
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
- const result = await next()
277
- console.info(typeof result === "string")
278
- assert.equal(console.output()[0][1], true)
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
- * #### `pause(ms)`
284
- *
285
- * Returns a promise that resolves after a given delay.
287
+ * ### Feedback & Progress
286
288
  */
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)
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
- * ### Errors
299
- *
300
- * #### `CancelError`
301
- *
302
- * Thrown when a user interrupts a process.
297
+ * #### Progress Bars
303
298
  */
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")
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("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)
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
- * ## Java•Script
333
+ * ### Functional Utilities
383
334
  */
384
- it("Uses `d.ts` files for autocompletion", () => {
385
- assert.equal(pkg.types, "types/index.d.ts")
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
- * ## Playground
391
- *
344
+ * #### Execution Control
392
345
  */
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
- }
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
- * ## Contributing
355
+ * ## Playground
356
+ *
357
+ * ```bash
358
+ * npm run play
359
+ * ```
416
360
  */
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
- }
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("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
- }
371
+ it('How to check the license?', () => {
372
+ assert.ok(pkg.license === 'ISC')
441
373
  })
442
374
  }
443
375
 
444
- describe("README.md testing", testRender)
376
+ describe('README.md testing', testRender)
445
377
 
446
- describe("Rendering README.md", async () => {
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
- 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"))
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
  })