Saltearse al contenido

Contribuir con un Generador

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

Explora el Plugin

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

Crea un Generador Vacío

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 así:

  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 ejecuta Nx 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 necesarias:

{
"$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 está configurado 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"
}
},
...

Implementa el Generador

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

Crea el Nuevo Procedimiento

Para crear el archivo TypeScript, usaremos una utilidad llamada generateFiles. Con esto, definimos una plantilla EJS que podemos renderizar en nuestro generador con variables basadas en las opciones seleccionadas.

Primero, definimos 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 usamos tres variables:

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Debemos asegurarnos de pasarlas a generateFiles, junto con el directorio destino (el sourceRoot del proyecto seleccionado). Actualicemos el generador:

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;

Agrega el Procedimiento al Router

Ahora debemos actualizar el router para incluir el nuevo procedimiento. Usamos manipulación del AST de TypeScript para modificar el código fuente.

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;

Compilemos el generador para probarlo:

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

Probando el Generador

Para probar, vincularemos nuestro plugin local a un proyecto existente.

Crea un Proyecto de Prueba con API tRPC

En otro directorio, crea un workspace:

Terminal window
npx create-nx-workspace@~20.6.3 trpc-generator-test --pm=pnpm --preset=ts --ci=skip --formatter=prettier

Genera una API tRPC:

  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

Vincula el Plugin Local

En tu proyecto, vincula @aws/nx-plugin:

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

Ejecuta el Nuevo Generador

Prueba el 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, se habrá generado un nuevo procedimiento y agregado al router en router.ts.

    Ejercicios

    Si llegaste hasta aquí y quieres experimentar más, aquí algunas sugerencias:

    1. Operaciones Anidadas

    Actualiza el generador para soportar routers anidados:

    • Aceptar notación de puntos en procedure (ej: games.query)
    • Generar procedimientos con nombres basados en la notación invertida (ej: queryGames)
    • Agregar el router anidado correspondiente

    2. Validación

    El generador debe prevenir problemas, como seleccionar un project que no sea una API tRPC. Revisa el generador api-connection como ejemplo.

    3. Pruebas Unitarias

    Escribe pruebas unitarias para el generador. El flujo general es:

    1. Crear un workspace vacío con createTreeUsingTsSolutionSetup()
    2. Agregar archivos existentes necesarios (ej: project.json, src/router.ts)
    3. Ejecutar el generador
    4. Validar los cambios en el árbol

    4. Pruebas End-to-End

    Actualmente tenemos una prueba “smoke test” que verifica que los generadores se construyen. Debes actualizarla para incluir este nuevo generador.