Créer une commande
La feature command sert à écrire des CLI qui restent simples à appeler et solides à maintenir. Vous décrivez ce que la commande accepte, la librairie lit les arguments du process, valide les entrées, génère le help et appelle votre handler avec des valeurs déjà typées.
Principe
Une commande est composée de trois éléments:
- des options, comme
--port 3000ou--verbose; - des sujets, c'est-à-dire soit des arguments positionnels après les options, soit des sous-commandes;
- un handler, appelé uniquement quand le parsing est valide.
Les options et les arguments s'appuient sur les DataParser de @duplojs/utils. C'est ce qui permet de transformer une entrée CLI, toujours textuelle au départ, en valeur métier typée.
Une première commande
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})`);
},
);Ici, exec crée la commande racine. La liste subjects déclare deux arguments positionnels (pattern et filePath), puis les valeurs parsées sont exposées dans args.pattern et args.filePath.
Ajouter des options
createBooleanOption crée un drapeau. Il vaut true si l'option est présente, false sinon.
createOption parse une valeur unique. Avec required: true, le handler reçoit toujours une valeur.
createArrayOption parse une liste de valeurs et peut imposer un minimum, un maximum ou un séparateur.
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 ?? []);
},
);Le type de options.type devient "file" | "directory". Si la valeur reçue ne correspond pas au parser, la commande n'appelle pas le handler.
Comprendre les erreurs
Quand le parsing échoue, exec affiche une erreur interprétée puis sort avec le code 1. L'erreur indique le chemin de commande, l'option/l'argument fautif, la valeur reçue et ce qui était attendu.
Ce comportement est utile pour les outils internes: vous pouvez garder des contrats stricts sans écrire vous-même les messages de diagnostic.
Sous-commandes
Pour une CLI avec plusieurs actions, créez des commandes avec SC.create, puis passez-les comme subjects d'une commande parente.
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");
},
);Chaque sous-commande peut avoir sa propre description, ses options, ses arguments de sujet et son handler. Le help suit l'arbre de commandes.
Help généré
--help et -h sont automatiquement disponibles. Le rendu est construit depuis les descriptions, les alias, les options et les sujets déclarés.
Il est donc important de renseigner les descriptions des commandes et des options: elles deviennent la documentation embarquée de votre outil.
Version légère avec execOptions
Quand vous écrivez un script qui n'a pas de sous-commandes ni d'argument positionnel, execOptions suffit. Il parse seulement les options du process et retourne un objet typé.
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`);
}Utilisez execOptions pour les scripts minimaux. Passez à exec dès que vous avez des arguments positionnels, des sous-commandes ou une vraie expérience CLI à exposer.
