Saltearse al contenido

Contribuir con un Generador

Vamos a crear un nuevo generador para contribuir a @aws/nx-plugin. Nuestro objetivo será generar un nuevo procedimiento para una API tRPC.

Primero, clonemos el plugin:

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

Luego instala y construye:

Ventana de terminal
cd nx-plugin-for-aws
pnpm i
pnpm nx run-many --target build --all

Creemos el nuevo generador en packages/nx-plugin/src/trpc/procedure.

¡Proveemos un generador para crear nuevos generadores para que puedas estructurar rápidamente tu nuevo generador! Puedes ejecutar este generador de la siguiente manera:

  1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
  2. Abra la consola Nx en VSCode
  3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
  4. Busque @aws/nx-plugin - ts#nx-generator
  5. Complete los parámetros requeridos
    • pluginProject: @aws/nx-plugin
    • name: ts#trpc-api#procedure
    • directory: trpc/procedure
    • description: Adds a procedure to a tRPC API
  6. Haga clic en Generate

Notarás que se han generado los siguientes archivos:

  • Directorypackages/nx-plugin/src/trpc/procedure
    • schema.json Define las entradas del generador
    • schema.d.ts Interfaz TypeScript que coincide con el schema
    • generator.ts Función que Nx ejecuta como generador
    • generator.spec.ts Pruebas para el generador
  • Directorydocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentación del generador
  • packages/nx-plugin/generators.json Actualizado para incluir el generador

Actualicemos el schema para agregar las propiedades que necesitaremos:

{
"$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"]
}

Notarás que el generador ya ha sido registrado en 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"
}
},
...

Para agregar un procedimiento a una API tRPC, necesitamos hacer dos cosas:

  1. Crear un archivo TypeScript para el nuevo procedimiento
  2. Agregar el procedimiento al router

Para crear el archivo TypeScript del nuevo procedimiento, usaremos una utilidad llamada generateFiles. Con esto, podemos definir una plantilla EJS que renderizaremos en nuestro generador con variables basadas en las opciones seleccionadas por el usuario.

Primero, definiremos la plantilla en 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 {};
});

En la plantilla, hicimos referencia a tres variables:

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Así que necesitamos asegurarnos de pasar esas variables a generateFiles, junto con el directorio donde generar los archivos, específicamente la ubicación de los archivos fuente (es decir, sourceRoot) para el proyecto tRPC seleccionado por el usuario como entrada del generador, que podemos extraer de la configuración del proyecto.

Actualicemos el generador para hacer esto:

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { TrpcProcedureSchema } from './schema';
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(__dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Luego, queremos que el generador conecte el nuevo procedimiento al router. ¡Esto implica leer y actualizar el código fuente del usuario!

Usamos manipulación de AST de TypeScript para actualizar las partes relevantes del archivo fuente. Hay algunos helpers llamados replace y destructuredImport para facilitar esto.

procedure/generator.ts
import {
generateFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { TrpcProcedureSchema } from './schema';
import { formatFilesInSubtree } from '../../utils/format';
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
import { destructuredImport, replace } from '../../utils/ast';
import { factory, ObjectLiteralExpression } from 'typescript';
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(__dirname, 'files'),
projectConfig.sourceRoot,
{
procedureNameCamelCase,
procedureNameKebabCase,
procedureType: options.type,
},
);
const routerPath = joinPathFragments(projectConfig.sourceRoot, 'router.ts');
destructuredImport(
tree,
routerPath,
[procedureNameCamelCase],
`./procedures/${procedureNameKebabCase}.js`,
);
replace(
tree,
routerPath,
'CallExpression[expression.name="router"] > ObjectLiteralExpression',
(node) =>
factory.createObjectLiteralExpression([
...(node as ObjectLiteralExpression).properties,
factory.createShorthandPropertyAssignment(procedureNameCamelCase),
]),
);
await formatFilesInSubtree(tree);
};
export default trpcProcedureGenerator;

Ahora que hemos implementado el generador, compilémoslo para asegurarnos de que esté disponible para probarlo en nuestro proyecto de aventura dungeon.

Ventana de terminal
pnpm nx run @aws/nx-plugin:compile

Para probar el generador, vincularemos nuestro Nx Plugin for AWS local a una base de código existente.

En un directorio separado, crea un nuevo workspace de prueba:

Terminal window
npx create-nx-workspace@~21.0.3 trpc-generator-test --pm=pnpm --preset=@aws/nx-plugin --ci=skip

Luego, generemos una API tRPC para agregar el procedimiento:

  1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
  2. Abra la consola Nx en VSCode
  3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
  4. Busque @aws/nx-plugin - ts#trpc-api
  5. Complete los parámetros requeridos
    • apiName: test-api
  6. Haga clic en Generate

En tu base de código, vinculamos nuestro @aws/nx-plugin local:

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

Probemos el nuevo generador:

  1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
  2. Abra la consola Nx en VSCode
  3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
  4. Busque @aws/nx-plugin - ts#trpc-api#procedure
  5. Complete los parámetros requeridos
    • Haga clic en Generate

    Si tiene éxito, deberíamos haber generado un nuevo procedimiento y agregado el procedimiento a nuestro router en router.ts.

    Si has llegado hasta aquí y aún tienes tiempo para experimentar con generadores Nx, aquí hay algunas sugerencias de características para agregar al generador de procedimientos:

    Intenta actualizar el generador para soportar routers anidados:

    • Aceptando notación de puntos para la entrada procedure (ej. games.query)
    • Generando un procedimiento con nombre basado en la notación de puntos invertida (ej. queryGames)
    • ¡Agregando el router anidado apropiado (o actualizándolo si ya existe)!

    Nuestro generador debería prevenir problemas potenciales, como que un usuario seleccione un project que no sea una API tRPC. Mira el generador api-connection para ver un ejemplo de esto.

    Escribe algunas pruebas unitarias para el generador. Son bastante sencillas de implementar y la mayoría sigue el flujo general:

    1. Crea un workspace tree vacío usando createTreeUsingTsSolutionSetup()
    2. Agrega cualquier archivo que deba existir previamente en el tree (ej. project.json y src/router.ts para un backend tRPC)
    3. Ejecuta el generador bajo prueba
    4. Valida que los cambios esperados se realicen en el tree

    Actualmente tenemos una sola “prueba de humo” que ejecuta todos los generadores y verifica que la construcción tenga éxito. Esto debería actualizarse para incluir el nuevo generador.