Create A Command
The command feature helps you write CLIs that are simple to call and solid to maintain. You describe what the command accepts, the library reads process arguments, validates inputs, generates help, and calls your handler with already typed values.
Principle
A command is made of three parts:
- options, such as
--port 3000or--verbose; - subjects, meaning either positional arguments after options, or subcommands;
- a handler, called only when parsing succeeds.
Options and arguments rely on DataParser from @duplojs/utils. This turns CLI input, which always starts as text, into typed domain values.
A First Command
import { SC } from "@duplojs/server-utils";
import { DP } from "@duplojs/utils";
await SC.exec(
{
description: "Search text in a file",
options: [
SC.createBooleanOption(
"ignore-case",
{
aliases: ["i"],
description: "Ignore letter case",
},
),
],
subjects: [
SC.createArgument("pattern", DP.string()),
SC.createArgument("filePath", DP.string()),
],
},
({ options, args: { pattern, filePath } }) => {
const sensitivity = options["ignore-case"] ? "case-insensitive" : "case-sensitive";
console.log(`search "${pattern}" in ${filePath} (${sensitivity})`);
},
);Here, exec creates the root command. The subjects list declares two positional arguments (pattern and filePath), and parsed values are exposed as args.pattern and args.filePath.
Add Options
createBooleanOption creates a flag. It is true when the option is present and false otherwise.
createOption parses a single value. With required: true, the handler always receives a value.
createArrayOption parses a list of values and can enforce a minimum, a maximum, or a custom separator.
import { SC } from "@duplojs/server-utils";
import { DP } from "@duplojs/utils";
await SC.exec(
{
options: [
SC.createOption(
"type",
DP.literal(["file", "directory"]),
{
required: true,
aliases: ["t"],
description: "Expected resource type",
},
),
SC.createArrayOption(
"name",
DP.string(),
{
separator: ",",
description: "Accepted resource names",
},
),
],
},
({ options }) => {
console.log(options.type);
console.log(options.name ?? []);
},
);The type of options.type becomes "file" | "directory". If the received value does not match the parser, the command does not call the handler.
Understand Errors
When parsing fails, exec prints an interpreted error and exits with code 1. The error shows the command path, the failing option/argument, the received value, and what was expected.
This is useful for internal tools: you can keep strict contracts without writing diagnostic messages yourself.
Subcommands
For a CLI with several actions, create commands with SC.create, then pass them as subjects of a parent command.
import { SC } from "@duplojs/server-utils";
import { DP } from "@duplojs/utils";
const installCommand = SC.create(
"install",
{
description: "Install a package",
options: [
SC.createBooleanOption(
"yes",
{
aliases: ["y"],
description: "Answer yes to prompts",
},
),
],
subjects: [SC.createArgument("packageName", DP.string())],
},
({ options, args: { packageName } }) => {
console.log(`install ${packageName}${options.yes ? " without prompt" : ""}`);
},
);
await SC.exec(
{
description: "Package manager",
subjects: [installCommand],
},
() => {
console.log("select a package command");
},
);Each subcommand can have its own description, options, argument subjects, and handler. Help follows the command tree.
Generated Help
--help and -h are automatically available. The output is built from declared descriptions, aliases, options, and subjects.
This makes command and option descriptions important: they become the embedded documentation of your tool.
Lightweight Version With execOptions
When your script has no subcommands and no positional argument, execOptions is enough. It only parses process options and returns a typed object.
import { SC } from "@duplojs/server-utils";
import { DP } from "@duplojs/utils";
const options = await SC.execOptions(
SC.createOption(
"port",
DP.coerce.number(),
{
required: true,
description: "HTTP port",
},
),
SC.createBooleanOption(
"reload",
{
aliases: ["r"],
description: "Restart on file change",
},
),
);
if (options.reload) {
console.log(`server listens on ${options.port} with reload`);
}Use execOptions for minimal scripts. Move to exec as soon as you have positional arguments, subcommands, or a real CLI experience to expose.
