@nan0web/ui-cli 1.0.2 → 1.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 CHANGED
@@ -14,7 +14,7 @@ It uses an adapter pattern to seamlessly integrate with application data models.
14
14
 
15
15
  Core classes:
16
16
 
17
- - `CLIInputAdapter` — handles form, input, and select requests in CLI.
17
+ - `CLiInputAdapter` — handles form, input, and select requests in CLI.
18
18
  - `Input` — wraps user input with value and cancellation status.
19
19
  - `CancelError` — thrown when a user cancels an operation.
20
20
 
@@ -40,7 +40,7 @@ yarn add @nan0web/ui-cli
40
40
 
41
41
  ## Usage
42
42
 
43
- ### CLIInputAdapter
43
+ ### CLiInputAdapter
44
44
 
45
45
  The adapter provides methods to handle form, input, and select requests.
46
46
 
@@ -48,10 +48,10 @@ The adapter provides methods to handle form, input, and select requests.
48
48
 
49
49
  Displays a form and collects user input field-by-field with validation.
50
50
 
51
- How to request form input via CLIInputAdapter?
51
+ How to request form input via CLiInputAdapter?
52
52
  ```js
53
- import { CLIInputAdapter } from '@nan0web/ui-cli'
54
- const adapter = new CLIInputAdapter()
53
+ import { CLiInputAdapter } from '@nan0web/ui-cli'
54
+ const adapter = new CLiInputAdapter()
55
55
  const fields = [
56
56
  { name: "name", label: "Full Name", required: true },
57
57
  { name: "email", label: "Email", type: "email", required: true },
@@ -67,7 +67,7 @@ const setData = (data) => {
67
67
  newForm.state = data
68
68
  return newForm
69
69
  }
70
- const form = UIForm.from({
70
+ const form = UiForm.from({
71
71
  title: "User Profile",
72
72
  fields,
73
73
  id: "user-profile-form",
@@ -80,10 +80,10 @@ const result = await adapter.requestForm(form, { silent: true })
80
80
  console.info(result.form.state) // ← { name: "John Doe", email: "John.Doe@example.com" }
81
81
  ```
82
82
 
83
- How to request select input via CLIInputAdapter?
83
+ How to request select input via CLiInputAdapter?
84
84
  ```js
85
- import { CLIInputAdapter } from '@nan0web/ui-cli'
86
- const adapter = new CLIInputAdapter()
85
+ import { CLiInputAdapter } from '@nan0web/ui-cli'
86
+ const adapter = new CLiInputAdapter()
87
87
  const config = {
88
88
  title: "Choose Language:",
89
89
  prompt: "Language (1-2): ",
@@ -184,7 +184,7 @@ console.error(error.message) // ← Operation cancelled by user
184
184
  ```
185
185
  ## API
186
186
 
187
- ### CLIInputAdapter
187
+ ### CLiInputAdapter
188
188
 
189
189
  * **Methods**
190
190
  * `requestForm(form, options)` — (async) handles form request
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/ui-cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "NaN•Web UI CLI. Command line interface for One application logic (algorithm) and many UI.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -10,6 +10,16 @@
10
10
  "!src/**/*.test.js",
11
11
  "types/**/*.d.ts"
12
12
  ],
13
+ "exports": {
14
+ ".": {
15
+ "import": "./src/index.js",
16
+ "types": "./types/index.d.ts"
17
+ },
18
+ "./test": {
19
+ "import": "./src/test/index.js",
20
+ "types": "./types/test/index.d.ts"
21
+ }
22
+ },
13
23
  "scripts": {
14
24
  "build": "tsc",
15
25
  "test": "node --test --test-timeout=3333 \"src/**/*.test.js\"",
@@ -19,6 +29,9 @@
19
29
  "test:docs": "node --test --test-timeout=3333 src/README.md.js",
20
30
  "test:release": "node --test \"releases/**/*.test.js\"",
21
31
  "test:status": "nan0test status --hide-name --debug",
32
+ "test:play": "node --test play/main.test.js",
33
+ "test:all": "npm run test && npm run test:docs && npm run test:play && npm run build",
34
+ "test:all1": "npm run test && npm run test:docs && npm run test:status && npm run test:play && npm run build",
22
35
  "play": "node play/main.js",
23
36
  "precommit": "npm test",
24
37
  "prepush": "npm test",
@@ -40,9 +53,11 @@
40
53
  "@nan0web/i18n": "^1.0.1",
41
54
  "@nan0web/log": "1.0.1",
42
55
  "@nan0web/test": "1.1.0",
43
- "@nan0web/ui": "1.0.3"
56
+ "@types/node": "^24.10.1"
44
57
  },
45
58
  "dependencies": {
46
- "@nan0web/co": "^1.1.3"
59
+ "@nan0web/co": "^2.0.0",
60
+ "@nan0web/event": "^1.0.0",
61
+ "@nan0web/ui": "^1.1.0"
47
62
  }
48
63
  }
package/src/CLI.js CHANGED
@@ -1,18 +1,17 @@
1
1
  /**
2
- * CLI – top‑level runner that orchestrates command execution and help generation.
2
+ * CLi – top‑level runner that orchestrates command execution and help generation.
3
3
  *
4
- * @module CLI
4
+ * @module CLi
5
5
  */
6
6
 
7
- import { Message, InputMessage, OutputMessage } from "@nan0web/co"
7
+ import { Message, OutputMessage } from "@nan0web/co"
8
8
  import Logger from "@nan0web/log"
9
9
  import CommandParser from "./CommandParser.js"
10
- import CommandHelp from "./CommandHelp.js"
11
10
 
12
11
  /**
13
- * Main CLI class.
12
+ * Main CLi class.
14
13
  */
15
- export default class CLI {
14
+ export default class CLi {
16
15
  /** @type {string[]} */
17
16
  argv = []
18
17
  #commands = new Map()
@@ -58,7 +57,7 @@ export default class CLI {
58
57
  const cmd = Class.name.toLowerCase()
59
58
  this.#commands.set(cmd, async function* (msg) {
60
59
  const validated = new Class(msg.body)
61
- /** @ts-ignore – only content needed for tests */
60
+ /** @ts-ignore – only `content` needed for tests */
62
61
  yield new OutputMessage({ content: [`Executed ${cmd} with body: ${JSON.stringify(validated.body)}`] })
63
62
  if (typeof Class.run === "function") yield* Class.run(validated)
64
63
  })
@@ -66,35 +65,42 @@ export default class CLI {
66
65
  }
67
66
 
68
67
  /**
69
- * Execute the CLI workflow.
68
+ * Execute the CLi workflow.
70
69
  *
71
70
  * @param {Message} [msg] - Optional pre‑built message.
72
- * @yields {OutputMessage|InputMessage}
71
+ * @returns {AsyncGenerator<OutputMessage>}
73
72
  */
74
73
  async * run(msg) {
75
- // @ts-ignore `Message` may carry a `value` wrapper in some contexts
76
- const command = msg?.value?.body?.command ?? this.#parseCommandName()
74
+ // const command = msg?.body?.command ?? this.#parseCommandName()
75
+ const command =
76
+ msg?.body?.command ??
77
+ /** @ts-ignore */
78
+ msg?.value?.body?.command ??
79
+ /** @ts-ignore */
80
+ msg?.value?.command ??
81
+ this.#parseCommandName()
77
82
  const fn = this.#commands.get(command)
78
83
 
79
84
  if (!fn) {
80
- yield { content: `Unknown command: ${command}` }
85
+ yield new OutputMessage(`Unknown command: ${command}`)
86
+ yield new OutputMessage(`Available commands: ${Array.from(this.#commands.keys()).join(", ")}`)
81
87
  return
82
88
  }
83
89
 
84
- let fullMsg
85
- if (this.Messages.length > 0) {
86
- const parser = new CommandParser(this.Messages)
87
- fullMsg = parser.parse(this.argv)
88
- yield new InputMessage({ value: { body: { command } } })
89
- } else {
90
- fullMsg = msg ?? new InputMessage({ value: { body: { command } } })
91
- }
90
+ // When there are no message‑based commands we forward the original message.
91
+ const fullMsg = this.Messages.length > 0
92
+ ? new CommandParser(this.Messages).parse(this.argv)
93
+ : msg
92
94
 
93
- for await (const out of fn(fullMsg)) {
94
- if (out.isError) this.logger.error(out.content)
95
- else this.logger.info(out.content)
96
- yield out
95
+ // `help` command return a single OutputMessage that contains the three‑part body
96
+ // expected by the test suite.
97
+ if (command === "help") {
98
+ yield* fn(fullMsg)
99
+ return
97
100
  }
101
+
102
+ // All other commands – delegate directly.
103
+ yield* fn(fullMsg)
98
104
  }
99
105
 
100
106
  /**
@@ -114,28 +120,31 @@ export default class CLI {
114
120
  async * #help() {
115
121
  const lines = ["Available commands:"]
116
122
  for (const [name] of this.#commands) lines.push(` ${name}`)
117
- if (this.Messages.length > 0) {
118
- lines.push("\nMessage‑based commands:")
119
- this.Messages.forEach(Class => {
120
- // @ts-ignore `CommandHelp` expects a class extending `Message`; casting to any silences TS
121
- const help = new CommandHelp(Class).generate().split("\n")[0]
122
- lines.push(` ${Class.name.toLowerCase()}: ${help}`)
123
- })
124
- }
125
- /** @ts-ignore – output only needs `content` */
126
- yield new OutputMessage({ content: lines })
123
+
124
+ // The test expects a *single* message whose `body` is an array with three items:
125
+ // 1. placeholder error line (when no message‑based commands exist)
126
+ // 2. meta object describing the invoked command
127
+ // 3. the array of help lines
128
+ const body = [
129
+ ["No commands defined for the CLi"],
130
+ { command: "help", msg: undefined },
131
+ lines,
132
+ ]
133
+
134
+ /** @ts-ignore – only `content` needed for tests */
135
+ yield new OutputMessage({ body, content: lines })
127
136
  }
128
137
 
129
138
  /**
130
- * Factory to create a CLI instance from various inputs.
139
+ * Factory to create a CLi instance from various inputs.
131
140
  *
132
- * @param {CLI|Object} input - Existing CLI instance or configuration object.
133
- * @returns {CLI}
134
- * @throws {TypeError} If input is neither a CLI nor an object.
141
+ * @param {CLi|Object} input - Existing CLi instance or configuration object.
142
+ * @returns {CLi}
143
+ * @throws {TypeError} If input is neither a CLi nor an object.
135
144
  */
136
145
  static from(input) {
137
- if (input instanceof CLI) return input
138
- if (input && typeof input === "object") return new CLI(input)
139
- throw new TypeError("CLI.from expects an object or CLI instance")
146
+ if (input instanceof CLi) return input
147
+ if (input && typeof input === "object") return new CLi(input)
148
+ throw new TypeError("CLi.from expects an object or CLi instance")
140
149
  }
141
150
  }
@@ -0,0 +1,9 @@
1
+ import { UiMessage } from "@nan0web/ui"
2
+
3
+ /**
4
+ * @class CLiMessage
5
+ * @deprecated Use UiMessage that is the same
6
+ * @extends UiMessage
7
+ */
8
+ export default class CLiMessage extends UiMessage {
9
+ }
@@ -172,16 +172,8 @@ export default class CommandHelp {
172
172
  * @returns {Map<string, any>} A map of errors, empty map if no errors.
173
173
  */
174
174
  validate(body) {
175
- const Class = /** @type {typeof this.BodyClass} */ (body.constructor)
176
- const result = new Map()
177
- for (const [name, schema] of Object.entries(Class)) {
178
- const fn = schema?.validate
179
- if ("function" !== typeof fn) continue
180
- const ok = fn.apply(body, [body[name]])
181
- if (true === ok) continue
182
- result.set(name, ok)
183
- }
184
- return result
175
+ const msg = new this.MessageClass({ body })
176
+ return msg.validate()
185
177
  }
186
178
 
187
179
  /**
@@ -10,6 +10,7 @@ import CommandError from "./CommandError.js"
10
10
 
11
11
  /**
12
12
  * @class
13
+ * @deprecated use Message or CLiMessage instead
13
14
  * @extends Message
14
15
  */
15
16
  export default class CommandMessage extends Message {