Pular para o conteúdo

Contribua com um Gerador

Vamos criar um novo gerador para contribuir com o @aws/nx-plugin. Nosso objetivo será gerar um novo procedimento para uma API tRPC.

Primeiro, vamos clonar o plugin:

Terminal window
git clone git@github.com:awslabs/nx-plugin-for-aws.git

Em seguida, instale e construa:

Terminal window
cd nx-plugin-for-aws
pnpm i
pnpm nx run-many --target build --all

Vamos criar o novo gerador em packages/nx-plugin/src/trpc/procedure.

Fornecemos um gerador para criar novos geradores, permitindo que você scaffold seu novo gerador rapidamente! Você pode executar este gerador da seguinte forma:

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - ts#nx-generator
  5. Preencha os parâmetros obrigatórios
    • pluginProject: @aws/nx-plugin
    • name: ts#trpc-api#procedure
    • directory: trpc/procedure
    • description: Adiciona um procedimento a uma API tRPC
  6. Clique em Generate

Você notará que os seguintes arquivos foram gerados para você:

  • Directorypackages/nx-plugin/src/trpc/procedure
    • schema.json Define a entrada para o gerador
    • schema.d.ts Uma interface TypeScript que corresponde ao schema
    • generator.ts Função que o Nx executa como gerador
    • generator.spec.ts Testes para o gerador
  • Directorydocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentação para o gerador
  • packages/nx-plugin/generators.json Atualizado para incluir o gerador

Vamos atualizar o schema para adicionar as propriedades necessárias para o gerador:

{
"$schema": "https://json-schema.org/schema",
"$id": "tRPCProcedure",
"title": "Adiciona um procedimento a uma API tRPC",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "Projeto da API tRPC",
"x-prompt": "Selecione o projeto da API tRPC para adicionar o procedimento",
"x-dropdown": "projects",
"x-priority": "important"
},
"procedure": {
"description": "Nome do novo procedimento",
"type": "string",
"x-prompt": "Como você gostaria de chamar seu novo procedimento?",
"x-priority": "important",
},
"type": {
"description": "Tipo de procedimento a ser gerado",
"type": "string",
"x-prompt": "Que tipo de procedimento você gostaria de gerar?",
"x-priority": "important",
"default": "query",
"enum": ["query", "mutation"]
}
},
"required": ["project", "procedure"]
}

Você notará que o gerador já foi conectado em packages/nx-plugin/generators.json:

...
"generators": {
...
"ts#trpc-api#procedure": {
"factory": "./src/trpc/procedure/generator",
"schema": "./src/trpc/procedure/schema.json",
"description": "Adiciona um procedimento a uma API tRPC"
}
},
...

Para adicionar um procedimento a uma API tRPC, precisamos fazer duas coisas:

  1. Criar um arquivo TypeScript para o novo procedimento
  2. Adicionar o procedimento ao router

Para criar o arquivo TypeScript do novo procedimento, usaremos um utilitário chamado generateFiles. Com ele, podemos definir um template EJS que será renderizado em nosso gerador com variáveis baseadas nas opções selecionadas pelo usuário.

Primeiro, definiremos o template em 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: definir input
}))
.output(z.object({
// TODO: definir output
}))
.<%- procedureType %>(async ({ input, ctx }) => {
// TODO: implementar!
return {};
});

No template, referenciamos três variáveis:

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Portanto, precisamos garantir que passamos essas variáveis para o generateFiles, bem como o diretório para gerar os arquivos, ou seja, a localização dos arquivos fonte (i.e. sourceRoot) do projeto tRPC selecionado pelo usuário como entrada do gerador, que podemos extrair da configuração do projeto.

Atualizaremos o gerador para fazer isso:

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;

Em seguida, queremos que o gerador conecte o novo procedimento ao router. Isso significa ler e atualizar o código fonte do usuário!

Usamos manipulação de AST do TypeScript para atualizar as partes relevantes do arquivo fonte. Existem alguns helpers chamados replace e destructuredImport para facilitar isso.

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;

Agora que implementamos o gerador, vamos compilá-lo para garantir que esteja disponível para testarmos em nosso projeto dungeon adventure.

Terminal window
pnpm nx run @aws/nx-plugin:compile

Para testar o gerador, vincularemos nosso Nx Plugin for AWS local a uma codebase existente.

Em um diretório separado, crie um novo workspace de teste:

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

Em seguida, vamos gerar uma API tRPC para adicionar o procedimento:

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - ts#trpc-api
  5. Preencha os parâmetros obrigatórios
    • apiName: test-api
  6. Clique em Generate

Em sua codebase, vamos vincular nosso @aws/nx-plugin local:

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

Vamos testar o novo gerador:

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - ts#trpc-api#procedure
  5. Preencha os parâmetros obrigatórios
    • Clique em Generate

    Se bem-sucedido, devemos ter gerado um novo procedimento e adicionado o procedimento ao nosso router em router.ts.

    Se você chegou até aqui e ainda tem tempo para experimentar com geradores Nx, aqui estão algumas sugestões de funcionalidades para adicionar ao gerador de procedimentos:

    Tente atualizar o gerador para suportar routers aninhados:

    • Aceitando notação de ponto para a entrada procedure (ex: games.query)
    • Gerando um procedimento com nome baseado na notação de ponto invertida (ex: queryGames)
    • Adicionando o router aninhado apropriado (ou atualizando-o se já existir!)

    Nosso gerador deve prevenir problemas potenciais, como um usuário selecionar um project que não é uma API tRPC. Consulte o gerador api-connection para um exemplo disso.

    Escreva alguns testes unitários para o gerador. Eles são relativamente simples de implementar e seguem o fluxo geral:

    1. Criar uma workspace tree vazia usando createTreeUsingTsSolutionSetup()
    2. Adicionar quaisquer arquivos que já deveriam existir na tree (ex: project.json e src/router.ts para um backend tRPC)
    3. Executar o gerador em teste
    4. Validar que as alterações esperadas foram feitas na tree

    Atualmente, temos um único “smoke test” que executa todos os geradores e verifica se a build é bem-sucedida. Isso deve ser atualizado para incluir o novo gerador.