Gioco di Dungeon con IA
Modulo 1: Configurazione del monorepo
Sezione intitolata “Modulo 1: Configurazione del monorepo”Iniziamo creando un nuovo monorepo. Esegui il seguente comando dalla directory desiderata:
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 così:
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 esteso da tutti i sottoprogetti basati su Node
- tsconfig.json
Ora siamo pronti per iniziare a creare i vari sottoprogetti 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 nel tuo albero delle directory.
File aggiornati da ts#trpc-api
Ecco l’elenco di tutti i file generati dal generatore ts#trpc-api
. Esamineremo alcuni file chiave evidenziati nell’albero:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici per l’applicazione
Directoryapis/
- game-api.ts costrutto CDK per creare la tua API tRPC
- index.ts
- …
- index.ts
Directorycore/ costrutti CDK generici
Directoryapi/
- rest-api.ts costrutto base 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 per le procedure/route dell’API
- echo.ts
- index.ts
- init.ts configura il contesto e i 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 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 è 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 creare 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 Gateway REST API * 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à bundleizzata. Ciò significa che al momento di cdk synth
non avviene il bundling (a differenza di NodeJsFunction) poiché è già stato effettuato 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 nel tuo albero delle directory.
File aggiornati da py#fast-api
Ecco l’elenco di tutti i file generati dal generatore py#fast-api
. Esamineremo alcuni file chiave evidenziati nell’albero:
Directory.venv/ singolo ambiente virtuale per il monorepo
- …
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici per l’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 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 creare 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 Gateway REST API * 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à bundleizzata. Ciò significa che al momento di cdk synth
non avviene il bundling (a differenza di PythonFunction) poiché è già stato effettuato 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 aggiornando la definizione di IRuntimeConfig
. Come puoi vedere, StoryApi
è stato aggiunto alla definizione, 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 dell’API. Come puoi vedere qui, abbiamo un metodo read_root
mappato alla route GET /
. Puoi utilizzare Pydantic per dichiarare input e output dei metodi e 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#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 nel tuo albero delle directory.
File aggiornati da ts#cloudscape-website
Ecco l’elenco di tutti i file generati dal generatore ts#cloudscape-website
. Esamineremo alcuni file chiave evidenziati nell’albero:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ costrutti CDK specifici per l’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 con @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 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. 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 aggiornando il file routeTree.gen.ts
. Questo file mantiene tutte le route in modo type-safe, quindi 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#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 o modifiche nel tuo albero delle directory.
File aggiornati da ts#cloudscape-website#auth
Ecco l’elenco di tutti i file generati/aggiornati dal generatore ts#cloudscape-website#auth
. Esamineremo alcuni file chiave evidenziati nell’albero:
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 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 o modifiche nel tuo albero delle directory.
File aggiornati da UI -> FastAPI api-connection
Ecco l’elenco di tutti i file generati/aggiornati dal generatore api-connection
. Esamineremo alcuni file chiave evidenziati nell’albero:
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 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 utilizzato per effettuare richieste API autenticate alla StoryApi
. Come puoi vedere nell’implementazione, utilizza StoryApi
generato al momento del build, quindi vedrai un errore nel tuo IDE finché non compiliamo il codice. Per 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
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 o modifiche nel tuo albero delle directory.
File aggiornati da UI -> tRPC api-connection
Ecco l’elenco di tutti i file generati/aggiornati dal generatore api-connection
. Esamineremo alcuni file chiave evidenziati nell’albero:
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 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 o modifiche nel tuo albero delle directory.
File aggiornati da ts#infra
Ecco l’elenco di tutti i file generati/aggiornati dal generatore ts#infra
. Esamineremo alcuni file chiave evidenziati nell’albero:
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 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
Sezione intitolata “Aggiorniamo la nostra infrastruttura”Modifichiamo il file packages/infra/src/stacks/application-stack.ts
per istanziare alcuni dei 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.
Costruiamo il codice
Sezione intitolata “Costruiamo il 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 siano eseguite nell’ordine corretto.
Puoi anche attivare un build (o qualsiasi altro task) per un singolo progetto eseguendo il target direttamente sul progetto. Ad esempio, se vogliamo costruire 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 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 ci possono essere casi in cui vuoi eseguire un 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 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 per i progetti dipendenti. Seleziona l’opzione Yes, sync the changes and run the tasks per procedere. Noterai che tutti gli errori di importazione nel tuo IDE verranno risolti automaticamente poiché il generatore di sync aggiungerà i riferimenti TypeScript mancanti!
Tutti gli artefatti di build 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. Se vuoi pulire i file, puoi semplicemente eliminare la cartella dist/
senza preoccuparti di file generati sparsi nell’albero.
Complimenti! Hai creato tutti i sottoprogetti necessari per iniziare a implementare il cuore del gioco Dungeon Adventure. 🎉🎉🎉