Gioco di Dungeon con IA
Modulo 1: Configurazione del monorepo
Sezione intitolata “Modulo 1: Configurazione del monorepo”Iniziamo creando un nuovo monorepo. Dal tuo percorso desiderato, esegui il seguente comando:
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --ci=skip
Questo configurerà un monorepo NX nella directory dungeon-adventure
che potrai aprire in VSCode. Dovrebbe apparire come segue:
Directory.nx/
- …
Directory.vscode/
- …
Directorynode_modules/
- …
Directorypackages/ qui risiederanno i tuoi sotto-progetti
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json configura le impostazioni predefinite della CLI NX e del monorepo
- package.json tutte le dipendenze Node sono definite qui
- pnpm-lock.yaml o bun.lock, yarn.lock, package-lock.json a seconda del gestore pacchetti
- pnpm-workspace.yaml se si utilizza pnpm
- README.md
- tsconfig.base.json tutti i sotto-progetti Node estendono questo file
- tsconfig.json
Ora siamo pronti per iniziare a creare i diversi sotto-progetti utilizzando @aws/nx-plugin
.
Game API
Sezione intitolata “Game API”Iniziamo creando la nostra Game API. Per farlo, creiamo un’API tRPC chiamata GameApi
seguendo questi passaggi:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#trpc-api
- Compila i parametri richiesti
- name: GameApi
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire nel tuo albero delle directory.
File aggiornati da ts#trpc-api
Di seguito è riportato l’elenco di tutti i file generati dal generatore ts#trpc-api
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici dell’applicazione
Directoryapis/
- game-api.ts costrutto CDK per creare l’API tRPC
- index.ts
- …
- index.ts
Directorycore/ costrutti CDK generici
Directoryapi/
- rest-api.ts costrutto base CDK per un’API Gateway Rest
- trpc-utils.ts utility per i costrutti CDK delle API tRPC
- utils.ts utility per i costrutti delle API
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Directorytypes/ tipi condivisi
Directorysrc/
- index.ts
- runtime-config.ts definizione dell’interfaccia usata sia da CDK che dal sito web
- project.json
- …
Directorygame-api/ API tRPC
Directorysrc/
Directoryclient/ client vanilla tipicamente usato per chiamate machine-to-machine in TS
- index.ts
- sigv4.ts
Directorymiddleware/ strumentazione Powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Directoryschema/ definizioni di input e output per la tua API
- echo.ts
Directoryprocedures/ implementazioni specifiche per le procedure/route della tua API
- echo.ts
- index.ts
- init.ts configura il contesto e il middleware
- local-server.ts usato per eseguire il server tRPC localmente
- router.ts punto di ingresso per il lambda handler che definisce tutte le procedure
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
Esaminando alcuni dei file chiave:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;
Il router definisce il punto di ingresso per la tua API tRPC ed è il luogo dove dichiarerai tutti i metodi della tua API. Come puoi vedere sopra, abbiamo un metodo chiamato echo
con la sua implementazione nel file ./procedures/echo.ts
.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Questo file contiene l’implementazione del metodo echo
ed è fortemente tipizzato dichiarando le sue strutture dati di input e output.
import { z } from 'zod/v4';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
Tutte le definizioni degli schema tRPC sono definite utilizzando Zod ed esportate come tipi TypeScript tramite la sintassi z.TypeOf
.
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from ':dungeon-adventure/game-api';
// Tipo union per tutti i nomi delle operazioni APItype Operations = Procedures<AppRouter>;
/** * Proprietà per la creazione di un costrutto GameApi * * @template TIntegrations - Mappa dei nomi delle operazioni alle loro integrazioni */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * Mappa dei nomi delle operazioni alle loro integrazioni API Gateway */ integrations: TIntegrations;}
/** * Un costrutto CDK che crea e configura un'API REST AWS API Gateway * specificamente per GameApi. * @template TIntegrations - Mappa dei nomi delle operazioni alle loro integrazioni */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * Crea integrazioni predefinite per tutte le operazioni, implementando ciascuna operazione come * una singola funzione lambda. * * @param scope - Il contesto del costrutto CDK * @returns Un IntegrationBuilder con integrazioni lambda predefinite */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: routerToOperations(appRouter), defaultIntegrationOptions: { runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // Qui concediamo a qualsiasi credenziale AWS dell'account in cui il progetto è deployato di chiamare l'API. // L'accesso granulare machine-to-machine può essere definito qui usando principal più specifici (es. ruoli o // utenti) e risorse (es. quali percorsi API possono essere invocati da quale principal) se necessario. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // Apriamo OPTIONS per permettere ai browser di fare richieste preflight non autenticate new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
Questo è il costrutto CDK che definisce la nostra GameApi. Come puoi vedere, fornisce un metodo defaultIntegrations
che crea automaticamente una funzione lambda per ogni procedura nella nostra API tRPC, puntando all’implementazione dell’API già bundled. Questo significa che al momento del cdk synth
non avviene il bundling (a differenza di NodeJsFunction) poiché lo abbiamo già incluso come parte del target di build del progetto backend.
Story API
Sezione intitolata “Story API”Ora creiamo la nostra Story API. Per farlo, creiamo un’API Fast chiamata StoryApi
seguendo questi passaggi:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - py#fast-api
- Compila i parametri richiesti
- name: StoryApi
- moduleName: story_api
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire nel tuo albero delle directory.
File aggiornati da py#fast-api
Di seguito è riportato l’elenco di tutti i file generati dal generatore py#fast-api
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directory.venv/ singolo virtual env per il monorepo
- …
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici dell’applicazione
Directoryapis/
- story-api.ts costrutto CDK per creare la tua Fast API
- index.ts aggiornato per esportare la nuova story-api
- project.json aggiornato per aggiungere una dipendenza di build su story_api
Directorytypes/ tipi condivisi
Directorysrc/
- runtime-config.ts aggiornato per aggiungere la StoryApi
Directorystory_api/
Directorystory_api/ modulo Python
- init.py configura Powertools, FastAPI e middleware
- main.py punto di ingresso per il lambda contenente tutte le route
Directorytests/
- …
- .python-version
- project.json
- pyproject.toml
- project.json
- .python-version versione Python bloccata per uv
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { OPERATION_DETAILS, Operations,} from '../../generated/story-api/metadata.gen.js';
/** * Proprietà per la creazione di un costrutto StoryApi * * @template TIntegrations - Mappa dei nomi delle operazioni alle loro integrazioni */export interface StoryApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * Mappa dei nomi delle operazioni alle loro integrazioni API Gateway */ integrations: TIntegrations;}
/** * Un costrutto CDK che crea e configura un'API REST AWS API Gateway * specificamente per StoryApi. * @template TIntegrations - Mappa dei nomi delle operazioni alle loro integrazioni */export class StoryApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * Crea integrazioni predefinite per tutte le operazioni, implementando ciascuna operazione come * una singola funzione lambda. * * @param scope - Il contesto del costrutto CDK * @returns Un IntegrationBuilder con integrazioni lambda predefinite */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: OPERATION_DETAILS, defaultIntegrationOptions: { runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `StoryApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: StoryApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'StoryApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // Qui concediamo a qualsiasi credenziale AWS dell'account in cui il progetto è deployato di chiamare l'API. // L'accesso granulare machine-to-machine può essere definito qui usando principal più specifici (es. ruoli o // utenti) e risorse (es. quali percorsi API possono essere invocati da quale principal) se necessario. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // Apriamo OPTIONS per permettere ai browser di fare richieste preflight non autenticate new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: OPERATION_DETAILS, ...props, }); }}
Questo è il costrutto CDK che definisce la nostra StoryApi. Come puoi vedere, fornisce un metodo defaultIntegrations
che crea automaticamente una funzione lambda per ogni operazione definita nella nostra FastAPI, puntando all’implementazione dell’API già bundled. Questo significa che al momento del cdk synth
non avviene il bundling (a differenza di PythonFunction) poiché lo abbiamo già incluso come parte del target di build del progetto backend.
export type ApiUrl = string;// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interfaceexport interface IRuntimeConfig { apis: { GameApi: ApiUrl; StoryApi: ApiUrl; };}
Ecco un esempio di come il generatore esegue una trasformazione AST che preserva tutto il codice esistente e aggiorna il file. Qui puoi vedere che StoryApi
è stato aggiunto alla definizione di IRuntimeConfig
, il che significa che quando verrà consumato dal frontend, garantirà la type safety!
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
Questo è dove verranno definiti tutti i metodi della tua API. Come puoi vedere qui, abbiamo un metodo read_root
mappato alla route GET /
. Puoi usare Pydantic per dichiarare gli input e gli output dei tuoi metodi per garantire la type safety.
Game UI: Website
Sezione intitolata “Game UI: Website”Ora creiamo l’interfaccia utente che permetterà di interagire con il gioco. Per farlo, creiamo un sito web chiamato GameUI
seguendo questi passaggi:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#react-website
- Compila i parametri richiesti
- name: GameUI
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire nel tuo albero delle directory.
File aggiornati da ts#react-website
Di seguito è riportato l’elenco di tutti i file generati dal generatore ts#react-website
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici dell’applicazione
Directorystatic-websites/
- game-ui.ts costrutto CDK per creare la tua Game UI
Directorycore/
- static-website.ts costrutto generico per siti web statici
Directorygame-ui/
Directorypublic/
- …
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.ts layout generale della pagina: header, footer, sidebar, ecc.
- navitems.ts elementi di navigazione della sidebar
Directoryhooks/
- useAppLayout.tsx permette di impostare dinamicamente elementi come notifiche, stile della pagina, ecc.
Directoryroutes/ route basate su file di @tanstack/react-router
- index.tsx pagina root ’/’ reindirizza a ‘/welcome’
- __root.tsx tutte le pagine usano questo componente come base
Directorywelcome/
- index.tsx
- config.ts
- main.tsx punto di ingresso React
- routeTree.gen.ts questo file viene aggiornato automaticamente da @tanstack/react-router
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
Questo è il costrutto CDK che definisce la nostra GameUI. Come puoi vedere, ha già configurato il percorso del file per il bundle generato dalla UI basata su Vite. Questo significa che al momento del build
, il bundling avviene all’interno del target di build del progetto game-ui e il suo output viene utilizzato qui.
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
// Registra l'istanza del router per la type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
Questo è il punto di ingresso dove viene montato React. Come mostrato, inizialmente configura solo un @tanstack/react-router
in una configurazione file-based-routing
. Questo significa che, finché il server di sviluppo è in esecuzione, puoi semplicemente creare file nella cartella routes
e @tanstack/react-router
creerà automaticamente il setup boilerplate per te, aggiornando il file routeTree.gen.ts
. Questo file mantiene tutte le route in modo type-safe, il che significa che quando usi <Link>
, l’opzione to
mostrerà solo route valide. Per maggiori informazioni, consulta la documentazione di @tanstack/react-router
.
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>Welcome to your new Cloudscape website!</Container> </SpaceBetween> </ContentLayout> );}
Un componente che verrà renderizzato quando si naviga alla route /welcome
. @tanstack/react-router
gestirà la Route
per te ogni volta che crei/sposti questo file (finché il dev server è in esecuzione). Questo verrà mostrato in una sezione successiva di questo tutorial.
Game UI: Auth
Sezione intitolata “Game UI: Auth”Ora configuriamo la nostra Game UI per richiedere l’accesso autenticato tramite Amazon Cognito seguendo questi passaggi:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#react-website#auth
- Compila i parametri richiesti
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire/cambiare nel tuo albero delle directory.
File aggiornati da ts#react-website#auth
Di seguito è riportato l’elenco di tutti i file generati/aggiornati dal generatore ts#react-website#auth
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- user-identity.ts costrutto CDK per creare pool di utenti/identità
Directorytypes/
Directorysrc/
- runtime-config.ts aggiornato per aggiungere cognitoProps
Directorygame-ui/
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.tsx aggiunge l’utente loggato/logout all’header
DirectoryCognitoAuth/
- index.ts gestisce il login in Cognito
DirectoryRuntimeConfig/
- index.tsx recupera il
runtime-config.json
e lo fornisce ai figli via contesto
- index.tsx recupera il
Directoryhooks/
- useRuntimeConfig.tsx
- main.tsx Aggiornato per aggiungere Cognito
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// Registra l'istanza del router per la type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
I componenti RuntimeConfigProvider
e CognitoAuth
sono stati aggiunti al file main.tsx
tramite una trasformazione AST. Questo permette al componente CognitoAuth
di autenticarsi con Amazon Cognito recuperando il runtime-config.json
che contiene la configurazione di connessione Cognito necessaria per effettuare chiamate al backend verso la destinazione corretta.
Game UI: Connessione a Story API
Sezione intitolata “Game UI: Connessione a Story API”Ora configuriamo la nostra Game UI per connettersi alla Story API creata precedentemente:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - api-connection
- Compila i parametri richiesti
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire/cambiare nel tuo albero delle directory.
File aggiornati da UI -> FastAPI api-connection
Di seguito è riportato l’elenco di tutti i file generati/aggiornati dal generatore api-connection
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directoryhooks/
- useSigV4.tsx usato da StoryApi per firmare le richieste
- useStoryApiClient.tsx hook per costruire un client StoryApi
- useStoryApi.tsx hook per interagire con StoryApi usando TanStack Query
Directorycomponents/
- QueryClientProvider.tsx provider del client TanStack Query
- StoryApiProvider.tsx Provider per l’hook TanStack Query di StoryApi
- main.tsx Inietta QueryClientProvider e StoryApiProvider
- .gitignore ignora i file client generati
- project.json aggiornato per aggiungere target per generare hook openapi
- …
Directorystory_api/
Directoryscripts/
- generate_open_api.py
- project.json aggiornato per emettere un file openapi.json
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.apis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
Questo hook può essere usato per effettuare richieste API autenticate alla StoryApi
. Come puoi vedere nell’implementazione, utilizza StoryApi
che viene generato al momento della build, quindi vedrai un errore nel tuo IDE finché non compiliamo il codice. Per maggiori dettagli su come viene generato il client o su come consumare l’API, consulta la guida React to FastAPI.
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
export default StoryApiProvider;
Il componente provider sopra utilizza l’hook useStoryApiClient
e istanzia StoryApiOptionsProxy
, che viene utilizzato per costruire le opzioni per gli hook TanStack Query. Puoi usare l’hook corrispondente useStoryApi
per accedere a questo proxy di opzioni, che fornisce un modo per interagire con la tua FastAPI in modo coerente con la tua API tRPC.
Poiché useStoryApiClient
ci fornisce un async iterator per la nostra API di streaming, in questo tutorial useremo direttamente il client vanilla.
Game UI: Connessione a Game API
Sezione intitolata “Game UI: Connessione a Game API”Ora configuriamo la nostra Game UI per connettersi alla Game API creata precedentemente:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - api-connection
- Compila i parametri richiesti
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire/cambiare nel tuo albero delle directory.
File aggiornati da UI -> tRPC api-connection
Di seguito è riportato l’elenco di tutti i file generati/aggiornati dal generatore api-connection
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directorycomponents/
- GameApiClientProvider.tsx configura il client GameAPI
Directoryhooks/
- useGameApi.tsx hook per chiamare la GameApi
- main.tsx inietta i provider del client trpc
- package.json
import { useGameApi as useClient } from '../components/GameApiClientProvider';
export const useGameApi = useClient;
Questo hook utilizza l’ultima integrazione React Query di tRPC, permettendo agli utenti di interagire direttamente con @tanstack/react-query
senza ulteriori livelli di astrazione. Per esempi su come chiamare le API tRPC, consulta la guida all’uso dell’hook tRPC.
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// Registra l'istanza del router per la type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <RouterProvider router={router} /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
Il file main.tsx
è stato aggiornato tramite una trasformazione AST per iniettare i provider tRPC.
Game UI: Infrastruttura
Sezione intitolata “Game UI: Infrastruttura”Ora l’ultimo sotto-progetto che dobbiamo creare è per l’infrastruttura CDK. Per crearlo, segui questi passaggi:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#infra
- Compila i parametri richiesti
- name: infra
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire/cambiare nel tuo albero delle directory.
File aggiornati da ts#infra
Di seguito è riportato l’elenco di tutti i file generati/aggiornati dal generatore ts#infra
. Esamineremo alcuni dei file chiave evidenziati nell’albero delle directory:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
Directorycfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
Directoryinfra
Directorysrc/
Directorystacks/
- application-stack.ts risorse CDK definite qui
- index.ts
- main.ts punto di ingresso che definisce tutti gli stack
- cdk.json
- project.json
- …
- package.json
- tsconfig.json aggiunge riferimenti
- tsconfig.base.json aggiunge alias
import { ApplicationStack } from './stacks/application-stack.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// Usa questo per deployare il tuo ambiente sandbox (presuppone le credenziali CLI)new ApplicationStack(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, crossRegionReferences: true,});
app.synth();
Questo è il punto di ingresso per la tua applicazione CDK.
È configurato per usare cfn-guard
per eseguire la validazione dell’infrastruttura basata sul set di regole configurato. Questo viene strumentato post-sintesi.
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// Il codice che definisce il tuo stack va qui }}
Qui istanzieremo i nostri costrutti CDK per costruire il nostro gioco dungeon adventure.
Aggiorniamo la nostra infrastruttura
Sezione intitolata “Aggiorniamo la nostra infrastruttura”Modifichiamo il file packages/infra/src/stacks/application-stack.ts
per istanziare alcuni dei nostri costrutti già generati:
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), }); const storyApi = new StoryApi(this, 'StoryApi', { integrations: StoryApi.defaultIntegrations(this).build(), });
// grant our authenticated role access to invoke our APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
Nota che forniamo integrazioni predefinite per le nostre due API. Di default, ogni operazione nella nostra API è mappata a una singola funzione lambda per gestire quell’operazione.
Compiliamo il nostro codice
Sezione intitolata “Compiliamo il nostro codice”Comandi Nx
Target singoli vs multipli
Sezione intitolata “Target singoli vs multipli”Il comando run-many
eseguirà un target su più sotto-progetti elencati (--all
li selezionerà tutti). Assicurerà che le dipendenze siano eseguite nell’ordine corretto.
Puoi anche attivare un build (o qualsiasi altro task) per un target di progetto singolo eseguendo il target direttamente sul progetto. Ad esempio, se vogliamo compilare il progetto @dungeon-adventure/infra
, puoi eseguire:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
Visualizzare le dipendenze
Sezione intitolata “Visualizzare le dipendenze”Puoi anche visualizzare le tue dipendenze tramite:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Caching
Sezione intitolata “Caching”Nx si basa sul caching per riutilizzare gli artefatti di build precedenti e velocizzare lo sviluppo. È necessaria una configurazione per farlo funzionare correttamente e ci possono essere casi in cui vuoi eseguire una build senza usare la cache. Per farlo, aggiungi semplicemente l’argomento --skip-nx-cache
al tuo comando. Ad esempio:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
Se per qualsiasi motivo vuoi cancellare la tua cache (memorizzata nella cartella .nx
), puoi eseguire:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
Dovresti ricevere il seguente prompt:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changes
Questo messaggio indica che NX ha rilevato alcuni file che possono essere aggiornati automaticamente. In questo caso, si riferisce ai file tsconfig.json
che non hanno riferimenti TypeScript configurati sui progetti referenziati. Seleziona l’opzione Yes, sync the changes and run the tasks per procedere. Dovresti notare che tutti gli errori di importazione nel tuo IDE vengono risolti automaticamente poiché il generatore di sync aggiungerà i riferimenti TypeScript mancanti automaticamente!
Tutti gli artefatti compilati sono ora disponibili nella cartella dist/
alla radice del monorepo. Questa è una pratica standard quando si usano progetti generati da @aws/nx-plugin
poiché non inquina l’albero delle directory con file generati. Nel caso volessi pulire i file, puoi semplicemente eliminare la cartella dist/
senza preoccuparti di file generati sparsi nell’albero delle directory.
Complimenti! Hai creato tutti i sotto-progetti necessari per iniziare a implementare il cuore del nostro gioco Dunegeon Adventure. 🎉🎉🎉