Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@effect/cli
Advanced tools
Installing @effect/cli
is straightforward and can be done using any of the popular package managers. Follow the steps below to get started with setting up @effect/cli
on your system.
@effect/cli
Choose your preferred package manager and run one of the following commands in your terminal:
Using npm:
npm install @effect/cli
Using pnpm:
pnpm add @effect/cli
Using yarn:
yarn add @effect/cli
@effect/cli
interacts directly with various platform-specific services like the file system and the terminal. Depending on the environment where you'll run your command-line application, you need to install the appropriate @effect/platform
package.
If your application will run in a Node.js environment, you'll need to install @effect/platform-node
. This package ensures that @effect/cli
can effectively interact with Node.js-specific functionalities.
Run one of the following commands based on your package manager:
Using npm:
npm install @effect/platform-node
Using pnpm:
pnpm add @effect/platform-node
Using yarn:
yarn add @effect/platform-node
After installing the necessary packages, you must configure your application to use the NodeContext.layer
from @effect/platform-node
. This step is crucial as it grants @effect/cli
access to all necessary Node.js services and APIs, ensuring your CLI tool functions correctly within the Node.js environment.
Here's how you can incorporate NodeContext.layer
into your application:
import { NodeContext, NodeRuntime } from "@effect/platform-node"
// Your application's setup code here
This configuration will make sure that your CLI application is fully integrated with the Node.js runtime, allowing it to perform optimally with access to system resources and services.
For a more detailed walkthrough, take a read through the Tutorial below.
@effect/cli
comes equipped with several powerful built-in options that enhance the functionality of your CLI applications without the need for additional coding. These options are ready to use immediately after installation and are designed to simplify common tasks and improve the user experience.
Here's a breakdown of the key built-in options available in @effect/cli
:
Log Level ([--log-level]
):
Command
's handler method--log-level (all | trace | debug | info | warning | error | fatal | none)
Command
's handler method. By setting this option, you can control the verbosity of the log output, ensuring that only logs of a certain priority or higher are output by your program.Shell Completions ([--completions]
):
--completions (bash | sh | fish | zsh)
Help ([-h | --help]
):
-h
or --help
Version ([--version]
):
--version
Wizard Mode ([--wizard]
):
--wizard
These built-in options are designed to make the CLI user-friendly and more accessible, especially for those who are new to command-line interfaces. They reduce the learning curve and provide immediate assistance, enhancing productivity and user engagement.
For instance, a new user can type the following to get a list of all commands and options:
your-cli-app --help
Or, to quickly add command completion to their shell, they might use:
source <(your-cli-app --completions bash)
@effect/cli
is a powerful framework designed to simplify the development of command-line applications in TypeScript. It employs a modular architecture that allows developers to create scalable and maintainable CLI tools. Below is a table highlighting its key features:
Feature | Description |
---|---|
Command Structure | Supports a hierarchical command structure with a top-level command and potentially multiple nested subcommands. |
Parsing Arguments | Built-in support for parsing command-line arguments efficiently. |
Generating Help Text | Automatically generates and displays help documentation for each command. |
Handling Subcommands | Facilitates the management and execution of nested subcommands. |
Built-in Options | Includes built-in options such as --help , --version , and shell completions for enhanced usability. |
Wizard Mode | Offers a Wizard Mode that guides users through constructing commands interactively. |
Platform-Specific Integration | Integrates with platform-specific services via @effect/platform packages, ensuring compatibility with diverse environments like Node.js. and Bun |
Every command-line application built with @effect/cli
consists of one or more commands (Command
). There is always a top-level command representing the application itself, and potentially multiple nested subcommands:
Command
.Command
, allowing you to organize functionality into distinct actions.--help
or --version
). These are specified using Options
and can be either required or optional. Options can be boolean flags or can accept values from user input.<directory>
). These are specified using Args
and represent the data that users need to provide to commands.This structure allows you to build complex CLI tools that are easy to extend and maintain. Whether you are adding new options to existing commands, creating new subcommands, or handling user inputs, @effect/cli
provides a structured and intuitive way to scale your application.
Starting with a basic "Hello World" application is a great way to get familiar with the @effect/cli
. Below is a step-by-step guide to creating your first command-line interface (CLI) application.
Begin by creating a new file for your project:
hello-world.ts
Now, let's write the code for your CLI. Open your hello-world.ts
file in your favorite code editor and insert the following TypeScript code:
// Import necessary modules from the libraries
import { Command } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
// Define the top-level command
const command = Command.make("hello-world", {}, () =>
Console.log("Hello World")
)
// Set up the CLI application
const cli = Command.run(command, {
name: "Hello World CLI",
version: "v1.0.0"
})
// Prepare and run the CLI application
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
Explanation of Code:
Import Statements:
Command
from @effect/cli
allows you to define commands and subcommands.NodeContext
and NodeRuntime
from @effect/platform-node
enable your CLI to interact and integrate seamlessly with the Node.js runtime environment. This setup is crucial for running your CLI on Node.js, as it ensures that all Node-specific APIs and functionalities are accessible.Console
and Effect
from effect
provide utilities for logging and managing effects, which are essential for handling asynchronous operations and side effects in your CLI.Command Definition:
Command.make
function creates a new command named "hello-world". This command is configured to print "Hello World" when executed, serving as the basic functionality of your CLI.CLI Configuration:
Command.run
function initializes your CLI application with a specific name and version, preparing it for execution.Execution Setup:
cli(process.argv)
call processes the command-line arguments.Effect.provide
to inject the NodeContext.layer
, which integrates the CLI with the Node.js environment, allowing your application to utilize Node-specific features and settings.NodeRuntime.runMain
ensures that your application is executed within the Node.js main runtime, handling any asynchronous tasks and managing the lifecycle of your CLI.After saving your hello-world.ts
file, you can run your CLI application directly from your terminal to see it in action. Here's how:
npx tsx hello-world.ts
# Expected Output: Hello World
Explanation of the Command:
tsx
: This is a command-line tool that enables direct execution of TypeScript files. It simplifies the development process by eliminating the need to manually compile TypeScript (*.ts
) files into JavaScript (*.js
) before running them. tsx
automatically compiles the TypeScript code on-the-fly and executes it, leveraging the Node.js environment.
npx
: Part of the npm (Node Package Manager) suite, npx
is used to execute packages. When you run npx tsx
, it temporarily installs tsx
if it isn't already present in your project's local node_modules
folder or globally on your machine. Then, npx
executes tsx
with the specified TypeScript file as its argument.
Usage in Your CLI: By using npx tsx hello-world.ts
, you're instructing npx
to execute your TypeScript file using tsx
. This command is especially useful for quick testing and development purposes, as it allows you to run your code directly without setting up a full TypeScript compilation workflow beforehand.
Your new CLI comes with a variety of built-in features designed to enhance usability and help you manage your application effectively:
You can display the version of your CLI by using the --version
option:
npx tsx hello-world.ts --version
# Output: v1.0.0
Your CLI automatically generates help documentation that describes available commands and options. This feature is useful when you need guidance on how the CLI operates or when you want to learn more about its capabilities.
npx tsx hello-world.ts --help # or -h
When you request help, the CLI will display information like this:
Hello World CLI
Hello World CLI v1.0.0
USAGE
$ hello-world
OPTIONS
--completions sh | bash | fish | zsh
One of the following: sh, bash, fish, zsh
Generate a completion script for a specific shell
This setting is optional.
(-h, --help)
A true or false value.
Show the help documentation for a command
This setting is optional.
--wizard
A true or false value.
Start wizard mode for a command
This setting is optional.
--version
A true or false value.
Show the version of the application
This setting is optional.
The --wizard
option activates the Wizard Mode in your CLI application, which provides a guided process for constructing commands. This is especially helpful for users who are new to your CLI or need assistance in building the correct command syntax.
To initiate the Wizard Mode, run your CLI application with the --wizard
option like this:
npx tsx hello-world.ts --wizard
When you start the Wizard Mode, the CLI will interactively guide you through the process of setting up a command. Here's what the interaction might look like:
Wizard Mode for CLI Application: Hello World CLI (v1.0.0)
Instructions
The wizard mode will assist you with constructing commands for Hello World CLI (v1.0.0).
Please answer all prompts provided by the wizard.
COMMAND: hello-world
Wizard Mode Complete!
You may now execute your command directly with the following options and arguments:
hello-world
✔ Would you like to run the command? … yes / no
Adding arguments to your commands allows your CLI applications to accept and process user input dynamically. Let's create a simple echo
CLI that echoes back whatever text you pass to it.
Begin by creating a new TypeScript file named echo.ts
. This file will contain all the code necessary to define a command that accepts user input as an argument.
// Import the necessary modules from the Effect libraries
import { Args, Command } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
// Define a text argument
const text = Args.text({ name: "text" })
// Create a command that logs the provided text argument to the console
const command = Command.make("echo", { text }, ({ text }) => Console.log(text))
// Configure and initialize the CLI application
const cli = Command.run(command, {
name: "Echo CLI",
version: "v0.0.1"
})
// Prepare and run the CLI application, providing necessary context and runtime
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
Understanding the Code:
text
declaration creates an input parameter that users need to provide when they run your CLI.Command.make
function sets up your CLI command. It's designed to take the text
argument and display it using Console.log
.Command.run
, the CLI application is formally structured with a specified name and version, and is ready to execute based on the defined command structure.cli(process.argv)
call processes the command-line arguments and runs the CLI application. The Effect.provide(NodeContext.layer)
injects the necessary Node.js context, and NodeRuntime.runMain
ensures proper execution within the Node.js environment.With your echo.ts
script ready, you can run it to interact with the argument you've set up.
Run Without Arguments
If you try running the CLI without specifying any arguments, it will remind you to provide the required text:
npx tsx echo.ts
# Output: Missing argument <text>
Run With Arguments
To pass the text argument correctly, wrap your input in quotes to treat it as a single string:
npx tsx echo.ts "This is a test"
# Output: This is a test
Common Mistake
Forgetting to use quotes can cause issues since each word might be interpreted as a separate argument:
npx tsx echo.ts This is a test
# Output: Received unknown argument: 'is'
Let's enhance the echo
CLI we built earlier by introducing an option that allows text to be displayed in bold. This tutorial will guide you on adding a --bold
option, abbreviated as -b
, to your command.
First, open the TypeScript file (echo.ts
) where your echo
command is defined. We will add a new option that enables users to choose whether their text output should be bold.
Below is the updated version of your code with the bold option included:
import { Args, Command, Options } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
const text = Args.text({ name: "text" })
// Define the 'bold' option with an alias '-b'
const bold = Options.boolean("bold").pipe(Options.withAlias("b"))
// Create the command that outputs the text with bold formatting if the bold option is used
const command = Command.make("echo", { text, bold }, ({ bold, text }) =>
Console.log(bold ? `\x1b[1m${text}\x1b[0m` : text)
)
const cli = Command.run(command, {
name: "Echo CLI",
version: "v0.0.2"
})
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
Bold Option: The bold
declaration creates a boolean option that users can toggle. Using Options.withAlias("b")
allows the option to be invoked with a shorter -b
flag. This makes the command easier to type and remember.
Command Functionality: Within the function of the command, there's a check to see if the bold
option is true. If it is, the text is formatted with ANSI escape codes (\x1b[1m
and \x1b[0m
) to appear bold in the terminal. This formatting adds visual emphasis and can help distinguish output in complex console applications.
With the command updated, you can now see the effect of the --bold
or -b
option by running:
npx tsx echo.ts --bold "This is a test"
or
npx tsx echo.ts -b "This is a test"
Expected Output:
Both commands will display "This is a test" in bold text, assuming your terminal supports ANSI escape codes.
When using your CLI, it's crucial to understand the order in which you specify options and arguments. By default, the @effect/cli
parses Options
and Args
before any subcommands. This means that options need to be placed directly after the main command, and before any subcommands or additional arguments.
For example, the command:
npx tsx echo.ts "This is a test" -b
would not work because the -b
option appears after the text argument "This is a test"
. The parser expects options to be specified before any standalone arguments or subcommands. This ensures that the options are correctly associated with the main command and not misinterpreted as arguments for a subcommand or additional text.
In this section, we will continue to improve our echo
CLI by introducing options to customize the color of the output text. This enhancement not only adds visual customization but also demonstrates how to use valued options to modify the behavior of commands.
Start by opening the TypeScript file where your echo
command is defined. We will add new functionalities that allow for text coloring and optional bold formatting.
Below is the modified version of your code, now including options for text color:
import { Args, Command, Options } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect, Option } from "effect"
// Define a text argument
const text = Args.text({ name: "text" })
// Define the 'bold' option with an alias '-b'
const bold = Options.boolean("bold").pipe(Options.withAlias("b"))
// Define the 'color' option with choices and an alias '-c'
const color = Options.choice("color", ["red", "green", "blue"]).pipe(
Options.withAlias("c"),
Options.optional
)
// Color codes for ANSI escape sequences
const colorCodes = {
red: "\x1b[31m",
green: "\x1b[32m",
blue: "\x1b[34m"
}
const resetCode = "\x1b[0m"
// Function to apply ANSI color codes based on user input
const applyColor = (text: string, color: Option.Option<string>): string =>
Option.match(color, {
onNone: () => text,
onSome: (color) => `${colorCodes[color]}${text}${resetCode}`
})
// Create the command that outputs formatted text
const command = Command.make(
"echo",
{ text, bold, color },
({ bold, color, text }) => {
let formattedText = applyColor(text, color)
if (bold) {
formattedText = `\x1b[1m${formattedText}\x1b[0m`
}
return Console.log(formattedText)
}
)
const cli = Command.run(command, {
name: "Echo CLI",
version: "v0.0.3"
})
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
Color Options: The color
option enables users to specify the text color as "red", "green", or "blue", enhancing the visual aspect of the output. This option uses Options.choice
to provide a list of possible values.
applyColor Function: This function applies the chosen ANSI color code to the text. If no color is selected, the text remains unchanged. This allows for dynamic customization of the output based on user preference.
With the new color and bold options, you can now run the CLI and see colored text output. Here's how to use these options:
npx tsx echo.ts --bold --color red "This is a test"
npx tsx echo.ts -b -c green "Another test"
Expected Output:
These commands will print "This is a test" in bold red and "Another test" in bold green, provided your terminal supports ANSI colors.
Let's enhance the functionality of your echo
CLI by adding a new subcommand called repeat
. This subcommand will allow users to repeat a specified message multiple times, providing a practical example of how to extend a CLI application.
Open the TypeScript file where your echo
command is defined. We'll incorporate the repeat
subcommand into this setup.
Repeat
SubcommandHere is how you can update your code to include the repeat
subcommand:
import { Args, Command, Options } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect, Option } from "effect"
const text = Args.text({ name: "text" })
const bold = Options.boolean("bold").pipe(Options.withAlias("b"))
const color = Options.choice("color", ["red", "green", "blue"]).pipe(
Options.withAlias("c"),
Options.optional
)
const colorCodes: { [key: string]: string } = {
red: "\x1b[31m",
green: "\x1b[32m",
blue: "\x1b[34m"
}
const resetCode = "\x1b[0m"
const applyColor = (text: string, color: Option.Option<string>): string =>
Option.match(color, {
onNone: () => text,
onSome: (color) => `${colorCodes[color]}${text}${resetCode}`
})
// Argument for the number of repetitions
const count = Args.integer().pipe(Args.withDefault(1))
// Creating the repeat subcommand
const repeat = Command.make("repeat", { count }, ({ count }) =>
echo.pipe(
Effect.andThen((config) => Effect.repeatN(echo.handler(config), count - 1))
)
)
// Main echo command
const echo = Command.make(
"echo",
{ text, bold, color },
({ bold, color, text }) => {
let formattedText = applyColor(text, color)
if (bold) {
formattedText = `\x1b[1m${formattedText}\x1b[0m`
}
return Console.log(formattedText)
}
)
// Combining commands
const command = echo.pipe(Command.withSubcommands([repeat]))
const cli = Command.run(command, {
name: "Echo CLI",
version: "v0.0.4"
})
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
count
. This argument specifies the number of times the echo message will be repeated.count
argument to repeat the message the specified number of times.repeat
subcommand into the existing echo
command structure. This means users can activate this feature by simply appending the repeat
keyword followed by the desired count after their message in the command line. For example, echo "Hello" repeat 5
would output "Hello" five times.[!NOTE] Since
Command
is a subtype ofEffect
, you can useEffect.andThen
within a subcommand's handler to directly access and utilize theConfig
from a parent command, and subsequently apply its handler.
Repeat
SubcommandWith the repeat
subcommand added, you can now use it to repeat messages:
npx tsx echo.ts -b -c red "This is a test" repeat 3
Expected Output:
This command will output "This is a test" in bold red text three times, demonstrating both the color and repeat functionalities.
In this tutorial, we will create a basic version of a Git-like command-line interface (CLI) called minigit
using the powerful @effect/cli
library. Our goal is to replicate a small set of Git commands to demonstrate how you can build structured CLI tools:
minigit [-v | --version] [-h | --help] [-c <name>=<value>]
minigit add [-v | --verbose] [--] [<pathspec>...]
minigit clone [--depth <depth>] [--] <repository> [<directory>]
[!NOTE] This guide focuses on the setup and parsing of commands. Implementing the actual functionality of these commands is beyond the scope of this tutorial but can be developed further based on the patterns shown here.
The full code for this CLI application can be found in our examples directory.
Begin by creating a TypeScript file named minigit.ts
. This file will host all your CLI application code. We will structure our CLI with three main commands, demonstrating the powerful features of the @effect/cli
library.
Let's start by setting up the primary command for our CLI, named minigit
. To do this, we use the Command.make
constructor which is pivotal for structuring commands in the @effect/cli
framework.
import { Command } from "@effect/cli"
import { Effect } from "effect"
// Define the main 'minigit' command
const minigit = Command.make(
"minigit",
// Configuration object for the command
{},
// Handler function that executes the command
(config) => Effect.succeed("Welcome to Minigit!")
)
Command.make
Function: This function constructs a new Command
. It requires three parameters:
Options
) and arguments (Args
) that the command can accept.The Command
interface in @effect/cli
is structured as follows:
export interface Command<Name extends string, R, E, A> {
readonly handler: (_: A) => Effect<void, E, R>
// Additional properties and methods...
}
Name
(Name extends string
): A unique string that identifies the command.R
(Environment): Defines the dependencies or the environment needed by the command's handler.E
(Expected Errors): Specifies the types of errors the command might expect during its execution.A
(Arguments/Configuration): Represents the configuration object the handler receives.Command Name:
The name
parameter in Command.make
designates the command's identifier. This name is crucial as it is used to invoke the command from the command line. For example, if we have a CLI application called my-cli-app
with a single subcommand named foo
, then executing the following command will run the foo
command in your CLI application:
my-cli-app foo
Command Configuration:
The configuration parameter allows you to define what options and arguments the command can accept. This setup includes both simple flags and more complex objects. The Config
is an object of key/value pairs where the keys are just identifiers and the values are the Options
and Args
that the Command
may receive. The Config
object can have nested Config
objects or arrays of Config
objects. When the CLI application is actually executed, the command Config
is parsed from the command-line options and arguments following the command name.
Command Handler:
This function is where the action happens. It takes the parsed configuration and executes the core functionality of the command, utilizing the full capabilities of the Effect
framework for managing effects and asynchronous operations.
Let's apply what we've learned from using the Command.make
method by defining the primary command for our minigit
CLI application. This command will include configurations that handle various options like version, help, and custom key-value pairs.
import { Command, Options } from "@effect/cli"
import { Console, Option } from "effect"
// minigit [--version] [-h | --help] [-c <name>=<value>]
const configs = Options.keyValueMap("c").pipe(Options.optional)
// Define the main 'minigit' command
const minigit = Command.make(
"minigit",
// Configuration object for the command
{ configs },
// Handler function that executes the command
({ configs }) =>
Option.match(configs, {
onNone: () => Console.log("Running 'minigit'"),
onSome: (configs) => {
const keyValuePairs = Array.from(configs)
.map(([key, value]) => `${key}=${value}`)
.join(", ")
return Console.log(
`Running 'minigit' with the following configs: ${keyValuePairs}`
)
}
})
)
Options Configuration:
Options.keyValueMap("c")
: This line sets up an option that accepts key-value pairs, allowing the user to input configurations in the format -c key=value
.Options.optional
: By chaining this combinator, the -c
option becomes optional, meaning the CLI will operate correctly whether or not this option is provided.Command Execution:
Option.match
function to determine how to respond based on whether the user has provided any key-value configurations.onNone
), it simply logs "Running 'minigit'."onSome
), it logs these configurations in a readable string format, enhancing user feedback and interaction.You may have noticed the lack of explicit version and help options in our command setup. This is due to @effect/cli
's design, which includes several built-in options such as --version
and --help
(see Built-In Options. These are automatically available and do not need to be manually configured, simplifying the setup of common CLI functionalities.
minigit
CLI with SubcommandsBuilding on our basic minigit
CLI, we'll now introduce two key subcommands: add
and clone
. These subcommands will demonstrate how to handle more complex command structures and multiple parameters using the @effect/cli
library.
We'll continue our minigit
CLI development by incorporating add
and clone
subcommands to handle specific actions, much like the original Git commands.
import { Args, Command, Options } from "@effect/cli"
import { Console, Option, Array } from "effect"
// minigit [--version] [-h | --help] [-c <name>=<value>]
const configs = Options.keyValueMap("c").pipe(Options.optional)
const minigit = Command.make("minigit", { configs }, ({ configs }) =>
Option.match(configs, {
onNone: () => Console.log("Running 'minigit'"),
onSome: (configs) => {
const keyValuePairs = Array.fromIterable(configs)
.map(([key, value]) => `${key}=${value}`)
.join(", ")
return Console.log(
`Running 'minigit' with the following configs: ${keyValuePairs}`
)
}
})
)
// minigit add [-v | --verbose] [--] [<pathspec>...]
const pathspec = Args.text({ name: "pathspec" }).pipe(Args.repeated)
const verbose = Options.boolean("verbose").pipe(Options.withAlias("v"))
const minigitAdd = Command.make(
"add",
{ pathspec, verbose },
({ pathspec, verbose }) => {
const paths = Array.match(pathspec, {
onEmpty: () => "",
onNonEmpty: (paths) => ` ${Array.join(paths, " ")}`
})
return Console.log(
`Running 'minigit add${paths}' with '--verbose ${verbose}'`
)
}
)
// minigit clone [--depth <depth>] [--] <repository> [<directory>]
const repository = Args.text({ name: "repository" })
const directory = Args.text({ name: "directory" }).pipe(Args.optional)
const depth = Options.integer("depth").pipe(Options.optional)
const minigitClone = Command.make(
"clone",
{ repository, directory, depth },
(config) => {
const depth = Option.map(config.depth, (depth) => `--depth ${depth}`)
const repository = Option.some(config.repository)
const optionsAndArgs = Array.getSomes([depth, repository, config.directory])
return Console.log(
"Running 'minigit clone' with the following options and arguments: " +
`'${Array.join(optionsAndArgs, ", ")}'`
)
}
)
Importing Modules:
Args
module from @effect/cli
is utilized to define positional arguments for both add
and clone
subcommands.Array
module from effect
is used to handle arrays and provide utility functions like Array.match
and Array.join
.Configuring Commands:
Options.withAlias
method simplifies command usage by providing shorthand aliases like -v
for --verbose
.Command Handlers:
Now that you've defined all the necessary commands for your CLI application, it's time to assemble them into a fully functional command-line interface. This section will guide you through setting up the CLI to run within a NodeJS environment, assuming that you've already installed @effect/platform-node
as described in the Installation guide.
Let's put together the minigit
CLI:
import { Args, Command, Options } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect, Option, Array } from "effect"
// minigit [--version] [-h | --help] [-c <name>=<value>]
const configs = Options.keyValueMap("c").pipe(Options.optional)
const minigit = Command.make("minigit", { configs }, ({ configs }) =>
Option.match(configs, {
onNone: () => Console.log("Running 'minigit'"),
onSome: (configs) => {
const keyValuePairs = Array.fromIterable(configs)
.map(([key, value]) => `${key}=${value}`)
.join(", ")
return Console.log(
`Running 'minigit' with the following configs: ${keyValuePairs}`
)
}
})
)
// minigit add [-v | --verbose] [--] [<pathspec>...]
const pathspec = Args.text({ name: "pathspec" }).pipe(Args.repeated)
const verbose = Options.boolean("verbose").pipe(Options.withAlias("v"))
const minigitAdd = Command.make(
"add",
{ pathspec, verbose },
({ pathspec, verbose }) => {
const paths = Array.match(pathspec, {
onEmpty: () => "",
onNonEmpty: (paths) => ` ${Array.join(paths, " ")}`
})
return Console.log(
`Running 'minigit add${paths}' with '--verbose ${verbose}'`
)
}
)
// minigit clone [--depth <depth>] [--] <repository> [<directory>]
const repository = Args.text({ name: "repository" })
const directory = Args.text({ name: "directory" }).pipe(Args.optional)
const depth = Options.integer("depth").pipe(Options.optional)
const minigitClone = Command.make(
"clone",
{ repository, directory, depth },
(config) => {
const depth = Option.map(config.depth, (depth) => `--depth ${depth}`)
const repository = Option.some(config.repository)
const optionsAndArgs = Array.getSomes([depth, repository, config.directory])
return Console.log(
"Running 'minigit clone' with the following options and arguments: " +
`'${Array.join(optionsAndArgs, ", ")}'`
)
}
)
// Combine all commands into the main 'minigit' command
const command = minigit.pipe(
Command.withSubcommands([minigitAdd, minigitClone])
)
// Initialize and run the CLI application
const cli = Command.run(command, {
name: "Minigit Distributed Version Control",
version: "v1.0.0"
})
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
minigit
command integrates both add
and clone
as subcommands, enabling a structured approach to handle different functionalities within the same CLI application.NodeContext
and NodeRuntime
to ensure that our CLI application correctly interacts with NodeJS's runtime environment, making full use of Node-specific features.Command.run
, the CLI application is formally structured with a specified name and version, and is ready to execute based on the defined command structure.cli(process.argv)
call processes the command-line arguments and runs the CLI application. The Effect.provide(NodeContext.layer)
injects the necessary Node.js context, and NodeRuntime.runMain
ensures proper execution within the Node.js environment.Now that your CLI application, minigit
, is fully assembled and prepared, it's time to see it in action! This section will guide you through running minigit
directly from the command line, using the example file minigit.ts
. If you are experimenting using the minigit
example from the provided repository, the same instructions apply, just replace the command file name with npx tsx ./examples/minigit.ts
.
First, let’s test out the built-in options to understand the core functionalities of our CLI application.
Run the following command to display the current version of your CLI application:
npx tsx minigit.ts --version
# Output: v1.0.0
To view the help documentation and understand the usage of each command within minigit
, use the -h
or --help
option:
npx tsx minigit.ts --help
Output:
Minigit Distributed Version Control
Minigit Distributed Version Control v1.0.0
USAGE
$ minigit [-c text]
OPTIONS
-c text
A user-defined piece of text.
This setting is a property argument which:
- May be specified a single time: '-c key1=value key2=value2'
- May be specified multiple times: '-c key1=value -c key2=value2'
This setting is optional.
--completions sh | bash | fish | zsh
One of the following: sh, bash, fish, zsh
Generate a completion script for a specific shell
This setting is optional.
(-h, --help)
A true or false value.
Show the help documentation for a command
This setting is optional.
--wizard
A true or false value.
Start wizard mode for a command
This setting is optional.
--version
A true or false value.
Show the version of the application
This setting is optional.
COMMANDS
- add [(-v, --verbose)] <pathspec>...
- clone [--depth integer] <repository> [<directory>]
This output provides a breakdown of all available commands and options, helping users navigate through the functionalities offered by minigit
.
To get more detailed help on subcommands like add
, simply append --help
after the subcommand:
npx tsx minigit.ts add --help
Output:
Minigit Distributed Version Control
Minigit Distributed Version Control v1.0.0
USAGE
$ add [(-v, --verbose)] <pathspec>...
ARGUMENTS
<pathspec>...
A user-defined piece of text.
This argument may be repeated zero or more times.
OPTIONS
(-v, --verbose)
A true or false value.
This setting is optional.
--completions sh | bash | fish | zsh
One of the following: sh, bash, fish, zsh
Generate a completion script for a specific shell
This setting is optional.
(-h, --help)
A true or false value.
Show the help documentation for a command
This setting is optional.
--wizard
A true or false value.
Start wizard mode for a command
This setting is optional.
--version
A true or false value.
Show the version of the application
This setting is optional.
Beyond viewing documentation, minigit
allows you to execute commands with specific options to tailor its behavior.
Here's how you can add files with the verbose option enabled or disabled:
npx tsx minigit.ts add .
# Output: Running 'minigit add .' with '--verbose false'
npx tsx minigit.ts add --verbose .
# Output: Running 'minigit add .' with '--verbose true'
npx tsx minigit.ts clone --depth 1 https://github.com/Effect-TS/cli.git
# Output: Running 'minigit clone' with the following options and arguments: '--depth 1, https://github.com/Effect-TS/cli.git'
In certain scenarios, you may want your subcommands to have access to the Options
and Args
passed to their parent commands.
Since Command
is a subtype of Effect
, you can use Effect.flatMap
within a subcommand's handler to extract the Config
from a parent command. This technique allows subcommands to utilize the configuration parameters specified at higher levels in the command hierarchy.
For example, let's say our minigit clone
subcommand needs access to the configuration parameters passed to the parent minigit
command via minigit -c key=value
. We can accomplish this by modifying the clone
command's handler to use Effect.flatMap
with the parent minigit
command:
const repository = Args.text({ name: "repository" })
const directory = Args.directory().pipe(Args.optional)
const depth = Options.integer("depth").pipe(Options.optional)
const minigitClone = Command.make(
"clone",
{ repository, directory, depth },
(subcommandConfig) =>
// By using `Effect.flatMap` on the parent command, we get access to its parsed config
Effect.flatMap(minigit, (parentConfig) => {
const depth = Option.map(
subcommandConfig.depth,
(depth) => `--depth ${depth}`
)
const repository = Option.some(subcommandConfig.repository)
const optionsAndArgs = Array.getSomes([
depth,
repository,
subcommandConfig.directory
])
const configs = Option.match(parentConfig.configs, {
onNone: () => "",
onSome: (map) =>
Array.fromIterable(map)
.map(([key, value]) => `${key}=${value}`)
.join(", ")
})
return Console.log(
"Running 'minigit clone' with the following options and arguments: " +
`'${Array.join(optionsAndArgs, ", ")}'\n` +
`and the following configuration parameters: ${configs}`
)
})
)
By examining the type of minigitClone
after incorporating the parent command, you can see the added context:
const minigitClone: Command.Command<
"clone",
// The parent `minigit` command has been added to the environment required by
// the subcommand's handler
Command.Command.Context<"minigit">,
never,
{
readonly repository: string
readonly directory: Option.Option<string>
readonly depth: Option.Option<number>
}
>
The parent command's context will be "erased" from the subcommand's environment when using Command.withSubcommands
:
const command = minigit.pipe(Command.withSubcommands([minigitClone]))
// ^? Command<"minigit", never, ..., ...>
Finally, run the command with some configuration parameters to see the result:
npx tsx minigit.ts -c key1=value1 clone --depth 1 https://github.com/Effect-TS/cli.git
# Running 'minigit clone' with the following options and arguments: '--depth 1, https://github.com/Effect-TS/cli.git'
# and the following configuration parameters: key1=value1
Understanding how command-line arguments are parsed in your applications is crucial for designing effective and user-friendly command interfaces. Here are the key rules that the internal command-line argument parser follows:
Options and arguments (collectively referred to as Options
/ Args
) associated with a command must be specified before any subcommands. This rule helps the parser determine which command the options apply to.
Examples:
Correct Usage:
program -v subcommand
In this example, the -v
option applies to the main program before the subcommand is processed.
Incorrect Usage:
program subcommand -v
Here, placing -v
after the subcommand causes confusion as to whether -v
applies to the main program or the subcommand.
The parser is designed to recognize options before any positional arguments. This ordering ensures clarity and prevents confusion between options and regular arguments.
Examples:
Valid Command:
program --option arg
This command correctly places the --option
before the positional argument arg
.
Invalid Command:
program arg --option
Placing an argument before an option is not allowed and can lead to errors in command processing.
If there are excess arguments that do not fit the expected structure of the command, the parser will return a ValidationError
. This safeguard prevents the execution of malformed or potentially harmful commands.
FAQs
A library for building command-line interfaces with Effect
The npm package @effect/cli receives a total of 18,057 weekly downloads. As such, @effect/cli popularity was classified as popular.
We found that @effect/cli demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.