Gioco di Dungeon con IA
Modulo 1: Configurazione del monorepo
Sezione intitolata “Modulo 1: Configurazione del monorepo”Iniziamo creando un nuovo monorepo. All’interno della directory desiderata, 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 all’interno della directory dungeon-adventure
che potrai aprire in VSCode. Dovrebbe apparire come segue:
Directory.nx/
- …
Directory.vscode/
- …
Directorynode_modules/
- …
Directorypackages/ qui risiederanno i tuoi sottoprogetti
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json configura la CLI NX e le impostazioni predefinite del monorepo
- package.json tutte le dipendenze Node sono definite qui
- pnpm-lock.yaml o bun.lock, yarn.lock, package-lock.json in base al package manager
- pnpm-workspace.yaml se si utilizza pnpm
- README.md
- tsconfig.base.json tutti i sottoprogetti basati su Node estendono questo file
- tsconfig.json
Ora siamo pronti per iniziare a creare i vari sottoprogetti utilizzando @aws/nx-plugin
.
API del gioco
Sezione intitolata “API del gioco”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 modificati 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/ componenti CDK specifici dell’applicazione
Directoryapis/
- game-api.ts costrutto CDK per creare l’API tRPC
- index.ts
- …
- index.ts
Directorycore/ componenti CDK generici
Directoryapi/
- rest-api.ts costrutto base CDK per un’API Gateway Rest API
- trpc-utils.ts utility per i costrutti CDK delle API tRPC
- utils.ts utility per i costrutti API
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Directorytypes/ tipi condivisi
Directorysrc/
- index.ts
- runtime-config.ts definizione dell’interfaccia utilizzata sia da CDK che dal sito web
- project.json
- …
Directorygame-api/ API tRPC
Directorysrc/
Directoryclient/ client vanilla tipicamente utilizzato 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 delle procedure/route dell’API
- echo.ts
- index.ts
- init.ts configura il contesto e il middleware
- local-server.ts utilizzato 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
Esaminiamo 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 dell’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';
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 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 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 utilizzando 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 effettuare 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. Ciò significa che al momento di cdk synth
non avviene il bundling (contrariamente all’uso di NodeJsFunction) poiché lo abbiamo già incluso come parte del target di build del progetto backend.
API della storia
Sezione intitolata “API della storia”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 modificati 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 ambiente virtuale per il monorepo
- …
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ componenti 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 la 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 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 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 utilizzando 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 effettuare 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. Ciò significa che al momento di cdk synth
non avviene il bundling (contrariamente a 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 solo le parti necessarie. Qui puoi vedere che StoryApi
è stato aggiunto alla definizione IRuntimeConfig
, il che significa che quando verrà utilizzato 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 sulla route GET /
. Puoi utilizzare Pydantic per dichiarare input e output dei metodi e garantire la type safety.
Interfaccia utente del gioco: Sito web
Sezione intitolata “Interfaccia utente del gioco: Sito web”Ora creiamo l’interfaccia utente che ti 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 modificati 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/ componenti 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 utilizzano questo componente come base
Directorywelcome/
- index.tsx
- config.ts
- main.tsx punto di ingresso di 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 per la UI basata su Vite. Ciò 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 un @tanstack/react-router
in una configurazione file-based-routing
. Ciò 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 server di sviluppo è in esecuzione). Questo verrà mostrato in una sezione successiva di questo tutorial.
Interfaccia utente del gioco: Autenticazione
Sezione intitolata “Interfaccia utente del gioco: Autenticazione”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/modificarsi nel tuo albero delle directory.
File modificati 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 context
- 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.
Interfaccia utente del gioco: Connessione a Story API
Sezione intitolata “Interfaccia utente del gioco: 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/modificarsi nel tuo albero delle directory.
File modificati 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 utilizzato da StoryApi per firmare le richieste
- useStoryApiClient.tsx hook per costruire un client StoryApi
- useStoryApi.tsx hook per interagire con StoryApi utilizzando 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 utilizzato 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 eseguiremo la build del 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 indicato utilizza l’hook useStoryApiClient
e istanzia StoryApiOptionsProxy
, che viene utilizzato per costruire le opzioni per gli hook TanStack Query. Puoi utilizzare 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 utilizzeremo direttamente il client vanilla.
Interfaccia utente del gioco: Connessione a Game API
Sezione intitolata “Interfaccia utente del gioco: 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/modificarsi nel tuo albero delle directory.
File modificati 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 { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;
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.
Interfaccia utente del gioco: Infrastruttura
Sezione intitolata “Interfaccia utente del gioco: Infrastruttura”Ora l’ultimo sottoprogetto 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/modificarsi nel tuo albero delle directory.
File modificati 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)],});
// Utilizza 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 utilizzare cfn-guard
per eseguire la validazione dell’infrastruttura basata sul ruleset 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 }}
Questo è dove istanzieremo i nostri costrutti CDK per costruire il nostro gioco dungeon adventure.
Aggiorniamo la nostra infrastruttura
Sezione intitolata “Aggiorniamo la nostra infrastruttura”Apportiamo una modifica al 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 nelle nostre 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ù sottoprogetti elencati (--all
li selezionerà tutti). Garantirà che le dipendenze vengano eseguite nell’ordine corretto.
Puoi anche attivare una build (o qualsiasi altro task) per un target di progetto singolo eseguendo il target direttamente sul progetto. Ad esempio, se vogliamo buildare 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 artefatti da build precedenti e velocizzare lo sviluppo. È necessaria una configurazione per far funzionare correttamente questa funzionalità e potrebbero esserci casi in cui vuoi eseguire una build senza utilizzare 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 volessi 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 per i progetti dipendenti. 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!
Tutti gli artefatti compilati sono ora disponibili nella cartella dist/
alla radice del monorepo. Questa è una pratica standard quando si utilizzano progetti generati da @aws/nx-plugin
poiché non inquina l’albero dei file con file generati. Nel caso volessi pulire i tuoi file, puoi semplicemente eliminare la cartella dist/
senza preoccuparti di file generati sparsi nell’albero delle directory.
Congratulazioni! Hai creato tutti i sottoprogetti necessari per iniziare a implementare il cuore del nostro gioco Dungeon Adventure. 🎉🎉🎉