Salta ai contenuti

Contribuire con un Generator

Creiamo un nuovo generator da contribuire a @aws/nx-plugin. Il nostro obiettivo sarà generare una nuova procedura per un’API tRPC.

Prima di tutto, cloniamo il plugin:

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

Successivamente, installiamo e compiliamo:

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

Creiamo il nuovo generator in packages/nx-plugin/src/trpc/procedure.

Forniamo un generator per creare nuovi generator in modo da poter scaffoldare rapidamente il tuo nuovo generator! Puoi eseguire questo generator come segue:

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --pluginProject=@aws/nx-plugin --name=ts#trpc-api#procedure --directory=trpc/procedure --description=Adds a procedure to a tRPC API
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --pluginProject=@aws/nx-plugin --name=ts#trpc-api#procedure --directory=trpc/procedure --description=Adds a procedure to a tRPC API --dry-run

Noterai che i seguenti file sono stati generati per te:

  • Directorypackages/nx-plugin/src/trpc/procedure
    • schema.json Definisce l’input per il generator
    • schema.d.ts Un’interfaccia typescript che corrisponde allo schema
    • generator.ts Funzione che Nx esegue come generator
    • generator.spec.ts Test per il generator
  • Directorydocs/src/content/docs/guides/
    • trpc-procedure.mdx Documentazione per il generator
  • packages/nx-plugin/generators.json Aggiornato per includere il generator

Aggiorniamo lo schema per aggiungere le proprietà di cui avremo bisogno per il generator:

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

Noterai che il generator è già stato collegato in 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"
}
},
...

Per aggiungere una procedura a un’API tRPC, dobbiamo fare due cose:

  1. Creare un file TypeScript per la nuova procedura
  2. Aggiungere la procedura al router

Per creare il file TypeScript per la nuova procedura, useremo un’utility chiamata generateFiles. Utilizzandola, possiamo definire un template EJS che possiamo renderizzare nel nostro generator con variabili basate sulle opzioni selezionate dall’utente.

Prima di tutto, definiremo il template in 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 {};
});

Nel template, abbiamo referenziato tre variabili:

  • procedureNameCamelCase
  • procedureNameKebabCase
  • procedureType

Quindi dovremo assicurarci di passarle a generateFiles, insieme alla directory in cui generare i file, ovvero la posizione dei file sorgente (cioè sourceRoot) per il progetto tRPC che l’utente ha selezionato come input per il generator, che possiamo estrarre dalla configurazione del progetto.

Aggiorniamo il generator per farlo:

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;

Successivamente, vogliamo che il generator colleghi la nuova procedura al router. Questo significa leggere e aggiornare il codice sorgente dell’utente!

Usiamo GritQL per cercare e trasformare il codice sorgente in modo dichiarativo. L’helper addDestructuredImport aggiunge import nominati, e applyGritQL applica un pattern GritQL per aggiungere la procedura all’oggetto letterale 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;

Ora che abbiamo implementato il generator, compiliamolo per assicurarci che sia disponibile per testarlo nel nostro progetto dungeon adventure.

Terminal window
pnpm nx compile @aws/nx-plugin

Per testare il generator, collegheremo il nostro Nx Plugin for AWS locale a una codebase esistente.

In una directory separata, crea un nuovo workspace di test:

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

Successivamente, generiamo un’API tRPC a cui aggiungere la procedura:

Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @aws/nx-plugin:ts#api --name=test-api --framework=trpc --no-interactive --dry-run

Nella tua codebase, colleghiamo il nostro @aws/nx-plugin locale:

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

Proviamo il nuovo generator:

Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @aws/nx-plugin:ts#trpc-api#procedure --dry-run

Se ha successo, dovremmo aver generato una nuova procedura e aggiunto la procedura al nostro router in router.ts.

Se sei arrivato fin qui e hai ancora del tempo per sperimentare con i generator Nx, ecco alcuni suggerimenti di funzionalità da aggiungere al generator di procedure:

Prova ad aggiornare il generator per supportare router annidati:

  • Accettando la notazione a punti per l’input procedure (ad es. games.query)
  • Generando una procedura con un nome basato sulla notazione a punti invertita (ad es. queryGames)
  • Aggiungendo il router annidato appropriato (o aggiornandolo se esiste già!)

Il nostro generator dovrebbe difendersi da potenziali problemi, come un utente che seleziona un project che non è un’API tRPC. Dai un’occhiata al generator connection per un esempio di questo.

Scrivi alcuni test unitari per il generator. Questi sono abbastanza semplici da implementare e la maggior parte segue il flusso generale:

  1. Creare un albero di workspace vuoto usando createTreeUsingTsSolutionSetup()
  2. Aggiungere eventuali file che dovrebbero già esistere nell’albero (ad es. project.json e src/router.ts per un backend tRPC)
  3. Eseguire il generator in fase di test
  4. Validare che le modifiche previste siano apportate all’albero

Abbiamo una suite di “smoke test” che eseguono i generator in un workspace nuovo e si assicurano che tutto venga compilato. Come minimo, il tuo nuovo generator dovrebbe essere aggiunto a entrambe le matrici di generator in modo che venga esercitato dagli smoke test:

  • e2e/src/smoke-tests/generator-matrix.ts — esegue ogni generator tramite la CLI, un’invocazione alla volta, esattamente come farebbe un utente.
  • packages/nx-plugin/src/internal/test-matrix/generator.ts — un generator nascosto che compone tutti gli altri per testare le migrazioni tra versioni.

Nota che la matrice di generator esegue solo i generator e compila il workspace — non istanzia alcuna infrastruttura. Per i generator che distribuiscono infrastruttura, considera di estendere i test e2e di deployment (e2e/src/smoke-tests/cdk-deploy.spec.ts e terraform-deploy.spec.ts) per distribuire effettivamente le tue risorse (tramite cdk deploy / terraform apply), quindi aggiungi un’asserzione a deploy-invocations.ts che invoca la risorsa distribuita e verifica che si comporti come previsto.

Se il tuo generator fornisce un server di sviluppo locale, considera di aggiungerlo al test e2e di sviluppo locale (e2e/src/smoke-tests/local-dev.spec.ts), che avvia il target dev ed esercita il server in esecuzione.

Questa sezione contiene alcune linee guida generali che possono aiutare quando si lavora sul Nx Plugin for AWS.

Un modo utile per costruire nuovi generator o aggiungere funzionalità/correzioni a un generator esistente è costruirlo prima per davvero. In questo modo puoi validare le tue idee e iterare rapidamente per ottenere la funzionalità di cui hai bisogno. Dopo aver stabilito il risultato desiderato, puoi quindi aggiornare il generator.

In pratica, questo processo potrebbe apparire così:

  1. Creare un nuovo workspace

    Terminal window
    pnpm create @aws/nx-workspace my-project
  2. Eseguire eventuali generator che potrebbero essere prerequisiti per il tuo nuovo generator/funzionalità/correzione

  3. Committare le tue modifiche (git commit)

  4. Apportare le modifiche desiderate e testarle secondo necessità

  5. Usare il git diff delle tue modifiche per informare quali modifiche dovrebbero essere apportate al Nx Plugin for AWS

  6. Eseguire un test end to end finale (collegando il tuo @aws/nx-plugin) per assicurarti che il tuo generator fornisca le modifiche di cui hai bisogno