Aller au contenu

Contribuer un générateur

Créons un nouveau générateur pour contribuer à @aws/nx-plugin. Notre objectif sera de générer une nouvelle procédure pour une API tRPC.

Tout d’abord, clonons le plugin :

Fenêtre de terminal
git clone git@github.com:awslabs/nx-plugin-for-aws.git

Ensuite, installons et construisons :

Fenêtre de terminal
cd nx-plugin-for-aws
pnpm i
pnpm nx run-many --target build --all

Créons le nouveau générateur dans packages/nx-plugin/src/trpc/procedure.

Nous fournissons un générateur pour créer de nouveaux générateurs afin que vous puissiez rapidement échafauder votre nouveau générateur ! Vous pouvez exécuter ce générateur comme suit :

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --project=@aws/nx-plugin --name=ts#trpc-api#procedure --directory=trpc/procedure --description=Adds a procedure to a tRPC API
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --project=@aws/nx-plugin --name=ts#trpc-api#procedure --directory=trpc/procedure --description=Adds a procedure to a tRPC API --dry-run

Vous remarquerez que les fichiers suivants ont été générés pour vous :

  • Répertoirepackages/nx-plugin/src/trpc/procedure
    • schema.json Définit l’entrée pour le générateur
    • schema.d.ts Une interface TypeScript qui correspond au schéma
    • generator.ts Fonction que Nx exécute en tant que générateur
    • generator.spec.ts Tests pour le générateur
  • Répertoiredocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentation pour le générateur
  • packages/nx-plugin/generators.json Mis à jour pour inclure le générateur

Mettons à jour le schéma pour ajouter les propriétés dont nous aurons besoin pour le générateur :

{
"$schema": "https://json-schema.org/schema",
"$id": "tRPCProcedure",
"title": "Adds a procedure to a tRPC API",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "tRPC API project",
"x-prompt": "Select the tRPC API project to add the procedure to",
"x-dropdown": "projects",
"x-priority": "important"
},
"procedure": {
"description": "The name of the new procedure",
"type": "string",
"x-prompt": "What would you like to call your new procedure?",
"x-priority": "important"
},
"type": {
"description": "The type of procedure to generate",
"type": "string",
"x-prompt": "What type of procedure would you like to generate?",
"x-priority": "important",
"default": "query",
"enum": ["query", "mutation"]
}
},
"required": ["project", "procedure"]
}

Vous remarquerez que le générateur a déjà été connecté dans packages/nx-plugin/generators.json :

...
"generators": {
...
"ts#trpc-api#procedure": {
"factory": "./src/trpc/procedure/generator",
"schema": "./src/trpc/procedure/schema.json",
"description": "Adds a procedure to a tRPC API"
}
},
...

Pour ajouter une procédure à une API tRPC, nous devons faire deux choses :

  1. Créer un fichier TypeScript pour la nouvelle procédure
  2. Ajouter la procédure au routeur

Pour créer le fichier TypeScript pour la nouvelle procédure, nous utiliserons un utilitaire appelé generateFiles. En l’utilisant, nous pouvons définir un modèle EJS que nous pouvons rendre dans notre générateur avec des variables basées sur les options sélectionnées par l’utilisateur.

Tout d’abord, nous définirons le modèle dans packages/nx-plugin/src/trpc/procedure/files/procedures/__procedureNameKebabCase__.ts.template :

files/procedures/__procedureNameKebabCase__.ts.template
import { publicProcedure } from '../init.js';
import { z } from 'zod';
export const <%- procedureNameCamelCase %> = publicProcedure
.input(z.object({
// TODO: define input
}))
.output(z.object({
// TODO: define output
}))
.<%- procedureType %>(async ({ input, ctx }) => {
// TODO: implement!
return {};
});

Dans le modèle, nous avons référencé trois variables :

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Nous devrons donc nous assurer de les transmettre à generateFiles, ainsi que le répertoire dans lequel générer les fichiers, à savoir l’emplacement des fichiers sources (c’est-à-dire sourceRoot) pour le projet tRPC que l’utilisateur a sélectionné comme entrée pour le générateur, que nous pouvons extraire de la configuration du projet.

Mettons à jour le générateur pour faire cela :

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
type Tree,
} from '@nx/devkit';
import type { TrpcProcedureSchema } from './schema.js';
import { formatFilesInSubtree } from '../../utils/format';
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
export const trpcProcedureGenerator = async (
tree: Tree,
options: TrpcProcedureSchema,
) => {
const projectConfig = readProjectConfiguration(tree, options.project);
const procedureNameCamelCase = camelCase(options.procedure);
const procedureNameKebabCase = kebabCase(options.procedure);
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Ensuite, nous voulons que le générateur connecte la nouvelle procédure au routeur. Cela signifie lire et mettre à jour le code source de l’utilisateur !

Nous utilisons GritQL pour rechercher et transformer de manière déclarative le code source. L’assistant addDestructuredImport ajoute des imports nommés, et applyGritQL applique un motif GritQL pour ajouter la procédure au littéral d’objet du routeur.

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
type Tree,
} from '@nx/devkit';
import type { TrpcProcedureSchema } from './schema.js';
import { formatFilesInSubtree } from '../../utils/format';
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
import { addDestructuredImport, applyGritQL } from '../../utils/ast';
export const trpcProcedureGenerator = async (
tree: Tree,
options: TrpcProcedureSchema,
) => {
const projectConfig = readProjectConfiguration(tree, options.project);
const procedureNameCamelCase = camelCase(options.procedure);
const procedureNameKebabCase = kebabCase(options.procedure);
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
const routerPath = joinPathFragments(projectConfig.sourceRoot, 'router.ts');
await addDestructuredImport(
tree,
routerPath,
[procedureNameCamelCase],
`./procedures/${procedureNameKebabCase}.js`,
);
await applyGritQL(
tree,
routerPath,
`\`router({ $props })\` => \`router({ $props, ${procedureNameCamelCase} })\` where { $props <: not contains \`${procedureNameCamelCase}\` }`,
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Maintenant que nous avons implémenté le générateur, compilons-le pour nous assurer qu’il est disponible pour que nous puissions le tester dans notre projet d’aventure de donjon.

Fenêtre de terminal
pnpm nx compile @aws/nx-plugin

Pour tester le générateur, nous allons lier notre Nx Plugin for AWS local à une base de code existante.

Dans un répertoire séparé, créez un nouvel espace de travail de test :

Terminal window
pnpm create @aws/nx-workspace trpc-generator-test

Ensuite, générons une API tRPC à laquelle ajouter la procédure :

Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive --dry-run

Dans votre base de code, lions notre @aws/nx-plugin local :

Terminal window
cd path/to/trpc-generator-test
pnpm link path/to/nx-plugin-for-aws/dist/packages/nx-plugin

Essayons le nouveau générateur :

Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure --dry-run

En cas de succès, nous devrions avoir généré une nouvelle procédure et ajouté la procédure à notre routeur dans router.ts.

Si vous êtes arrivé jusqu’ici et que vous avez encore du temps pour expérimenter avec les générateurs Nx, voici quelques suggestions de fonctionnalités à ajouter au générateur de procédures :

Essayez de mettre à jour le générateur pour prendre en charge les routeurs imbriqués en :

  • Acceptant la notation par points pour l’entrée procedure (par exemple games.query)
  • Générant une procédure avec un nom basé sur la notation par points inversée (par exemple queryGames)
  • Ajoutant le routeur imbriqué approprié (ou en le mettant à jour s’il existe déjà !)

Notre générateur devrait se défendre contre les problèmes potentiels, comme un utilisateur sélectionnant un project qui n’est pas une API tRPC. Jetez un œil au générateur connection pour un exemple de cela.

Écrivez quelques tests unitaires pour le générateur. Ceux-ci sont assez simples à implémenter, et la plupart suivent le flux général :

  1. Créer un arbre d’espace de travail vide en utilisant createTreeUsingTsSolutionSetup()
  2. Ajouter tous les fichiers qui devraient déjà exister dans l’arbre (par exemple project.json et src/router.ts pour un backend tRPC)
  3. Exécuter le générateur testé
  4. Valider que les modifications attendues sont apportées à l’arbre

Nous avons une suite de “smoke tests” qui exécutent des générateurs dans un espace de travail frais et s’assurent que tout se construit. Au minimum, votre nouveau générateur devrait être ajouté aux deux matrices de générateurs afin qu’il soit exercé par les smoke tests :

  • e2e/src/smoke-tests/generator-matrix.ts — exécute chaque générateur via la CLI, une invocation à la fois, exactement comme le ferait un utilisateur.
  • packages/nx-plugin/src/internal/test-matrix/generator.ts — un générateur caché qui compose tous les autres pour tester les migrations entre les versions.

Notez que la matrice de générateurs exécute uniquement les générateurs et construit l’espace de travail — elle n’instancie aucune infrastructure. Pour les générateurs qui déploient de l’infrastructure, envisagez d’étendre les tests e2e de déploiement (e2e/src/smoke-tests/cdk-deploy.spec.ts et terraform-deploy.spec.ts) pour déployer réellement vos ressources (via cdk deploy / terraform apply), puis ajoutez une assertion à deploy-invocations.ts qui invoque la ressource déployée et vérifie qu’elle se comporte comme prévu.

Si votre générateur fournit un serveur de développement local, envisagez de l’ajouter au test e2e de développement local (e2e/src/smoke-tests/local-dev.spec.ts), qui démarre la cible dev et exerce le serveur en cours d’exécution.

Conseils généraux pour accélérer la contribution

Section intitulée « Conseils généraux pour accélérer la contribution »

Cette section contient quelques conseils généraux qui peuvent aider lors du travail sur le Nx Plugin for AWS.

Travailler à rebours à partir d’un projet réel

Section intitulée « Travailler à rebours à partir d’un projet réel »

Une façon utile de créer de nouveaux générateurs ou d’ajouter des fonctionnalités/corrections à un générateur existant est de le construire d’abord pour de vrai. De cette façon, vous pouvez valider vos idées et itérer rapidement pour obtenir la fonctionnalité dont vous avez besoin. Après avoir établi le résultat souhaité, vous pouvez ensuite mettre à jour le générateur.

En pratique, ce processus pourrait ressembler à :

  1. Créer un nouvel espace de travail

    Terminal window
    pnpm create @aws/nx-workspace my-project
  2. Exécuter tous les générateurs qui peuvent être des prérequis pour votre nouveau générateur/fonctionnalité/correction

  3. Valider vos modifications (git commit)

  4. Apporter vos modifications souhaitées et les tester selon les besoins

  5. Utiliser le git diff de vos modifications pour informer les modifications qui devraient être apportées au Nx Plugin for AWS

  6. Effectuer un dernier test de bout en bout (en liant votre @aws/nx-plugin) pour vous assurer que votre générateur fournit les modifications dont vous avez besoin