Passer au contenu

Nest Commander

En approfondissant la documentation sur les applications autonomes, il existe également le package nest-commander pour écrire des applications en ligne de commande dans une structure similaire à votre application Nest typique.

Installation

Comme pour tout autre package, vous devez l’installer avant de pouvoir l’utiliser.

Fenêtre de terminal
$ npm i nest-commander

Un fichier de commande

nest-commander facilite l’écriture de nouvelles applications en ligne de commande avec des décorateurs via le décorateur @Command() pour les classes et le décorateur @Option() pour les méthodes de cette classe. Chaque fichier de commande doit implémenter la classe abstraite CommandRunner et doit être décoré avec un décorateur @Command().

Chaque commande est considérée comme un @Injectable() par Nest, donc votre injection de dépendance normale fonctionnera comme prévu. La seule chose à noter est la classe abstraite CommandRunner, qui doit être implémentée par chaque commande. La classe abstraite CommandRunner garantit que toutes les commandes ont une méthode run qui renvoie un Promise<void> et prend en paramètres string[], Record<string, any>. La commande run est l’endroit où vous pouvez démarrer toute votre logique, elle prendra tous les paramètres qui ne correspondent pas aux indicateurs d’option et les passera sous forme de tableau, juste au cas où vous voudriez vraiment travailler avec plusieurs paramètres. En ce qui concerne les options, le Record<string, any>, les noms de ces propriétés correspondent à la propriété name donnée aux décorateurs @Option(), tandis que leur valeur correspond à la valeur de retour du gestionnaire d’options. Si vous souhaitez une meilleure sécurité de type, vous pouvez également créer une interface pour vos options.

Exécution de la commande

Comme dans une application NestJS, nous pouvons utiliser NestFactory pour créer un serveur pour nous, et l’exécuter en utilisant listen, le package nest-commander expose une API simple à utiliser pour exécuter votre serveur. Importez CommandFactory et utilisez la méthode statique run, en passant le module racine de votre application. Cela pourrait ressembler à ce qui suit :

Bootstrap de l'application
import { CommandFactory } from 'nest-commander';
import { AppModule } from './app.module';
async function bootstrap() {
await CommandFactory.run(AppModule);
}
bootstrap();

Par défaut, le logger de Nest est désactivé lors de l’utilisation de CommandFactory. Il est cependant possible de le fournir comme deuxième argument à la fonction run. Vous pouvez soit fournir un logger NestJS personnalisé, soit un tableau de niveaux de log que vous souhaitez conserver - il pourrait être utile de fournir au moins ['error'] ici, si vous souhaitez seulement imprimer les logs d’erreur de Nest.

Logging dans la commande
import { CommandFactory } from 'nest-commander';
import { AppModule } from './app.module';
import { LogService } from './log.service';
async function bootstrap() {
await CommandFactory.run(AppModule, new LogService());
// ou, si vous souhaitez uniquement imprimer les avertissements et erreurs de Nest
await CommandFactory.run(AppModule, ['warn', 'error']);
}
bootstrap();

Et voilà. En arrière-plan, CommandFactory s’occupera d’appeler NestFactory pour vous et d’appeler app.close() lorsque cela est nécessaire, donc vous ne devriez pas avoir à vous soucier des fuites de mémoire à ce sujet. Si vous avez besoin d’ajouter un certain traitement des erreurs, il y a toujours un try/catch englobant la commande run, ou vous pouvez enchaîner un peu de méthode .catch() à l’appel de bootstrap().

Tests

Alors, quel est l’intérêt d’écrire un script de ligne de commande super génial si vous ne pouvez pas le tester facilement, n’est-ce pas ? Heureusement, nest-commander dispose de certaines utilitaires que vous pouvez utiliser qui s’intègrent parfaitement dans l’écosystème NestJS, cela paraîtra naturel à tous les Nestlings. Au lieu d’utiliser CommandFactory pour construire la commande en mode test, vous pouvez utiliser CommandTestFactory et passer vos métadonnées, très similaire à la façon dont Test.createTestingModule de @nestjs/testing fonctionne. En fait, il utilise ce package en arrière-plan. Vous êtes également toujours en mesure d’enchaîner les méthodes overrideProvider avant d’appeler compile() afin de pouvoir échanger des pièces de DI directement dans le test.

Tout mettre ensemble

La classe suivante équivaut à avoir une commande CLI qui peut prendre en sous-commande basic ou être appelée directement, avec -n, -s, et -b (avec leurs longs indicateurs) étant tous pris en charge et avec des analyseurs personnalisés pour chaque option. L’indicateur --help est également pris en charge, comme c’est généralement le cas avec commander.

Exemple de commande de base
import { Command, CommandRunner, Option } from 'nest-commander';
import { LogService } from './log.service';
interface BasicCommandOptions {
string?: string;
boolean?: boolean;
number?: number;
}
@Command({
name: 'basic',
description: 'Un analyseur de paramètres',
})
export class BasicCommand extends CommandRunner {
constructor(private readonly logService: LogService) {
super();
}
async run(
passedParam: string[],
options?: BasicCommandOptions,
): Promise<void> {
if (options?.boolean !== undefined && options?.boolean !== null) {
this.runWithBoolean(passedParam, options.boolean);
} else if (options?.number) {
this.runWithNumber(passedParam, options.number);
} else if (options?.string) {
this.runWithString(passedParam, options.string);
} else {
this.runWithNone(passedParam);
}
}
@Option({
flags: '-n, --number [number]',
description: 'Un analyseur de nombres de base',
})
parseNumber(val: string): number {
return Number(val);
}
@Option({
flags: '-s, --string [string]',
description: 'Une chaîne de retour',
})
parseString(val: string): string {
return val;
}
@Option({
flags: '-b, --boolean [boolean]',
description: 'Un analyseur booléen',
})
parseBoolean(val: string): boolean {
return JSON.parse(val);
}
runWithString(param: string[], option: string): void {
this.logService.log({ param, string: option });
}
runWithNumber(param: string[], option: number): void {
this.logService.log({ param, number: option });
}
runWithBoolean(param: string[], option: boolean): void {
this.logService.log({ param, boolean: option });
}
runWithNone(param: string[]): void {
this.logService.log({ param });
}
}

Assurez-vous que la classe de commande est ajoutée à un module.

Ajout du module
import { Module } from '@nestjs/common';
@Module({
providers: [LogService, BasicCommand],
})
export class AppModule {}

Et maintenant, pour pouvoir exécuter le CLI dans votre main.ts, vous pouvez faire comme suit :

Bootstrap de l'application CLI
async function bootstrap() {
await CommandFactory.run(AppModule);
}
bootstrap();

Et voilà, vous avez une application en ligne de commande.

Plus d’informations

Visitez le site de documentation de nest-commander pour plus d’informations, des exemples et la documentation de l’API.