Gioco di Dungeon con IA
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 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 di pacchetti
- 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 diversi sottoprogetti utilizzando il plugin @aws/nx-plugin
.
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 API
- 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 utilizzata sia da CDK che dal sito web
- project.json
- …
Directorygame-api/
Directorybackend/ codice di implementazione 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
Directoryprocedures/ implementazioni specifiche per le 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 l’handler Lambda che definisce tutte le procedure
- project.json
- …
Directoryschema/
Directorysrc/
Directoryprocedures/
- echo.ts
- index.ts
- 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 in cui 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 ':dungeon-adventure/game-api-schema';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Questo file è l’implementazione del metodo echo
e, come puoi vedere, è fortemente tipizzato dichiarando le sue strutture dati di input e output. Importa queste definizioni dal progetto :dungeon-adventure/game-api-schema
, che è un alias per il progetto schema.
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 di stringhe 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 costrutto CDK scope * @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/backend/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 è distribuito 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
, il bundling non avviene (contrariamente all’uso di NodeJsFunction) poiché lo abbiamo già incluso come parte del target di build del progetto backend.
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
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --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 ambiente virtuale 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 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 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 costrutto CDK scope * @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 è distribuito 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
, il bundling non avviene (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 del generatore che esegue una trasformazione AST preservando tutto il codice esistente e aggiungendo un aggiornamento. Qui puoi vedere che StoryApi
è stato aggiunto alla definizione IRuntimeConfig
, il che significa che quando questo verrà consumato dal nostro 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 utilizzare Pydantic per dichiarare gli input e gli output dei tuoi metodi per garantire la type safety.
Game UI: Website
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#cloudscape-website
- Compila i parametri richiesti
- name: GameUI
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
Dovresti vedere alcuni nuovi file apparire nel tuo albero delle directory.
File aggiornati da ts#cloudscape-website
Di seguito è riportato l’elenco di tutti i file generati dal generatore ts#cloudscape-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 utilizzano 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 al bundle generato per la nostra UI basata su Vite. Ciò significa che al momento della 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 React viene montato. 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 all’interno della cartella routes
e @tanstack/react-router
creerà automaticamente la configurazione 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
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#cloudscape-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#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-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#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-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#cloudscape-website#auth
Di seguito è riportato l’elenco di tutti i file generati/aggiornati dal generatore ts#cloudscape-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 le chiamate al backend verso la destinazione corretta.
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 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 Strumenta QueryClientProvider e StoryApiProvider
- .gitignore ignora i file client generati
- project.json aggiornato per aggiungere target per la generazione di 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 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.
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/
DirectoryTrpcClients/
- index.tsx
- TrpcApis.tsx tutte le API tRPC configurate
- TrpcClientProviders.tsx crea un client provider per ogni API tRPC
- TrpcProvider.tsx
Directoryhooks/
- useGameApi.tsx hook per chiamare la GameApi
- main.tsx inietta i provider client tRPC
- package.json
import { TrpcApis } from '../components/TrpcClients';
export const useGameApi = () => TrpcApis.GameApi.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 TrpcClientProviders from './components/TrpcClients';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> <TrpcClientProviders> <RouterProvider router={router} /> </TrpcClientProviders> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
Il file main.tsx
è stato aggiornato tramite una trasformazione AST per iniettare i provider tRPC.
Game UI: 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/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)],});
// Utilizza questo per distribuire 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 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 }}
Questo è dove istanzieremo i nostri costrutti CDK per costruire il nostro gioco dungeon adventure.
Aggiorniamo la nostra infrastruttura
Apportiamo un aggiornamento 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. Per impostazione predefinita, ogni operazione nella nostra API è mappata a una singola funzione lambda per gestire quell’operazione.
Compiliamo il nostro codice
Comandi Nx
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
Puoi anche visualizzare le tue dipendenze tramite:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Caching
Nx si basa sul caching per riutilizzare gli artefatti di build precedenti e velocizzare lo sviluppo. È necessaria una configurazione per far funzionare correttamente questa funzionalità e potrebbero esserci casi in cui desideri 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 desideri 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 all’interno della cartella dist/
situata alla radice del monorepo. Questa è una pratica standard quando si utilizzano progetti generati dal plugin @aws/nx-plugin
, poiché non inquina l’albero delle directory con file generati. Nel caso desideri pulire i file, puoi semplicemente eliminare la cartella dist/
senza preoccuparti di file generati sparsi nell’albero delle directory.
Complimenti! Hai creato tutti i sottoprogetti necessari per iniziare a implementare il cuore del nostro gioco Dunegeon Adventure. 🎉🎉🎉