Ir al contenido

Contribuir 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

A continuación, instala y compila:

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

Vamos a crear el nuevo generador en packages/nx-plugin/src/trpc/procedure.

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

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
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
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

Notarás que se han generado los siguientes archivos para ti:

  • Directoriopackages/nx-plugin/src/trpc/procedure
    • schema.json Define la entrada para el generador
    • schema.d.ts Una interfaz de typescript que coincide con el esquema
    • generator.ts Función que Nx ejecuta como el generador
    • generator.spec.ts Pruebas para el generador
  • Directoriodocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentación para el generador
  • packages/nx-plugin/generators.json Actualizado para incluir el generador

Actualicemos el esquema para agregar las propiedades que necesitaremos para el generador:

{
"$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 conectado 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 para el nuevo procedimiento, usaremos una utilidad llamada generateFiles. Con esto, podemos definir una plantilla EJS que podemos renderizar 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, referenciamos tres variables:

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Así que necesitaremos asegurarnos de pasar esas a generateFiles, así como el directorio donde generar archivos, es decir, la ubicación de los archivos fuente (i.e. sourceRoot) para el proyecto tRPC que el usuario seleccionó como entrada para el generador, que podemos extraer de la configuración del proyecto.

Actualicemos el generador para hacer eso:

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;

A continuación, queremos que el generador conecte el nuevo procedimiento al router. ¡Esto significa leer y actualizar el código fuente del usuario!

Usamos GritQL para buscar y transformar código fuente de manera declarativa. El helper addDestructuredImport agrega importaciones nombradas, y applyGritQL aplica un patrón GritQL para agregar el procedimiento al objeto literal del router.

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;

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

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

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

Crear un Proyecto de Prueba con una API tRPC

Sección titulada «Crear un Proyecto de Prueba con una API tRPC»

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

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

A continuación, generemos una API tRPC para agregar el procedimiento:

Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive --dry-run

En tu base de código, vinculemos 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:

Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure --dry-run

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 algo de 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 mediante:

  • Aceptar notación de puntos para la entrada procedure (por ejemplo, games.query)
  • Generar un procedimiento con un nombre basado en notación de puntos invertida (por ejemplo, queryGames)
  • Agregar el router anidado apropiado (¡o actualizarlo si ya existe!)

Nuestro generador debería defenderse contra problemas potenciales, como que un usuario seleccione un project que no sea una API tRPC. Echa un vistazo al generador connection para ver un ejemplo de esto.

Escribe algunas pruebas unitarias para el generador. Estas son bastante sencillas de implementar, y la mayoría siguen el flujo general:

  1. Crear un árbol de workspace vacío usando createTreeUsingTsSolutionSetup()
  2. Agregar cualquier archivo que ya debería existir en el árbol (por ejemplo, project.json y src/router.ts para un backend tRPC)
  3. Ejecutar el generador bajo prueba
  4. Validar que se realicen los cambios esperados en el árbol

Tenemos un conjunto de “smoke tests” que ejecutan generadores en un workspace nuevo y se aseguran de que todo compile. Como mínimo, tu nuevo generador debería agregarse a ambas matrices de generadores para que sea ejercitado por los smoke tests:

  • e2e/src/smoke-tests/generator-matrix.ts — ejecuta cada generador a través del CLI, una invocación a la vez, exactamente como lo haría un usuario.
  • packages/nx-plugin/src/internal/test-matrix/generator.ts — un generador oculto que compone todos los demás para probar migraciones entre versiones.

Ten en cuenta que la matriz de generadores solo ejecuta los generadores y compila el workspace; no instancia ninguna infraestructura. Para generadores que despliegan infraestructura, considera extender las pruebas e2e de despliegue (e2e/src/smoke-tests/cdk-deploy.spec.ts y terraform-deploy.spec.ts) para realmente desplegar tus recursos (a través de cdk deploy / terraform apply), luego agrega una aserción a deploy-invocations.ts que invoque el recurso desplegado y verifique que se comporte como se espera.

Si tu generador proporciona un servidor de desarrollo local, considera agregarlo a la prueba e2e de desarrollo local (e2e/src/smoke-tests/local-dev.spec.ts), que inicia el target dev y ejercita el servidor en ejecución.

Orientación General para Acelerar la Contribución

Sección titulada «Orientación General para Acelerar la Contribución»

Esta sección contiene orientación general que puede ayudar al trabajar en el Nx Plugin for AWS.

Trabajar hacia atrás desde un proyecto real

Sección titulada «Trabajar hacia atrás desde un proyecto real»

Una forma útil de construir nuevos generadores o agregar características/correcciones a un generador existente es construirlo primero de verdad. De esta manera puedes validar tus ideas e iterar rápidamente para lograr la funcionalidad que necesitas. Después de que hayas decidido el resultado deseado, puedes actualizar el generador.

En la práctica, este proceso podría verse así:

  1. Crear un nuevo workspace

    Terminal window
    pnpm create @aws/nx-workspace my-project
  2. Ejecutar cualquier generador que pueda ser un requisito previo para tu nuevo generador/característica/corrección

  3. Confirmar tus cambios (git commit)

  4. Hacer los cambios deseados y probarlos según sea necesario

  5. Usar el git diff de tus cambios para informar qué cambios deben hacerse al Nx Plugin for AWS

  6. Realizar una prueba final de extremo a extremo (vinculando tu @aws/nx-plugin) para asegurar que tu generador proporcione los cambios que necesitas