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.

Verifique o Plugin

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

Crie um Gerador Vazio

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"
}
},
...

Implemente o Gerador

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

Crie o Novo Procedimento

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;

Adicione o Procedimento ao Router

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

Testando o Gerador

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

Crie um Projeto de Teste com uma API tRPC

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

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

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

Vincule nosso Nx Plugin for AWS local

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

Execute o Novo Gerador

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.

    Exercícios

    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:

    1. Operações Aninhadas

    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!)

    2. Validação

    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.

    3. Testes Unitários

    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

    4. Testes End to End

    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.