Compare commits

...

2 Commits

5 changed files with 1214 additions and 76 deletions

349
changelog.md Normal file
View File

@ -0,0 +1,349 @@
# Changelog
## 2025-04-01 - 4.0.12 - fix(docs)
Update documentation with comprehensive usage examples, improved command alias descriptions, and detailed configuration instructions
- Revised readme.md with in-depth examples covering multiple CLI scenarios and RxJS integration
- Updated package.json and npmextra.json descriptions and keywords to reflect enhanced functionality
- Expanded usage guide with additional commands, error handling strategies, and testing guidelines
## 2024-05-29 - 4.0.11 - general
update description
- Updated the project description
## 2024-05-28 - 4.0.10 - core
fix(core): update
- Fixed core functionality
## 2024-04-13 - 4.0.9 - core
fix(core): update
- Improved core update handling
## 2024-04-12 - 4.0.8 - core / npmextra
- fix(core): update
- update npmextra.json: githost (this change was applied multiple times)
## 2023-08-19 - 4.0.7 - core
fix(core): update
- Fixed core issues
## 2023-07-12 - 4.0.6 - core / org
- fix(core): update
- switch to new org scheme (applied twice)
## 2022-08-07 - 4.0.5 - core
fix(core): update
- Fixed core functionality
## 2022-08-04 - 4.0.4 - core
fix(core): update
- Improved core update
## 2022-08-03 - 4.0.3 - core
fix(core): update
- Fixed core issues
## 2022-08-03 - 4.0.2 - core
fix(core): update
- Updated core handling
## 2022-08-03 - 4.0.1 - core
fix(core): update
- Fixed core functionality
## 2022-08-03 - 4.0.0 - core
fix(core): update
- Improved core update handling
## 2022-08-03 - 3.0.14 - core
BREAKING CHANGE(core): switch to esm
- Switched the project to use ECMAScript modules
## 2021-04-07 - 3.0.13 - core
fix(core): update
- Fixed core update issues
## 2021-04-07 - 3.0.12 - core
fix(core): update
- Updated core functionality
## 2020-05-29 - 3.0.11 - core
fix(core): update
- Fixed core functionality
## 2020-04-13 - 3.0.10 - core
fix(core): more consistent handling of process.enc.CLI_CALL
- Made process.enc.CLI_CALL handling more consistent
## 2020-04-13 - 3.0.9 - core
fix(core): now works better with tapbundle tests
- Improved compatibility with tapbundle tests
## 2020-03-11 - 3.0.8 - core
fix(core): update
- Updated core functionality
## 2020-03-11 - 3.0.7 - core
fix(core): update
- Fixed core update issues
## 2018-12-11 - 3.0.6 - core
fix(core): update
- Updated core functionality
## 2018-09-30 - 3.0.5 - ci
fix(ci): remove obsolete dependencies
- Removed obsolete dependencies from CI configuration
## 2018-09-30 - 3.0.4 - core
fix(core): update
- Updated core functionality
## 2018-08-30 - 3.0.3 - structure
fix(structure): remove dist/ dir from git repo
- Removed the dist/ directory from the repository
## 2018-08-30 - 3.0.2 - dependencies
fix(dependencies): update to latest versions
- Bumped dependency versions to the latest
## 2018-06-28 - 3.0.1 - core
fix(core): slim down dependencies
- Slimmed down core dependencies
## 2018-05-03 - 3.0.0 - general
update
- General update
## 2018-05-03 - 2.0.12 - core / architecture
- change to an all rxjs Subject architecture
- system change
- fix(core): cleanup
- remove package-lock since using yarn
## 2018-01-27 - 2.0.11 - security
fix(improve security CI step):
- Improved security in the CI step
## 2018-01-27 - 2.0.10 - core / ci
- fix(core): remove vulnerable paths
- update ci (applied multiple times)
## 2018-01-27 - 2.0.09 - CI
add security step to CI
- Added an extra security step in the CI process
## 2017-10-12 - 2.0.08 - compatibility
ensure compatibility with code assertion library
- Ensured compatibility with the code assertion library
## 2017-10-12 - 2.0.07 - tests / cli
- fix tests and add .triggerOnlyOnProcessEnvCliCall()
- fix linting issues
## 2017-05-07 - 2.0.06 - tasks
fix promise rejection on standard task
- Fixed a promise rejection issue on standard tasks
## 2017-04-23 - 2.0.05 - tapbundle
use new tapbundle
- Switched to the new tapbundle for testing
## 2017-04-22 - 2.0.04 - tests
- comment out one test that makes problems due to tap
- update tests
## 2017-04-22 - 2.0.03 - npmextra
add npmextra.json
- Added npmextra.json for extra configuration
## 2017-04-22 - 2.0.02 - ci
update ci
- Updated CI configuration
## 2017-04-22 - 2.0.01 - misc
- update .gitignore
- update to latest standards
## 2016-12-18 - 2.0.00 - core
fix argvArg for observables
- Fixed the argvArg handling for observables
## 2016-12-18 - 1.0.16 - triggers
introduce triggers
- Introduced triggers
## 2016-11-19 - 1.0.15 - triggers / docs
- added .triggerCommandByName
- improve README
## 2016-11-19 - 1.0.14 - metadata
Update Metadata
- Updated project metadata
## 2016-11-19 - 1.0.13 - docs
improve README
- Improved the README documentation
## 2016-11-19 - 1.0.11 - core / tests / docs
- cleanup
- improve README
- update test file
## 2016-10-14 - 1.0.10 - deps
update deps
- Updated dependencies
## 2016-10-14 - 1.0.09 - standardJS
implement standardJS
- Implemented standardJS support
## 2016-09-04 - 1.0.08 - typings
improve typings
- Improved TypeScript typings
## 2016-09-04 - 1.0.07 - ci
fix ci
- Fixed CI configuration
## 2016-09-04 - 1.0.06 - base
fix base image
- Fixed the base image used for builds
## 2016-09-04 - 1.0.05 - interaction
- add page stage
- improve typings and docs
- update smartcli
- Add new file
- start interaction module
## 2016-08-26 - 1.0.04 - intellisense
improve intellisense
- Improved editor intellisense
## 2016-06-22 - 1.0.03 - compile
compile fix
- Fixed compilation issues
## 2016-06-22 - 1.0.02 - updates
- fix
- add getCommandPromise
- update deps and transition from npmts to npmts-g
## 2016-06-16 - 1.0.01 - tasks
- standard tasks now returns argv
- some cosmetics
- introduce new classes
## 2016-06-10 - 1.0.00 - version
- fix version return
- return argv to command
## 2016-06-10 - 0.0.13 - smartcli
- first version with basic funtionality
- remove bulk and add some features to Smartcli class
- start restructuring to use a smarter Smartcli class that handles command evaluation for you
- update dependencies
- compile
- add gitlab ci
- start smartcli class
- add class smartcli
- Update README and include commander
- fixed type issue
- fixed test issue
- update deps
## 2016-04-04 - 0.0.12 - deps
updated deps
- Updated dependencies
## 2016-04-04 - 0.0.11 - interface
- updated deps
- work in progress (noted twice)
- small interface fix
## 2015-11-09 - 0.0.10 - travis
improve travis process
- Improved the Travis process
## 2015-11-09 - 0.0.09 - tests / CLI
- add tests and fix some errors
- add Tests and improve TypeScript organization
- start smarter CLI logic
- fix small comment error (applied twice)
## 2015-10-14 - 0.0.08 - readme
- improved readme
- updated readme
- added devStatus badge
## 2015-10-12 - 0.0.07 - various
- improved return objects
- (Minor dependency updates and CI tweaks for beautylog and travis were also applied in this version)
## 2015-10-06 - 0.0.05 - CLI
- small update
- now handling CLI options
## 2015-10-05 - 0.0.4 - tests
modified test
## 2015-10-05 - 0.0.02 - travis / tests
- added travis + tests
- package.json update
## 2015-10-04 - 0.0.01 - initial
added initial structure
## 2015-10-04 - unknown - initial
Initial commit
---
## Summary of Omitted Versions
The following versions contained no additional userfacing changes beyond version bumps and are summarized here: 1.0.12, 0.0.6, and 0.0.3.

View File

@ -9,17 +9,19 @@
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartcli",
"description": "A library for easily creating observable CLI tasks with support for commands, arguments, and options.",
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.",
"npmPackagename": "@push.rocks/smartcli",
"license": "MIT",
"keywords": [
"CLI",
"command line",
"observable",
"tasks",
"reactive",
"asynchronous",
"commands",
"arguments",
"options",
"alias",
"typescript",
"node.js",
"development tool"

View File

@ -1,8 +1,8 @@
{
"name": "@push.rocks/smartcli",
"private": false,
"version": "4.0.11",
"description": "A library for easily creating observable CLI tasks with support for commands, arguments, and options.",
"version": "4.0.12",
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
@ -19,10 +19,12 @@
"CLI",
"command line",
"observable",
"tasks",
"reactive",
"asynchronous",
"commands",
"arguments",
"options",
"alias",
"typescript",
"node.js",
"development tool"
@ -63,4 +65,4 @@
"browserslist": [
"last 1 chrome versions"
]
}
}

919
readme.md
View File

@ -1,117 +1,902 @@
# @push.rocks/smartcli
easy observable cli tasks
A library for easily creating observable CLI tasks with support for commands, arguments, and options.
## Install
To install `@push.rocks/smartcli`, use the following command in your terminal:
To install the package, simply run:
```sh
npm install @push.rocks/smartcli --save
```
This will add `@push.rocks/smartcli` as a dependency to your project's `package.json` file and download it into the `node_modules` folder.
This command will add @push.rocks/smartcli as a dependency in your package.json and download it into your node_modules folder.
## Usage
The `@push.rocks/smartcli` module is designed to simplify the creation of command-line interfaces (CLIs) by providing an easy-to-use API for managing CLI commands and options. It combines observables with the parsing power of [yargs-parser](https://www.npmjs.com/package/yargs-parser) to offer a dynamic and flexible way to handle various CLI tasks.
The @push.rocks/smartcli library was created to simplify the development of command-line tools using a reactive programming paradigm. Built with TypeScript and ESM syntax in mind, this library lets you define and handle CLI commands, arguments, and options easily. It leverages observables to allow asynchronous operations and reactive logic in your CLI commands, making it ideal for tasks like file manipulation, network calls, or interactive command processing.
### Getting Started
In this Usage section, we will explore an extensive range of practical examples and in-depth explanations, guiding you through the entire process—from setting up a basic CLI to handling advanced asynchronous commands with observables. Well discuss how to define commands, set up aliases, handle options, integrate asynchronous tasks, and manage errors gracefully. In doing so, you will gain a complete understanding of all that smartcli has to offer, supported by multiple comprehensive TypeScript examples.
First, ensure you have TypeScript and the necessary types for node installed in your project. If not, you can add them by running:
─────────────────────────────
### 1. Getting Started: Setting Up Your CLI
Before you start using smartcli, ensure that you are working in a TypeScript project environment. If you havent set up TypeScript in your project yet, initialize it with the following commands:
```sh
npm install typescript @types/node --save-dev
npx tsc --init
```
Then, import the `Smartcli` class from the `@push.rocks/smartcli` package.
Once your environment is ready, you can start by creating a new TypeScript file (e.g., cli.ts) where you will import smartcli and set up your command-line application.
Below is a minimal example demonstrating how to instantiate smartcli, create a basic command, and initiate command parsing. For this example, lets create a simple CLI tool that responds to the command "greet" by printing a greeting message.
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
```
import { Subject } from '@push.rocks/smartrx';
### Creating an Instance
// Create an instance of Smartcli
const cli = new Smartcli();
Create an instance of `Smartcli`. This instance will be used to define and manage your CLI commands.
// Define a command where the user types "greet"
const greetCommand = cli.addCommand('greet');
```typescript
const mySmartcli = new Smartcli();
```
### Defining Commands
With `Smartcli`, you can define commands that your CLI tool can execute. Here's how you can add a new command:
```typescript
mySmartcli.addCommand('install').subscribe((argv) => {
console.log('Install command was called with arguments:', argv);
// Subscribe to the greet commands output, providing your logic
greetCommand.subscribe((argv: any) => {
// Check if there is a "name" option available; if not, default to "World"
const name = argv.name || 'World';
console.log(`Hello, ${name}!`);
});
```
In this example, when the user types `install` after your CLI tool's name in the terminal, the provided function will execute, printing the parsed arguments to the console.
### Handling Options
Options can be accessed using the `getOption` method. If you have an option named `--config` or `-c`, you can access its value like this:
```typescript
const configValue = mySmartcli.getOption('config');
console.log('Config value:', configValue);
```
### Default Task (Standard Command)
If you want to perform a task when no specific command is provided, you can use the `standardCommand` method:
```typescript
mySmartcli.standardCommand().subscribe((argv) => {
console.log('No specific command provided. Running default task with arguments:', argv);
// Define a standard task if no command is provided (this will act as a fallback)
cli.standardCommand().subscribe((argv: any) => {
console.log('No command specified. Showing default help usage:');
console.log('Usage: cli greet --name [YOUR_NAME]');
});
```
### Version and Help
// Optionally, add version support
cli.addVersion('1.0.0');
To add a version option that prints the version of your CLI tool, use `addVersion`:
```typescript
mySmartcli.addVersion('1.0.0');
```
For a help option that displays helpful information about your CLI commands and options, use `addHelp`:
```typescript
mySmartcli.addHelp({
helpText: 'Here are the commands you can use...'
// Optionally, add a help command to provide custom help information
cli.addHelp({
helpText: `
SmartCLI Help - Usage Information:
• greet: Prints a greeting message. Use --name to specify your name.
• -v, --version: Prints the current version.
• Without a command, the default task shows a help message.
`,
});
// Start parsing command-line arguments using smartcli
cli.startParse();
```
### Parsing and Execution
In the above script:
- We import the Smartcli class from the package.
- We create a new instance of Smartcli.
- We add a command named “greet” that will be executed when the user types “greet” in the CLI.
- We subscribe to the commands observable, which fires when the command is matched.
- We add a standard command to serve as a fallback when no specific command is provided.
- We configure version and help output.
- Finally, we call startParse() to begin processing command-line inputs.
After defining all your commands and options, call `startParse` to begin parsing the command line input and execute the corresponding actions:
─────────────────────────────
### 2. Defining Multiple Commands and Handling Options
smartcli enables you to define multiple commands that your CLI can execute. Imagine you want a CLI tool that supports several functionalities such as “install”, “uninstall”, and “update”. Each command may need to handle its own set of parameters and options. Lets see how to set up multiple commands:
```typescript
mySmartcli.startParse();
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
const cli = new Smartcli();
// Command: install
const installCommand = cli.addCommand('install');
installCommand.subscribe((argv: any) => {
// You can access additional command line options as properties on argv, e.g. --package
const packageName = argv.package;
if (!packageName) {
console.log('Please specify the package to install using --package option.');
return;
}
console.log(`Installing package: ${packageName} ...`);
// Add your asynchronous installation process here (e.g., calling external APIs or running tasks)
});
// Command: uninstall
const uninstallCommand = cli.addCommand('uninstall');
uninstallCommand.subscribe((argv: any) => {
const packageName = argv.package;
if (!packageName) {
console.log('Please specify the package to uninstall using --package option.');
return;
}
console.log(`Uninstalling package: ${packageName} ...`);
// Include uninstallation logic, such as cleaning up dependencies
});
// Command: update
const updateCommand = cli.addCommand('update');
updateCommand.subscribe((argv: any) => {
console.log('Updating all packages...');
// Execute update logic, possibly involving asynchronous operations
});
// A standard task, in case no command is provided
cli.standardCommand().subscribe((argv: any) => {
console.log('No specific command provided. Please use one of the following: install, uninstall, update.');
});
// Start parsing user input
cli.startParse();
```
### Advanced Usage: Command Aliases
In this example:
- Multiple commands are registered using addCommand().
- Each commands subscriber receives a parsed argv object containing options defined by the user.
- We leverage the argv object to extract key options such as package (--package) that are necessary for the respective operation.
- The standard command notifies the user if no valid command is entered.
You can also define aliases for your commands, allowing users to use alternate names for them:
─────────────────────────────
### 3. Using Command Aliases
A common requirement for CLI applications is to provide short aliases for commands. With smartcli, you can set up aliases so that users can type shorthand commands instead of the full command names. Heres how to define and use command aliases:
```typescript
mySmartcli.addCommandAlias('install', 'i');
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
const cli = new Smartcli();
// Add a primary command "install"
const installCommand = cli.addCommand('install');
installCommand.subscribe((argv: any) => {
console.log('Executing installation process...');
// Installation logic here
});
// Add an alias, so "i" will also trigger the "install" command
cli.addCommandAlias('install', 'i');
// You could define more aliases if needed:
cli.addCommandAlias('uninstall', 'uni');
cli.addCommandAlias('update', 'upd');
// Define other commands similarly
const uninstallCommand = cli.addCommand('uninstall');
uninstallCommand.subscribe((argv: any) => {
console.log('Executing uninstallation process...');
});
const updateCommand = cli.addCommand('update');
updateCommand.subscribe((argv: any) => {
console.log('Executing update process...');
});
// Start parsing command-line arguments
cli.startParse();
```
With this setup, both `install` and `i` will trigger the same command action.
Key points regarding aliases:
- The addCommandAlias method maps a shorter or alternative command name to the primary command.
- Internally, when the command line input is parsed, smartcli checks for both the registered command and its aliases.
- This feature improves user experience by allowing shorter commands for frequently used operations.
### Observables and Async Operations
─────────────────────────────
Since commands in `smartcli` are handled using observables, you can easily integrate asynchronous operations within your command actions. This makes it perfect for CLI tasks that involve file operations, network requests, or any other async tasks.
### 4. Accessing Command-Line Options
### Conclusion
smartcli is built on top of yargs-parser, making it easy to retrieve options specified on the command line. For example, you may have a command that requires options like --config, --verbose, or custom flags. The getOption method can help you retrieve these options anywhere in your script. Consider the following example:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
const cli = new Smartcli();
// Define a command “deploy” that uses options
const deployCommand = cli.addCommand('deploy');
deployCommand.subscribe((argv: any) => {
// Access options directly via the parsed argv object:
const configFile = cli.getOption('config');
const isVerbose = cli.getOption('verbose');
console.log('Starting deployment...');
if (configFile) {
console.log(`Using config file: ${configFile}`);
}
if (isVerbose) {
console.log('Verbose mode is enabled.');
}
// Your deployment logic could be asynchronous; use observables or promises as needed
});
// Start parsing the CLI arguments
cli.startParse();
```
In this scenario, the getOption method abstracts the direct use of yargs-parser, letting you focus on the application logic rather than argument parsing intricacies. Whether your CLI requires configuration files or toggles for debug modes, getOption enables a clean and efficient way to extract these parameters.
─────────────────────────────
### 5. Integrating Asynchronous Operations with Observables
One of the most powerful aspects of @push.rocks/smartcli is its ability to integrate asynchronous tasks using observables. By leveraging the RxJS observable pattern, you can easily chain actions, manage asynchronous flows, and even handle errors gracefully during command execution.
Imagine a scenario where you have to fetch data from an external API or perform lengthy file I/O operations before the CLI outputs its result. In such cases, you can integrate asynchronous libraries or directly use RxJS to handle the asynchronous commands. Heres an example demonstrating asynchronous behavior:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { of, throwError } from 'rxjs';
import { delay, catchError, tap, switchMap } from 'rxjs/operators';
const cli = new Smartcli();
// Define a command 'fetch-data' that simulates asynchronous data retrieval
const fetchCommand = cli.addCommand('fetch-data');
fetchCommand.subscribe((argv: any) => {
console.log('Starting the data fetching process...');
// Simulate a network request using an observable that emits a value after a delay
of({ data: 'Sample data from API' })
.pipe(
delay(2000),
tap((response) => {
console.log('Data received:', response.data);
}),
switchMap((response) => {
// Further process the data asynchronously if needed
return of(response.data + ' processed');
}),
tap((processedData) => {
console.log('Processed Data:', processedData);
}),
catchError((error) => {
console.error('An error occurred during data fetching:', error);
return throwError(() => new Error('Data fetch failed'));
})
)
.subscribe({
next: (result) => {
console.log('Asynchronous operation complete with result:', result);
},
error: (err: any) => {
console.error('Error in observable chain:', err);
},
});
});
// Start parsing CLI input
cli.startParse();
```
In this example:
- We simulate an asynchronous API call using RxJSs of() combined with delay().
- The observable chain processes the emitted value by tapping into the data, then further transforming it.
- Errors in the asynchronous pipeline are caught using catchError to allow for graceful degradation.
- Finally, the subscriber is notified when the asynchronous task completes.
This pattern is especially useful if your CLI commands perform operations that cannot be completed synchronously, such as interacting with remote servers, processing large datasets, or performing computationally expensive tasks.
─────────────────────────────
### 6. Combining Multiple Features for a Complex CLI Application
Developers often need to build CLI tools that incorporate many of the features described above into a single cohesive application. Below, we walk through a comprehensive example that assembles multiple commands, handles aliases, incorporates asynchronous tasks, provides detailed help and versioning information, and gracefully handles unknown inputs.
Imagine we are developing a CLI tool to manage a simple task list. The CLI supports commands for adding a task, listing tasks, removing a task, and updating a tasks status. For demonstration purposes, we will simulate these tasks rather than connecting to a real database. We will also support an alias for the “list” command and implement both synchronous and asynchronous operations.
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { of, timer } from 'rxjs';
import { delay, tap, switchMap } from 'rxjs/operators';
// Let's simulate an in-memory task list
interface Task {
id: number;
description: string;
completed: boolean;
}
let tasks: Task[] = [];
// Instantiate Smartcli
const cli = new Smartcli();
// Command: add - to add a new task to the task list
const addCommand = cli.addCommand('add');
addCommand.subscribe((argv: any) => {
const taskDesc = argv.description || 'Untitled task';
const newTask: Task = { id: tasks.length + 1, description: taskDesc, completed: false };
tasks.push(newTask);
console.log(`Task added: [${newTask.id}] ${newTask.description}`);
});
// Command: list - list all tasks
const listCommand = cli.addCommand('list');
listCommand.subscribe((argv: any) => {
if (tasks.length === 0) {
console.log('No tasks available.');
return;
}
console.log('Task List:');
tasks.forEach((task) => {
console.log(`- [${task.id}] ${task.description} ${task.completed ? '(completed)' : ''}`);
});
});
// Add an alias for the list command
cli.addCommandAlias('list', 'ls');
// Command: remove - delete a task by its ID
const removeCommand = cli.addCommand('remove');
removeCommand.subscribe((argv: any) => {
const taskId = Number(argv.id);
if (!taskId) {
console.log('Please provide a valid task id using --id flag.');
return;
}
const taskIndex = tasks.findIndex(task => task.id === taskId);
if (taskIndex === -1) {
console.log(`Task with id [${taskId}] not found.`);
return;
}
const removedTask = tasks.splice(taskIndex, 1)[0];
console.log(`Task removed: [${removedTask.id}] ${removedTask.description}`);
});
// Command: complete - mark a task as completed
const completeCommand = cli.addCommand('complete');
completeCommand.subscribe((argv: any) => {
const taskId = Number(argv.id);
if (!taskId) {
console.log('Please provide a valid task id using --id flag.');
return;
}
const task = tasks.find(task => task.id === taskId);
if (!task) {
console.log(`Task with id [${taskId}] not found.`);
return;
}
// Simulate an asynchronous operation (e.g., updating a remote server) with RxJS timer
timer(1000)
.pipe(
tap(() => {
task.completed = true;
console.log(`Task [${task.id}] marked as completed.`);
})
)
.subscribe();
});
// Command: update - update an existing task's description
const updateCommand = cli.addCommand('update');
updateCommand.subscribe((argv: any) => {
const taskId = Number(argv.id);
const newDesc = argv.description;
if (!taskId || !newDesc) {
console.log('Usage: update --id [taskId] --description "New Description"');
return;
}
const task = tasks.find(task => task.id === taskId);
if (!task) {
console.log(`Task with id [${taskId}] not found.`);
return;
}
// Simulate a delay to represent asynchronous update (like saving to a database)
of(null)
.pipe(
delay(500),
tap(() => {
task.description = newDesc;
console.log(`Task [${task.id}] updated to: ${task.description}`);
})
)
.subscribe();
});
// Standard command to handle cases where no specific command is provided
cli.standardCommand().subscribe((argv: any) => {
console.log('No command provided. Available commands: add, list (ls), remove, complete, update.');
console.log('For help, try: --help');
});
// Add version information to support -v or --version flags
cli.addVersion('2.0.0');
// Add help text to provide an overview of available commands and options
cli.addHelp({
helpText: `
Task Manager CLI Help:
Commands:
add : Add a new task. Use --description "Your task here" to set the task details.
list (ls) : List all tasks.
remove : Remove a task by id. Use --id [taskID] to specify which task to remove.
complete : Mark a task as completed. Use --id [taskID] to specify which task.
update : Update a task's description. Use --id [taskID] and --description "New description".
Global Options:
--version, -v: Show the CLI version.
--help : Display this help information.
`,
});
// Start parsing command-line input
cli.startParse();
```
This layered example demonstrates multiple aspects:
• Each command is defined with its distinct business logic.
• Asynchronous operations are incorporated using RxJS operators such as timer, delay, and of to simulate delays.
• Aliases (such as ls for list) enhance usability.
• The help function provides a descriptive guide for end users.
• Standard commands act as fallbacks, ensuring that users always receive informative feedback.
─────────────────────────────
### 7. Developing an Interactive CLI with Error Handling and Feedback
In many practical applications, commands might reject invalid input or encounter errors during asynchronous operations. smartcli allows you to integrate error handling as part of your observable pipelines. This design is especially beneficial when real-world issues occur (e.g., network errors, missing arguments, or unrecognized commands).
Consider an extension of the “fetch-data” command where errors are handled meticulously. In the following example, we simulate fetching external data with built-in error handling:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { of, throwError } from 'rxjs';
import { delay, catchError, tap, switchMap } from 'rxjs/operators';
const cli = new Smartcli();
const fetchDataCommand = cli.addCommand('fetch-data');
fetchDataCommand.subscribe((argv: any) => {
console.log('Initiating data fetch...');
// Simulate a network fetch which may fail
const simulateNetworkCall = Math.random() > 0.5
? of({ data: 'Fetched data successfully!' }).pipe(delay(1500))
: throwError(() => new Error('Network Error: Unable to fetch data')).pipe(delay(1500));
simulateNetworkCall
.pipe(
tap((response: any) => {
console.log('Data received:', response.data);
}),
switchMap((response: any) => {
// Continue processing the data after successful fetch
return of(`Data processed: ${response.data.toUpperCase()}`);
}),
tap((processedData: string) => {
console.log(processedData);
}),
catchError((error: Error) => {
console.error('Encountered an error during fetch:', error.message);
// Optionally, provide recovery or fallback logic here
return of('Default data after error handling');
})
)
.subscribe({
next: (result: any) => {
console.log('Final result:', result);
},
error: (err: Error) => {
console.error('Error in subscription:', err.message);
},
});
});
// Define a standard fallback command in case no specific command is provided
cli.standardCommand().subscribe(() => {
console.log('No command provided. For fetching data, use: fetch-data');
});
// Start parsing the command
cli.startParse();
```
In this example, we simulate a network call that may either succeed or fail. Notice:
• We use throwError to simulate a network error.
• The catchError operator intercepts errors, logs the error message, and allows us to provide fallback data.
• This example demonstrates reactive error handling in smartcli, ensuring that your CLI tool remains robust even in adverse conditions.
─────────────────────────────
### 8. Advanced Integration: Combining Observables, Configuration Files, and Environment Variables
Many CLI applications require dynamically reading configurations from files or based on environment variables. Combining smartclis reactive capabilities with Nodes file system and environment handling can create truly adaptive CLI applications.
Lets build an example where a command “configure” reads a configuration file, uses environment variables, and then applies those settings to the CLIs operation. In this scenario, we assume that the configuration is stored in a JSON file. The command will read the file asynchronously, merge it with any options passed via the CLI, and then print out the resultant configuration.
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { promises as fs } from 'fs';
import path from 'path';
import { from } from 'rxjs';
import { switchMap, tap, catchError } from 'rxjs/operators';
const cli = new Smartcli();
// Command: configure
const configureCommand = cli.addCommand('configure');
configureCommand.subscribe((argv: any) => {
// Determine the configuration file path (default or provided via --config flag)
const configFilePath = argv.config || path.join(process.cwd(), 'config.json');
console.log(`Attempting to load configuration from: ${configFilePath}`);
// Read the configuration file asynchronously using fs.promises
from(fs.readFile(configFilePath, 'utf-8'))
.pipe(
switchMap((fileContent: string) => {
// Parse the JSON content of the configuration file
const fileConfig = JSON.parse(fileContent);
console.log('Configuration loaded from file:', fileConfig);
// Merge file configurations with additional options from command line, such as overriding values via --option flags
const finalConfig = { ...fileConfig, ...argv };
return from(Promise.resolve(finalConfig));
}),
tap((finalConfig) => {
console.log('Final merged configuration:', finalConfig);
// Here you might dynamically adjust your CLI behavior based on finalConfig values
}),
catchError((err: Error) => {
console.error('Error loading configuration file:', err.message);
return from(Promise.resolve({ error: 'default configuration activated' }));
})
)
.subscribe((result) => {
console.log('Configuration process completed with result:', result);
});
});
// Standard fallback command
cli.standardCommand().subscribe(() => {
console.log('No command specified. Please use one of the available commands (e.g. configure, add, list, remove, complete, update).');
});
// Start parsing commands
cli.startParse();
```
In this use case:
• We leverage Nodes fs.promises to read a configuration file asynchronously.
• The configuration file is merged with runtime arguments provided by the user.
• We employ RxJS operators to manage asynchronous operations and error handling.
• The final configuration is then outputted or used to alter further application behavior.
─────────────────────────────
### 9. Testing Your CLI Application
For robust CLI applications, automated tests are essential. smartclis design promotes testability by exposing its observable commands directly. You can simulate command inputs and verify that the respective command callbacks are executed correctly.
Below is an example test scenario using a testing framework (such as tap or jest) to verify that commands are being correctly registered and executed:
```typescript
// test/cli.test.ts
import { Smartcli } from '@push.rocks/smartcli';
import { Subject } from '@push.rocks/smartrx';
// Test to verify that a command executes as expected
const cli = new Smartcli();
let commandExecuted = false;
// Register a test command "testcmd"
const testCommand = cli.addCommand('testcmd');
testCommand.subscribe((argv: any) => {
commandExecuted = true;
console.log('Test command executed with:', argv);
});
// Simulate passing the command as if from process.argv
// For testing, you can modify process.argv temporarily
process.argv.splice(2, 0, 'testcmd', '--sample', 'value');
// Start parsing and executing the test command
cli.startParse();
// After parsing, verify if commandExecuted is true
setTimeout(() => {
if (commandExecuted) {
console.log('Test passed: command was executed.');
} else {
console.error('Test failed: command was not executed.');
}
}, 1000);
```
This test demonstrates:
• Creating an instance of smartcli.
• Adding a command and subscribing to its observable output.
• Simulating command-line input by adjusting process.argv.
• Validating that the command logic executes as expected.
─────────────────────────────
### 10. Best Practices and Recommendations
Now that we have explored various usage scenarios of smartcli, here are some best practices and recommendations to help you maximize the benefits of this library in your CLI development:
1. Modularize Your Commands
Organize your CLI application by separating commands into different modules or files. This aids maintainability and scalability, especially when dealing with a large number of functions.
2. Utilize Observables for Async Processing
Embrace the power of observables to manage asynchronous operations in a declarative manner. Utilize RxJS operators like switchMap, delay, and catchError to handle asynchronous workflows and potential errors.
3. Consistent Command Naming and Alias Management
Design a consistent naming scheme for your commands and define intuitive aliases to improve the overall user experience. This can be especially useful when commands are likely to be used repeatedly.
4. Offer Comprehensive Help and Feedback
Use the addHelp method to provide detailed usage instructions. A well-documented CLI helps end users understand available commands, options, and expected behaviors. Always consider fallback or default commands to guide users when unknown commands are entered.
5. Include Version Information
The addVersion method is a convenient way to ensure that users can quickly check which version of your CLI they are using, facilitating troubleshooting and support.
6. Integrate Error Handling Throughout
In a production CLI application, robust error handling can make the difference between a good user experience and a frustrating one. Incorporate error handling in observable pipelines to handle network failures or invalid user input gracefully.
7. Leverage Environment Variables and Configuration Files
For flexible CLI behavior, consider dynamically loading configuration from files or environment variables. This improves the usability of your CLI tool in different deployment environments.
8. Test Thoroughly
Since smartcli decouples command registration and execution, automated testing becomes straightforward. Write unit tests to simulate various command inputs and verify that your commands respond correctly. This helps to catch regressions and ensures a high-quality tool.
─────────────────────────────
### 11. Integrating with Other Reactive Libraries and Tools
smartclis design encourages integration with other reactive and asynchronous libraries. You can blend smartcli with external modules to create advanced workflows, such as chaining CLI commands with external APIs, handling streams of data, or integrating real-time logging utilities.
For instance, if your CLI needs to process a stream of events from a WebSocket or external feed, you can subscribe to those events within a command callback and process the data reactively. Heres an abstract example of integrating a WebSocket stream within a CLI command:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { webSocket } from 'rxjs/webSocket';
import { tap, catchError } from 'rxjs/operators';
const cli = new Smartcli();
const streamCommand = cli.addCommand('stream-data');
streamCommand.subscribe((argv: any) => {
const socketUrl = argv.url || 'wss://example.com/socket';
console.log(`Connecting to ${socketUrl}...`);
// Create a WebSocket subject that acts as an observable stream
const socketSubject = webSocket(socketUrl);
socketSubject.pipe(
tap((message: any) => console.log('Message received:', message)),
catchError((err: any) => {
console.error('Error in WebSocket stream:', err);
throw err;
})
).subscribe({
next: (msg: any) => {
// process messages as they arrive
},
error: (err: any) => {
console.error('WebSocket error:', err);
},
complete: () => {
console.log('WebSocket connection closed.');
}
});
});
// Fall back standard command if no argument provided
cli.standardCommand().subscribe(() => {
console.log('No command provided. To stream data, use: stream-data --url [WS_URL]');
});
// Start the CLI argument parsing
cli.startParse();
```
This example is an outline of how you might integrate real-time data streams with smartcli to build a highly interactive command-line tool. By combining reactive streams with smartclis command management, you can create advanced workflows and event-driven CLI applications.
─────────────────────────────
### 12. Debugging and Logging
Debugging command-line applications can sometimes be challenging. smartcli integrates well with logging libraries, such as @push.rocks/smartlog, to provide developers with useful output during command execution.
In your commands, consider logging key events, such as:
• When a command is received.
• The start and end of an asynchronous process.
• Errors or warnings when parsing options.
• Informational logs to indicate the progress of long-running tasks.
Below is a brief example illustrating logging within a command:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
const cli = new Smartcli();
const debugCommand = cli.addCommand('debug');
debugCommand.subscribe((argv: any) => {
console.log('Debug command initiated...');
console.log('Parsed arguments:', argv);
// Simulate an asynchronous task with logging
setTimeout(() => {
console.log('Debug task completed successfully.');
}, 1000);
});
cli.standardCommand().subscribe(() => {
console.log('For debugging, use: debug with appropriate flags.');
});
cli.startParse();
```
Using proper logging throughout your CLI can significantly ease troubleshooting and enable you to better understand the flow of execution.
─────────────────────────────
### 13. Comprehensive Walkthrough and Further Customization
Lets take a step-by-step walkthrough of a final, comprehensive example. In this example, we will create a CLI tool that not only supports multiple commands and asynchronous operations but also demonstrates how to work with configuration files, environment variables, reactive streams, debugging logs, and error handling. This walkthrough is designed to illustrate a real-world application where smartcli is brought to its fullest potential.
1. Initialization and Setup
Create a file called cli.ts and include the following content at its top:
```typescript
#!/usr/bin/env node
import { Smartcli } from '@push.rocks/smartcli';
import { promises as fs } from 'fs';
import path from 'path';
import { from, timer } from 'rxjs';
import { delay, tap, switchMap, catchError } from 'rxjs/operators';
const cli = new Smartcli();
// Set global version and help text
cli.addVersion('3.0.0');
cli.addHelp({
helpText: `
Comprehensive Task CLI:
• add : Add a new task. Use --description to set details.
• list (ls) : List tasks.
• remove : Remove a task with --id.
• complete : Complete a task with --id.
• config : Load configuration from a JSON file.
• debug : Run in debug mode.
`,
});
```
2. Adding Commands
Split your command definitions into separate sections for clarity. For every command, register the command, subscribe to its observable, and implement your business logic:
• For delayed operations and asynchronous processing, use RxJS observables with proper delays and error handling.
• For configuration loading, use a combination of fs.promises and observables to merge file-based settings and command-line arguments.
Example for the configuration command:
```typescript
const configCommand = cli.addCommand('config');
configCommand.subscribe((argv: any) => {
const configFilePath = argv.config || path.join(process.cwd(), 'config.json');
console.log(`Loading configuration from ${configFilePath}`);
from(fs.readFile(configFilePath, 'utf-8'))
.pipe(
switchMap((content: string) => {
const parsedConfig = JSON.parse(content);
console.log('Configuration loaded:', parsedConfig);
return from(Promise.resolve({ ...parsedConfig, ...argv }));
}),
tap((finalConfig) => {
console.log('Merged configuration:', finalConfig);
}),
catchError((err: Error) => {
console.error('Error loading configuration:', err.message);
return from(Promise.resolve({ error: true }));
})
)
.subscribe((result: any) => {
console.log('Configuration process complete.', result);
});
});
```
3. Handling Debug Mode and Logging
Create a debug command that logs extensive details about the input:
```typescript
const debugCommand = cli.addCommand('debug');
debugCommand.subscribe((argv: any) => {
console.log('Debug mode activated.');
console.log('Current process arguments:', process.argv);
console.log('Parsed options:', argv);
// Insert additional debugging routines as necessary
timer(500)
.pipe(
tap(() => {
console.log('Debug tasks executed.');
})
)
.subscribe();
});
```
4. Finalizing with a Standard Command
Always have a fallback standard command that provides instructions if no valid command is given:
```typescript
cli.standardCommand().subscribe(() => {
console.log('No command provided. Please use one of the available commands: add, list, remove, complete, config, debug.');
});
```
5. Starting the CLI Parser
After all command registrations, start the parser which will process the arguments and trigger the appropriate command:
```typescript
cli.startParse();
```
6. Running Your CLI
Make sure your file has proper executable permissions (e.g., chmod +x cli.ts) and is referenced in your package.jsons bin field if you plan on publishing it as a global CLI tool.
This complete walkthrough shows how you can use smartcli to build a full-featured, robust CLI application. By dividing functionality into separate commands, handling asynchronous operations gracefully, and providing detailed help and debugging support, you ensure that your CLI application is both powerful and user-friendly.
─────────────────────────────
### 14. Summary of Features and Next Steps for Your Project
The @push.rocks/smartcli library is a versatile solution for constructing command-line interfaces. Its core features include:
• Defining and subscribing to reactive commands.
• Seamless integration with RxJS for asynchronous and reactive operations.
• Support for command aliases, allowing multiple inputs to trigger the same functionality.
• A simple API to manage command-line options with sophisticated error handling.
• Built-in methods for adding version, help text, and fallback commands.
By exploring and combining these features, you can build robust, interactive, and user-friendly CLI tools that adapt to your applications unique requirements. Whether its a simple utility or a complex task manager, smartcli offers the flexibility and power to develop modern command-line applications using TypeScript and ESM syntax.
─────────────────────────────
### 15. Additional Considerations and Practical Tips
As you continue developing your CLI application with smartcli, consider these additional tips:
• Always validate command-line options to ensure your commands receive the expected input.
• Use descriptive log messages not only for debugging but also to provide users with clear feedback.
• Avoid coupling your command logic with side effects; design your commands as pure functions where possible, managing side effects through subscription callbacks.
• Explore further RxJS operators and patterns to enhance your observable pipelines. The more familiar you become with reactive programming, the more elegant your CLI code will be.
• Consider integrating configuration management libraries or even state management systems if your CLI tool grows in complexity.
• Write comprehensive integration tests that mimic real-world usage scenarios, ensuring that your CLI behaves correctly regardless of the input provided.
• Keep your project modular by refactoring commands into separate files or modules, which not only improves readability but also simplifies maintenance and future enhancements.
By adhering to these recommendations, you can create a CLI tool that is reliable, maintainable, and enjoyable to use.
─────────────────────────────
### 16. Final Thoughts on smartcli Usage
The extensive usage examples above have demonstrated how smartcli transforms the process of building CLI applications. By unifying command parsing, observable-based asynchronous handling, and modular command definitions within a single, cohesive framework, smartcli becomes an indispensable tool for developers.
Start by modifying the basic examples, then combine more features as your project requirements expand. With its intuitive API and extensive customization capabilities, smartcli enables you to develop professional-grade CLI tools that serve a wide range of use cases—from simple automation scripts to complex interactive applications.
We encourage you to experiment with the various functions outlined in this document. Explore different command structures, integrate external APIs, and test robust error-handling scenarios. The reactive approach provided by smartcli opens up many possibilities to build scalable, responsive, and user-friendly CLI interfaces.
Happy coding with smartcli, and may your command-line applications be as smart and dynamic as the library itself!
─────────────────────────────
This exhaustive guide and the corresponding examples provide you with all the tools and insights you need to leverage @push.rocks/smartcli in your projects. Enjoy developing your next observable CLI task application using TypeScript and ESM syntax!
`@push.rocks/smartcli` offers a robust and intuitive way to build CLI tools with TypeScript, leveraging the reactive programming paradigm. By following the examples provided in this guide, you'll be able to create a feature-rich command-line application tailored to your specific needs.
Remember, this is just the start. The true power of `smartcli` lies in its flexibility and the vast ecosystem of RxJS. Dive into the RxJS documentation to explore more ways to handle data streams and asynchronous events in your CLI app.
## License and Legal Information

View File

@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartcli',
version: '4.0.11',
description: 'A library for easily creating observable CLI tasks with support for commands, arguments, and options.'
version: '4.0.12',
description: 'A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.'
}