Skip to content

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 3000 ou --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

ts
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.

grep-like
$ grep-like "TODO" ./src/index.ts --ignore-case search "TODO" in ./src/index.ts (case-insensitive)

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.

ts
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.

find-like
$ find-like --type socketCommand failedCOMMAND: rootOPTION: --type expected file | directory but received "socket"

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.

ts
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");
}, );
apt-like
$ apt-like install typescript --yes install typescript without prompt

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.

apt-like
$ apt-like --helpCOMMAND: rootDESCRIPTION:Package managerCOMMAND: installDESCRIPTION:Install a packageOPTIONS:- yes: -y, --yesAnswer yes to promptsARGUMENTS: <packageName>- packageName: string

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é.

ts
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`);
}
server-script
$ server-script --port 3000 --reload server listens on 3000 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.

Diffusé sous licence MIT.