@noresources/command 0.0.0-stage → 0.1.1

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
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # @noresources/command
2
+
3
+ A lightweight command line interface definition package focusing on reusability, extensibility and a clean API.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ npm install --save @noresources/command
9
+ ```
10
+
11
+ ## Features
12
+
13
+ * Plain object based command component descriptors
14
+ * Program
15
+ * Command, subcommands
16
+ * Option
17
+ * Positional
18
+ * Parameter (option and positional argument value type)
19
+ * Chain-based program builder API
20
+ * Exporter to well-known command packages
21
+ * [Commander.js](https://github.com/tj/commander.js/)
22
+
23
+ ## Examples
24
+
25
+ ### Component descriptors
26
+
27
+ Commands, options and positional arguments can be declared using a simple plain javascript object.
28
+
29
+ ```javascript
30
+ export const Verbosity = {
31
+ 'type': 'counter',
32
+ 'documentation': 'Verbosity level',
33
+ 'initial': 0,
34
+ 'names': ['verbosity', 'v']
35
+ };
36
+ ```
37
+
38
+ Then, a factory function will creates the correct class instance.
39
+
40
+ ```javascript
41
+ import { createOption } from "@noresources/command"
42
+ const option = createOption(Verbosity);
43
+ ```
44
+ ### Builder API
45
+
46
+ The `ProgramBuilder` class provides a fluent interface to define the whole program interface using method chaining.
47
+
48
+ ```javascript
49
+ import { ProgramBuilder } from "@noresources/command"
50
+
51
+ const builder = new ProgramBuilder;
52
+ builder.name('chatbot')
53
+ .version('1.2.3')
54
+ .option(Verbosity) // out option descriptor presented above.
55
+ .positional({
56
+ documentation: 'Some text to insert in conversation'
57
+ }).handler(function (input) {
58
+ if (input.options.verbosity > 2) console.log('Bark bark')
59
+ if (input.arguments.length)
60
+ console.log (input.arguments[0])
61
+ console.log ('This program is not really meaningful'),
62
+ })
63
+ ```
64
+ ### Exporter to commander.js
65
+
66
+ For now, this package does not provide a command line parser by itself. It relies on well established parser [Commander.js](https://www.npmjs.com/package/commander)a to for parsing.
67
+
68
+ Converting a `@noresources/command` program to a `Commander.js` command is made with a simple function call.
69
+
70
+ ```javascript
71
+ import { createCommand } from '@noresources/command/commander'
72
+
73
+ const command = createCommand(builder.program); // assuming builder is the ProgramBuilder instance defined above.
74
+ command.parse ();
75
+ ```
76
+ Note that the `commander` package is not included in `@noresources/command` package dependencies. You have to install it manually to usie this feature.
2
77
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
- "name": "@noresources/command",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
2
+ "name": "@noresources/command",
3
+ "version": "0.1.1",
4
+ "description": "A lightweight command line interface definition package focusing on reusability, extensibility and a clean API",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "tape tests/tape/*.mjs",
8
+ "lint": "eslint --fix",
9
+ "doc": "jsdoc --recurse -c resources/jsdoc/configuration.json --readme README.md --tutorials resources/jsdoc/tutorials --destination doc src/"
10
+ },
11
+ "keywords": [
12
+
13
+ ],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/noresources/js-command.git"
19
+ },
20
+ "dependencies": {
21
+ "@noresources/core": "^0.2.0"
22
+ },
23
+ "devDependencies": {
24
+ "commander": "^15.0.0",
25
+ "tape": "^5.10.2"
26
+ },
27
+ "type": "module",
28
+ "files": [
29
+ "./src"
30
+ ],
31
+ "module": "./index.mjs",
32
+ "exports": {
33
+ ".": [
34
+ {
35
+ "import": "./index.mjs"
36
+ },
37
+ "./index.mjs"
38
+ ],
39
+ "./src/lib/options": [
40
+ {
41
+ "import": "./src/lib/options/index.mjs"
42
+ },
43
+ "./src/lib/options/index.mjs"
44
+ ]
45
+ }
6
46
  }
@@ -0,0 +1,368 @@
1
+ 'use strict';
2
+
3
+ import {
4
+ merge
5
+ } from '@noresources/core';
6
+
7
+ import {
8
+ createOption,
9
+ Option
10
+ } from './Options.mjs';
11
+
12
+ import {
13
+ Parameter,
14
+ createParameter
15
+ } from './Parameters.mjs';
16
+
17
+ import {
18
+ createPositional,
19
+ Positional,
20
+ PositionalValue
21
+ } from './Positionals.mjs';
22
+
23
+ import {
24
+ Command,
25
+ Program
26
+ } from './Command.mjs';
27
+
28
+
29
+ /**
30
+ * Internal function to process and register a command descriptor with the builder.
31
+ * This method abstracts the logic of determining if the input is a Command object
32
+ * or a raw descriptor object before adding it to the builder's structure.
33
+ * @param {CommandBaseBuilder} builder - The CommandBuilder instance managing the current context.
34
+ * @param {object} descriptor - The raw or instantiated command definition object.
35
+ * @private
36
+ */
37
+ function addCommand (builder, descriptor) {
38
+ let c;
39
+ let b;
40
+ if (descriptor instanceof Command) {
41
+ c = descriptor;
42
+ b = new CommandBuilder(c, builder.parent);
43
+ } else {
44
+ const argument = merge ({}, descriptor);
45
+
46
+ delete argument.options;
47
+ delete argument.positionals;
48
+ delete argument.commands;
49
+
50
+ c = new Command(argument);
51
+ b = new CommandBuilder(c, builder.parent);
52
+
53
+ if ( Array.isArray(descriptor.options) ) {
54
+ for (const o of descriptor.options) {
55
+ addOption(b, o);
56
+ }
57
+ }
58
+
59
+ if ( Array.isArray(descriptor.positionals) ) {
60
+ for (const p of descriptor.positionals) {
61
+ addParameter(b, p);
62
+ }
63
+ }
64
+
65
+ if ( Array.isArray(descriptor.commands) ) {
66
+ for (const cd of descriptor.commands) {
67
+ addCommand(b, cd);
68
+ }
69
+ }
70
+
71
+ }
72
+ builder._object.commands.push (c);
73
+ builder._commands.push (b);
74
+ return b;
75
+ }
76
+
77
+ function addOption (builder, descriptor) {
78
+ const o = (descriptor instanceof Option)
79
+ ? descriptor
80
+ : builder.optionFactory(descriptor);
81
+ builder._object.options.push (o);
82
+ }
83
+
84
+ function addParameter (builder, descriptor) {
85
+ let p;
86
+ if (descriptor instanceof Parameter) {
87
+ descriptor = new PositionalValue({
88
+ 'parameter': descriptor
89
+ });
90
+ }
91
+
92
+ p = (descriptor instanceof Positional)
93
+ ? descriptor
94
+ : builder.positionalFactory(descriptor);
95
+
96
+ builder._object.positionals.push (p);
97
+ }
98
+
99
+ /**
100
+ * @class CommandBaseBuilder
101
+ * @description Base class providing common chaining methods (like `command`, `option`, `positional`)
102
+ * and property accessors for command structures. It is designed to be extended by
103
+ * `CommandBuilder` or `ProgramBuilder` to define specific program or command behavior.
104
+ * @property {object} _object - The underlying representation (Command or Program) being configured.
105
+ * @property {Array<CommandBuilder|ProgramBuilder>} _commands - Internal array tracking child builders.
106
+ */
107
+ class CommandBaseBuilder {
108
+ /**
109
+ * @constructor
110
+ * @param {object} object - The root data structure (e.g., Command object) that this builder is manipulating.
111
+ */
112
+ constructor(object) {
113
+ this._object = object;
114
+ this._commands = [];
115
+ }
116
+
117
+
118
+ /**
119
+ * @command
120
+ * @description Attaches a new command definition to the current builder's context.
121
+ * This method allows for the creation of nested commands (subcommands) within the current scope.
122
+ * @param {Command | object} descriptor - The definition of the new command. This can be a fully
123
+ * instantiated Command object or a raw configuration object containing options, positionals, and subcommands.
124
+ * @returns {CommandBuilder} A new instance of the builder specifically for the newly created subcommand, allowing for further configuration chaining.
125
+ */
126
+ command (descriptor) {
127
+ return addCommand(this, descriptor);
128
+ }
129
+
130
+ /**
131
+ * @option
132
+ * @description Configures and registers an option (flag or argument) on the current command definition.
133
+ * Options define optional arguments that the command can consume.
134
+ * @param {object} descriptor - The option definition object. This object dictates the name, type, and optionality of the argument.
135
+ * @returns {CommandBaseBuilder} The current instance of the builder, enabling method chaining.
136
+ */
137
+ option (descriptor) {
138
+ addOption(this, descriptor);
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * @positional
144
+ * @description Configures and registers a required positional parameter for the current command.
145
+ * Positional parameters are arguments provided by the user that do not have a name (e.g., file names in 'git checkout <file>').
146
+ * @param {object} descriptor - The positional parameter definition. This defines constraints like type or description.
147
+ * @returns {CommandBaseBuilder} The current instance of the builder, enabling method chaining.
148
+ */
149
+ positional (descriptor) {
150
+ addParameter(this, descriptor);
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * @handler
156
+ * @description Sets the primary execution handler for this command structure.
157
+ * This function will be invoked by the runtime when the command is executed.
158
+ * @param {function(object): void} callback - The callback function that receives the parsed arguments/context and performs the core logic. It is expected to not return a value.
159
+ * @returns {CommandBaseBuilder} The current builder instance, allowing for method chaining.
160
+ * @notes If the handler is set, it provides the final action for this command chain.
161
+ */
162
+ handler (callback) {
163
+ this._object.handler = callback;
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * @property {function(): function} parameterFactory
169
+ * @description The function used internally to create Parameter objects.
170
+ * @returns {function} The parameter creation function.
171
+ */
172
+ get parameterFactory () {
173
+ return this._parameterFactory || createParameter;
174
+ }
175
+
176
+ /**
177
+ * @property {function(): function} optionFactory
178
+ * @description The function used internally to create Option objects.
179
+ * @returns {function} The option creation function.
180
+ */
181
+ get optionFactory () {
182
+ return this._optionFactory;
183
+ }
184
+
185
+ /**
186
+ * @property {function(): function} positionalFactory
187
+ * @description The function used internally to create PositionalValue objects.
188
+ * @returns {function} The positional value creation function.
189
+ */
190
+ get positionalFactory () {
191
+ return this._positionalFactory;
192
+ }
193
+
194
+ /**
195
+ * @setter
196
+ * @description Overrides the default parameter creation mechanism, allowing advanced customization of parameter behavior.
197
+ * @param {function} parameterFactory - The custom function to be used by the builder to construct parameters.
198
+ * @throws {Error} If the provided `parameterFactory` is not a function.
199
+ */
200
+ set parameterFactory (parameterFactory) {
201
+ if (typeof parameterFactory !== 'function') {
202
+ throw new Error ('Invalid parameter factory. Parameter is not a function');
203
+ }
204
+ this._parameterFactory = parameterFactory;
205
+ const thisArg = {parameterFactory};
206
+ this._optionFactory = createOption.bind (thisArg);
207
+ this._positionalFactory = createPositional.bind(thisArg);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @class CommandBuilder
213
+ * @extends CommandBaseBuilder
214
+ * @description Represents a single, specific command within the overall application structure.
215
+ * This builder manages the configuration (name, aliases, options, subcommands) of one command
216
+ * and provides the interface to manipulate it before it is instantiated.
217
+ */
218
+ export class CommandBuilder extends CommandBaseBuilder {
219
+ /**
220
+ * @constructor
221
+ * @param {Command | CommandBuilder | ProgramBuilder} command - The existing Command or builder object this context is operating on.
222
+ * @param {CommandBuilder|ProgramBuilder} parent - The parent builder context that contains this command.
223
+ * @description Initializes the command builder. The parent reference is critical for hierarchical traversal (e.g., finding the root Program).
224
+ */
225
+ constructor (command, parent) {
226
+ super (command || new Command );
227
+ this._parent = parent;
228
+ this.parameterFactory = (parent)
229
+ ? (parent.parameterFactory || createParameter)
230
+ : createParameter;
231
+
232
+ }
233
+
234
+ /**
235
+ * @property {Command} object
236
+ * @description Getter that exposes the underlying Command object that this builder is configuring.
237
+ * @returns {Command} The actual Command instance, allowing inspection or modification of its state.
238
+ */
239
+ get object () {
240
+ return this._object;
241
+ }
242
+
243
+ /**
244
+ * @property {ProgramBuilder|null} program
245
+ * @description Traverses up the chain of builders (CommandBuilder -> ProgramBuilder) to locate the root ProgramBuilder.
246
+ * @returns {ProgramBuilder|null} The root ProgramBuilder instance, or null if the command has no ancestor program.
247
+ */
248
+ get program () {
249
+ let p = this.parent;
250
+ while (p && p.parent) {
251
+ p = p.parent;
252
+ }
253
+ return p;
254
+ }
255
+
256
+ /**
257
+ * @property {CommandBuilder|ProgramBuilder|null} parent
258
+ * @description Getter that provides a reference to the immediate parent builder in the hierarchy.
259
+ * @returns {CommandBuilder|ProgramBuilder|null} The parent builder instance, used for traversing up the command structure.
260
+ */
261
+ get parent () {
262
+ return this._parent;
263
+ }
264
+
265
+ /**
266
+ * @description Sets the main executable name for this command. If a command with this name already exists, it will be overwritten.
267
+ * @param {string} name - The desired, primary name for this command.
268
+ * @returns {CommandBuilder} The current CommandBuilder instance, facilitating method chaining.
269
+ * @throws {Error} Throws an error if the provided name is not a string or is an empty string.
270
+ */
271
+ name (name) {
272
+ if (typeof name !== 'string') {
273
+ throw new Error ('String expected. Got ' + (typeof name));
274
+ }
275
+ if (name.length == 0) {
276
+ throw new Error ('Invalid empty name');
277
+ }
278
+ const i = this._object.names.indexOf (name);
279
+ if (i == 0) {
280
+ return this;
281
+ }
282
+ if (i > 0) {
283
+ this._object.names.splice(i, 1);
284
+ }
285
+ this._object.names.unshift(name);
286
+ return this;
287
+ }
288
+
289
+ /**
290
+ * @description Adds one or more aliases (alternative keywords) to the command, allowing multiple ways to invoke the same functionality.
291
+ * @param {...string} aliases - A variable list of alternative names (aliases) that can map to this command.
292
+ * @returns {CommandBuilder} The current CommandBuilder instance, allowing for continuous method calls.
293
+ * @throws {Error} Throws an error if any provided alias is not a string or is an empty string.
294
+ */
295
+ aliases () {
296
+ for (const a of arguments) {
297
+ if (Array.isArray(a)) {
298
+ this.aliases(...a);
299
+ continue;
300
+ }
301
+ if (typeof a !== 'string') {
302
+ throw new Error ('String expected. Got ' + (typeof a));
303
+ }
304
+ if (a.length == 0) {
305
+ throw new Error ('Invalid empty name.');
306
+ }
307
+ if (this._object.names.includes (a)) {
308
+ continue;
309
+ }
310
+
311
+ this._object.names.push (a);
312
+ }
313
+ return this;
314
+ }
315
+
316
+
317
+ } /* CommandBuilder */
318
+
319
+ /**
320
+ * @class ProgramBuilder
321
+ * @extends CommandBaseBuilder
322
+ * @description Represents the root configuration object for the entire application.
323
+ * This builder is used to set global properties of the program, such as the name and version.
324
+ */
325
+ export class ProgramBuilder extends CommandBaseBuilder {
326
+ /**
327
+ * @constructor
328
+ * @description Initializes the ProgramBuilder, setting up the root Program object.
329
+ */
330
+ constructor () {
331
+ super ( new Program );
332
+ this.parameterFactory = createParameter;
333
+ }
334
+
335
+ /**
336
+ * @property {Program} program
337
+ * @description Getter that exposes the underlying Program object that this builder is configuring.
338
+ * @returns {Program} The actual Program instance, representing the application's definition.
339
+ */
340
+ get program () {
341
+ return this._object;
342
+ }
343
+
344
+ /**
345
+ * @description Sets the main name under which the application is recognized.
346
+ * @param {string} name - The name of the application program.
347
+ * @returns {ProgramBuilder} The current ProgramBuilder instance, enabling method chaining.
348
+ */
349
+ name (name) {
350
+ this._object.name = name;
351
+ return this;
352
+ }
353
+
354
+ /**
355
+ * @description Sets the version number metadata for the program, which is used in version reporting.
356
+ * @param {number|string} version - The version identifier for the program (e.g., "1.0.0" or 1).
357
+ * @returns {ProgramBuilder} The current ProgramBuilder instance, allowing for method chaining.
358
+ */
359
+ version (version) {
360
+ this._object.version = version;
361
+ return this;
362
+ }
363
+ } /* Program Builder */
364
+
365
+ export default {
366
+ CommandBuilder,
367
+ ProgramBuilder
368
+ };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file Defines the core structural components for building command-line interface (CLI) applications.
5
+ * This module provides the foundational classes (CommandBase, Command, Program) necessary to manage
6
+ * command hierarchy, configure metadata, and handle command execution logic within a robust JS application.
7
+ * It centralizes the management of command names, aliases, and execution handlers.
8
+ */
9
+
10
+ import { createDocumentation } from './Documentation.mjs';
11
+
12
+ /**
13
+ * @class CommandBase
14
+ * @description Provides a foundational base class for defining command structures within the CLI.
15
+ * This base class handles common utilities such as documentation caching, managing command arguments,
16
+ * and attaching specific command execution handlers.
17
+ */
18
+ class CommandBase {
19
+ /**
20
+ * Creates an instance of CommandBase.
21
+ * @param {object} [options] - Initialization options provided during construction.
22
+ * @param {object} [options.documentation] - Optional initial, raw documentation string. This string is processed to create the internal documentation object.
23
+ * @param {function} [options.handler] - The primary function object responsible for handling command execution. This function contains the actual business logic.
24
+ */
25
+ constructor (options) {
26
+ if (!options) {
27
+ options = {};
28
+ }
29
+ this.documentation = options.documentation;
30
+ this.options = [];
31
+ this.commands = [];
32
+ this.positionals = [];
33
+
34
+ if (typeof options.handler === 'function') {
35
+ this.handler = options.handler;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Gets the command documentation.
41
+ *
42
+ * @returns {Documentation}
43
+ */
44
+ get documentation () {
45
+ return this._documentation;
46
+ }
47
+
48
+ /**
49
+ * Sets the command documentation.
50
+ *
51
+ * The input value is normalized to a {Documentation} object.
52
+ *
53
+ * @param {Documentation|string|undefined value - User-defined documentation.
54
+ * If undefined, the documentation object is cleared.
55
+ */
56
+ set documentation (value) {
57
+ if (!value) {
58
+ this._documentation = undefined;
59
+ return;
60
+ }
61
+
62
+ this._documentation = createDocumentation(value);
63
+ }
64
+
65
+ get handler () {
66
+ return this._handler;
67
+ }
68
+
69
+
70
+ /**
71
+ * Attaches the primary handler function responsible for executing the command logic.
72
+ * This handler is the core logic execution entry point that dictates the command's behavior when invoked.
73
+ * @param {function} handler - The callable function object that encapsulates the complete command execution logic.
74
+ * @returns {void}
75
+ * @throws {Error} Throws an Error if the provided 'handler' argument is not a valid function type.
76
+ */
77
+ set handler (handler) {
78
+ if (typeof handler !== 'function') {
79
+ throw new Error ('Invalid handler argument. A function object is expected.');
80
+ }
81
+ this._handler = handler;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @class Command
87
+ * @extends {CommandBase}
88
+ * @description Represents a single, executable command unit within the CLI program structure.
89
+ * This class is responsible for defining the command's unique identifier(s), managing aliases,
90
+ * and providing a specialized interface on top of CommandBase. larger application ecosystem.
91
+ */
92
+ export class Command extends CommandBase {
93
+ /**
94
+ * Creates an instance of Command, setting up its formal primary name and registering callable aliases.
95
+ * @param {object} options - Command initialization options.
96
+ * @param {Array<string>} [options.names] - An optional array of names. The first element MUST be the primary command name, while subsequent elements serve as secondary aliases for execution convenience.
97
+ */
98
+ constructor (options) {
99
+ if (!options) {
100
+ options = {};
101
+ }
102
+ super (options);
103
+ this.names = [];
104
+ if (Array.isArray (options.names)) {
105
+ options.names.forEach(n => {
106
+ this.names.push (n);
107
+ });
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Retrieves the primary, canonical name of the command.
113
+ * This name is defined by the first element provided during construction and is the primary identifier used for invocation.
114
+ * @returns {string} The main name used to execute the command (e.g., 'user'). This string cannot be empty.
115
+ */
116
+ get name () {
117
+ return this.names[0];
118
+ }
119
+
120
+ /**
121
+ * Retrieves an array of recognized aliases for this command.
122
+ * These are supplementary names that can be used interchangeably with the main command name.
123
+ * @returns {Array<string>} An array containing all secondary aliases registered for this command. This array may be empty.
124
+ */
125
+ get aliases () {
126
+ return this.names.slice (1);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * @class Program
132
+ *
133
+ * @extends {CommandBase}
134
+ * @description Represents the root executable container for the entire CLI application.
135
+ * This class establishes the global context of the application, holding critical metadata
136
+ * such as the application name and version. It functions as the primary organizational entry point
137
+ * from which all sub-commands are registered, managed, and ultimately executed.
138
+ */
139
+ export class Program extends CommandBase {
140
+ /**
141
+ * Creates an instance of Program, setting up the global application metadata that defines the CLI interface.
142
+ * @param {object} [options] - Program configuration options.
143
+ * @param {string} [options.name] - The official, user-facing name of the entire program (e.g., 'mytool'). This is used in help output.
144
+ * @param {string} [options.version] - The current, semantic version string of the application (e.g., '1.0.0').
145
+ */
146
+ constructor (options) {
147
+ if (!options) {
148
+ options = {};
149
+ }
150
+ super (options);
151
+ this.name = (typeof options.name === 'string') ? options.name : 'undefined';
152
+ this.version = (typeof options.version === 'string') ? options.version : '1.0.0';
153
+ }
154
+ }
155
+
156
+ export default {
157
+ Command,
158
+ Program
159
+ };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ *
5
+ * The expected Command handler function signature.
6
+ *
7
+ * @param {Input} input - The parsed command structure provided by the CLI parser. This object must contain all necessary arguments and flags required for command execution.
8
+ *
9
+ * @returns {number} The process exit code. A return value of 0 indicates successful execution, while non-zero values signal specific failure modes (e.g., missing arguments, invalid state).
10
+ * @throws {Error} Throws an Error if the input data is fundamentally malformed or an expected command dependency is not available.
11
+ */
12
+ export function CommandHandler ( /* input */ ) {
13
+ ;
14
+ }