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.
$ 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 :
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.
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.
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.
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 :
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.