Configurare un monorepo
Task 1: Creare un monorepo
Sezione intitolata “Task 1: Creare un monorepo”Per creare un nuovo monorepo, esegui il seguente comando dalla directory desiderata:
npx create-nx-workspace@21.6.5 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.5 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.5 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.5 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsQuesto configurerà un monorepo NX all’interno della directory dungeon-adventure. Quando apri la directory in VSCode, vedrai questa struttura di file:
Directory.nx/
- …
Directory.vscode/
- …
Directorynode_modules/
- …
Directorypackages/ qui risiederanno i tuoi sotto-progetti
- …
- .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 sotto-progetti basati su Node
- tsconfig.json
- aws-nx-plugin.config.mts configurazione per il Plugin Nx per AWS
Ora possiamo iniziare a creare i diversi sotto-progetti utilizzando @aws/nx-plugin.
Task 2: Creare una Game API
Sezione intitolata “Task 2: Creare una Game API”Per prima cosa, creiamo 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-interactiveyarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactivenpx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactivebunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-runVedrai alcuni nuovi file apparire nella struttura delle cartelle.
File aggiornati da ts#trpc-api
Di seguito l’elenco dei file generati dal generatore ts#trpc-api. Esamineremo alcuni file chiave evidenziati nella struttura:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ CDK constructs specifici per l’applicazione
Directoryapis/
- game-api.ts CDK construct per creare l’API tRPC
- index.ts
- …
- index.ts
Directorycore/ CDK constructs generici
Directoryapi/
- rest-api.ts CDK construct base per API Gateway Rest API
- trpc-utils.ts utility per CDK constructs di API tRPC
- utils.ts utility per i constructs API
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Directorytypes/ tipi condivisi
Directorysrc/
- index.ts
- runtime-config.ts definizione dell’interfaccia usata sia da CDK che dal sito web
- project.json
- …
Directorygame-api/ API tRPC
Directorysrc/
Directoryclient/ client vanilla tipicamente usato per chiamate machine-to-machine in TS
- index.ts
- sigv4.ts
Directorymiddleware/ strumentazione Powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Directoryschema/ definizioni di input e output per l’API
- echo.ts
Directoryprocedures/ implementazioni specifiche delle procedure/route dell’API
- echo.ts
- index.ts
- init.ts configura contesto e middleware
- local-server.ts usato per eseguire il server tRPC localmente
- router.ts entrypoint per il lambda handler che definisce tutte le procedure
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
Esaminiamo questi 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 l’entrypoint per l’API tRPC ed è dove dichiarerai tutti i metodi dell’API. Come visibile sopra, abbiamo un metodo chiamato echo con l’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 implementa il metodo echo ed è fortemente tipizzato dichiarando le 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 create con 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 construct 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;}
/** * CDK construct che crea e configura un'API REST API Gateway AWS * specifica 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 singola funzione lambda. * * @param scope - Scope del construct CDK * @returns 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: [ // Concediamo a qualsiasi credenziale AWS dell'account di deployment di chiamare l'API. // È possibile definire qui accessi granulari machine-to-machine usando principal specifici 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 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 CDK construct che definisce la nostra GameApi. Fornisce un metodo defaultIntegrations che crea automaticamente una funzione Lambda per ogni procedura nell’API tRPC, puntando all’implementazione bundled. Ciò significa che al momento di cdk synth non avviene il bundling (a differenza di NodeJsFunction) poiché è già stato eseguito come parte del target di build del progetto backend.
Task 3: Creare gli Story agents
Sezione intitolata “Task 3: Creare gli Story agents”Ora creiamo i nostri Story Agents.
Story agent: Progetto Python
Sezione intitolata “Story agent: Progetto Python”Per creare un progetto Python:
- 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#project - Compila i parametri richiesti
- name: story
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactiveyarn nx g @aws/nx-plugin:py#project --name=story --no-interactivenpx nx g @aws/nx-plugin:py#project --name=story --no-interactivebunx nx g @aws/nx-plugin:py#project --name=story --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runyarn nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runnpx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runbunx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runVedrai alcuni nuovi file apparire nella struttura delle cartelle.
File aggiornati da py#project
Il generatore py#project genera questi file:
Directory.venv/ virtual env condiviso per il monorepo
- …
Directorypackages/
Directorystory/
Directorydungeon_adventure_story/ modulo Python
- hello.py file Python di esempio (ignorabile)
Directorytests/
- …
- .python-version
- pyproject.toml
- project.json
- .python-version versione Python bloccata per UV
- pyproject.toml
- uv.lock
Questo ha configurato un progetto Python e un UV Workspace con ambiente virtuale condiviso.
Story agent: Agente Strands
Sezione intitolata “Story agent: Agente Strands”Per aggiungere un agente Strands al progetto con il generatore py#strands-agent:
- 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#strands-agent - Compila i parametri richiesti
- project: story
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactiveyarn nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactivenpx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactivebunx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-runyarn nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-runnpx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-runbunx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-runVedrai alcuni nuovi file apparire nella struttura delle cartelle.
File aggiornati da py#strands-agent
Il generatore py#strands-agent genera questi file:
Directorypackages/
Directorystory/
Directorydungeon_adventure_story/ modulo Python
Directoryagent/
- main.py entrypoint per l’agente in Bedrock AgentCore Runtime
- agent.py definisce un agente e strumenti di esempio
- agentcore_mcp_client.py utility per client MCP
- Dockerfile definisce l’immagine Docker per il deployment in AgentCore Runtime
Directorycommon/constructs/
Directorysrc
Directorycore/agent-core/
- runtime.ts construct generico per deployment in AgentCore Runtime
Directoryapp/agents/story-agent/
- story-agent.ts construct per deployare lo Story agent in AgentCore Runtime
Esaminiamo alcuni file in dettaglio:
from contextlib import contextmanager
from strands import Agent, toolfrom strands_tools import current_time
# Definisci uno strumento personalizzato@tooldef add(a: int, b: int) -> int: return a + b
@contextmanagerdef get_agent(session_id: str): yield Agent( system_prompt="""Sei un mago delle addizioni.Usa lo strumento 'add' per le operazioni di addizione.Riferisciti agli strumenti come al tuo 'grimorio'.""", tools=[add, current_time], )Questo crea un agente Strands di esempio e definisce uno strumento di addizione.
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
@app.entrypointasync def invoke(payload, context): """Handler per l'invocazione dell'agente""" prompt = payload.get( "prompt", "Nessun prompt trovato nell'input, guida l'utente " "a creare un payload JSON con chiave prompt" )
with get_agent(session_id=context.session_id) as agent: stream = agent.stream_async(prompt) async for event in stream: print(event) yield (event)
if __name__ == "__main__": app.run()Questo è l’entrypoint dell’agente, configurato con Amazon Bedrock AgentCore SDK. Utilizza il supporto streaming di Strands per inviare eventi al client in tempo reale.
import { Lazy, Names } from 'aws-cdk-lib';import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';import { Construct } from 'constructs';import { execSync } from 'child_process';import * as path from 'path';import * as url from 'url';import { AgentCoreRuntime, AgentCoreRuntimeProps,} from '../../../core/agent-core/runtime.js';
export type StoryAgentProps = Omit< AgentCoreRuntimeProps, 'runtimeName' | 'serverProtocol' | 'containerUri'>;
export class StoryAgent extends Construct { public readonly dockerImage: DockerImageAsset; public readonly agentCoreRuntime: AgentCoreRuntime;
constructor(scope: Construct, id: string, props?: StoryAgentProps) { super(scope, id);
this.dockerImage = new DockerImageAsset(this, 'DockerImage', { platform: Platform.LINUX_ARM64, directory: path.dirname(url.fileURLToPath(new URL(import.meta.url))), extraHash: execSync( `docker inspect dungeon-adventure-story-agent:latest --format '{{.Id}}'`, { encoding: 'utf-8' }, ).trim(), });
this.agentCoreRuntime = new AgentCoreRuntime(this, 'StoryAgent', { runtimeName: Lazy.string({ produce: () => Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }), }), serverProtocol: 'HTTP', containerUri: this.dockerImage.imageUri, ...props, }); }}Questo configura un DockerImageAsset CDK che carica l’immagine Docker dell’agente su ECR e la ospita usando AgentCore Runtime.
Potresti notare un Dockerfile aggiuntivo che referenzia l’immagine Docker dal progetto story, permettendo di collocare Dockerfile e codice sorgente insieme.
Task 4: Configurare gli strumenti di inventario
Sezione intitolata “Task 4: Configurare gli strumenti di inventario”Inventory: Progetto TypeScript
Sezione intitolata “Inventory: Progetto TypeScript”Creiamo un server MCP che fornirà strumenti allo Story Agent per gestire l’inventario del giocatore.
Iniziamo creando un progetto TypeScript:
- 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#project - Compila i parametri richiesti
- name: inventory
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactiveyarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactivenpx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactivebunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runQuesto creerà un progetto TypeScript vuoto.
File aggiornati da ts#project
Il generatore ts#project genera questi file.
Directorypackages/
Directoryinventory/
Directorysrc/
- index.ts entry point con funzione di esempio
- project.json configurazione del progetto
- eslint.config.mjs configurazione lint
- vite.config.ts configurazione test
- tsconfig.json configurazione TypeScript base
- tsconfig.lib.json configurazione per compilazione e bundling
- tsconfig.spec.json configurazione per i test
- tsconfig.base.json aggiornato con alias per riferire questo progetto
Inventory: Server MCP
Sezione intitolata “Inventory: Server MCP”Aggiungiamo un server MCP al progetto TypeScript:
- 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#mcp-server - Compila i parametri richiesti
- project: inventory
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactiveyarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactivenpx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactivebunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runQuesto aggiungerà un server MCP.
File aggiornati da ts#mcp-server
Il generatore ts#mcp-server genera questi file.
Directorypackages/
Directoryinventory/
Directorysrc/mcp-server/
- server.ts crea il server MCP
Directorytools/
- add.ts strumento di esempio
Directoryresources/
- sample-guidance.ts risorsa di esempio
- stdio.ts entry point per MCP con trasporto STDIO
- http.ts entry point per MCP con trasporto HTTP streamable
- Dockerfile builda l’immagine per AgentCore Runtime
- rolldown.config.ts configurazione per il bundling del server MCP
Directorycommon/constructs/
Directorysrc
Directoryapp/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts construct per deployare il server MCP in AgentCore Runtime
Task 5: Creare l’interfaccia utente (UI)
Sezione intitolata “Task 5: Creare l’interfaccia utente (UI)”In questo task, creeremo l’interfaccia utente che ti permetterà di interagire con il gioco.
Game UI: Sito Web
Sezione intitolata “Game UI: Sito Web”Per creare l’interfaccia utente, 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-interactiveyarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactivenpx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactivebunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-runVedrai alcuni nuovi file apparire nella struttura delle cartelle.
File aggiornati da ts#react-website
Il generatore ts#react-website genera questi file. Esaminiamo alcuni file chiave evidenziati nella struttura:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ CDK constructs specifici per l’applicazione
Directorystatic-websites/
- game-ui.ts CDK construct per creare la Game UI
Directorycore/
- static-website.ts construct generico per siti statici
Directorygame-ui/
Directorypublic/
- …
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.ts layout generale della pagina: header, footer, sidebar, ecc
- navitems.ts elementi di navigazione sidebar
Directoryhooks/
- useAppLayout.tsx permette di impostare notifiche, stile pagina, ecc
Directoryroutes/ routing basato su file @tanstack/react-router
- index.tsx root ’/’ reindirizza a ‘/welcome’
- __root.tsx componente base per tutte le pagine
Directorywelcome/
- index.tsx
- config.ts
- main.tsx entrypoint React
- routeTree.gen.ts generato 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, { websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}Questo è il CDK construct che definisce la nostra GameUI. Ha già configurato il percorso del 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 l’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 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 è l’entry point dove React viene montato. Come mostrato, inizialmente configura solo un @tanstack/react-router in una configurazione file-based-routing. Finché il server di sviluppo è in esecuzione, puoi creare file nella cartella routes e @tanstack/react-router creerà automaticamente la configurazione boilerplate per te, aggiornando anche 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>Benvenuto nel tuo nuovo sito Cloudscape!</Container> </SpaceBetween> </ContentLayout> );}Un componente verrà renderizzato quando si naviga verso la route /welcome. @tanstack/react-router gestirà la Route per te ogni volta che crei/sposti questo file (finché il server dev è in esecuzione).
Game UI: Autenticazione
Sezione intitolata “Game UI: Autenticazione”Configuriamo la nostra Game UI per richiedere autenticazione 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-interactiveyarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactivenpx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactivebunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactivePuoi 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-runyarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runVedrai alcuni nuovi file apparire/cambiare nella struttura delle cartelle.
File aggiornati da ts#react-website#auth
Il generatore ts#react-website#auth aggiorna/genera questi file. Esaminiamo alcuni file chiave evidenziati nella struttura:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- user-identity.ts CDK construct per pool utenti/identità
Directorytypes/
Directorysrc/
- runtime-config.ts aggiornato con cognitoProps
Directorygame-ui/
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.tsx aggiunge utente/logout nell’header
DirectoryCognitoAuth/
- index.ts gestisce il login a Cognito
DirectoryRuntimeConfig/
- index.tsx recupera
runtime-config.jsone lo fornisce via context
- index.tsx recupera
Directoryhooks/
- useRuntimeConfig.tsx
- main.tsx Aggiornato con 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 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 necessaria per effettuare le chiamate backend alla destinazione corretta.
Game UI: Connessione alla Game API
Sezione intitolata “Game UI: Connessione alla Game API”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-interactiveyarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactivenpx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactivebunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactivePuoi 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-runyarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runnpx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runbunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runVedrai alcuni nuovi file apparire/cambiare nella struttura delle cartelle.
File aggiornati da UI -> tRPC api-connection
Il generatore api-connection genera/aggiorna questi file. Esaminiamo alcuni file chiave evidenziati nella struttura:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directorycomponents/
- GameApiClientProvider.tsx configura il client GameAPI
Directoryhooks/
- useGameApi.tsx hook per chiamare la GameApi
- main.tsx inietta i provider trpc client
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;Questo hook utilizza l’ultima integrazione di tRPC con React Query 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’utilizzo 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 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”Creiamo il progetto finale per l’infrastruttura CDK.
- 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-interactiveyarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactivenpx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactivebunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactivePuoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runVedrai alcuni nuovi file apparire/cambiare nella struttura delle cartelle.
File aggiornati da ts#infra
Il generatore ts#infra genera/aggiorna questi file. Esaminiamo alcuni file chiave evidenziati nella struttura:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- checkov.ts
- index.ts
Directoryinfra
Directorysrc/
Directorystages/
- application-stage.ts stack CDK definiti qui
Directorystacks/
- application-stack.ts risorse CDK definite qui
- index.ts
- main.ts entrypoint che definisce tutti gli stage
- cdk.json
- project.json
- …
- package.json
- tsconfig.json aggiunti riferimenti
- tsconfig.base.json aggiunto alias
import { ApplicationStage } from './stacks/application-stage.js';import { App } from ':dungeon-adventure/common-constructs';
const app = new App();
// Deploya un ambiente sandbox personale (richiede credenziali CLI)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();Questo è l’entry point per l’applicazione CDK.
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 lo stack va qui }}Istanzieremo i nostri CDK constructs per costruire il gioco dungeon adventure.
Task 6: Aggiornare l’infrastruttura
Sezione intitolata “Task 6: Aggiornare l’infrastruttura”Aggiorniamo packages/infra/src/stacks/application-stack.ts per istanziare alcuni dei nostri constructs generati:
import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, StoryAgent, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), });
const { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: { customJwtAuthorizer: { discoveryUrl: `https://cognito-idp.${Stack.of(userPool).region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`, allowedAudience: [userPoolClient.userPoolClientId], }, }, environment: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.arn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.arn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.arn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.arn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}import { Stack, StackProps } from 'aws-cdk-lib';import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, StoryAgent, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: 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 { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: { customJwtAuthorizer: { discoveryUrl: `https://cognito-idp.${Stack.of(userPool).region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`, allowedAudience: [userPoolClient.userPoolClientId], }, }, environment: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.arn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.arn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.arn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.arn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}Task 7: Compilare il codice
Sezione intitolata “Task 7: Compilare il codice”Comandi Nx
Target singoli vs multipli
Sezione intitolata “Target singoli vs multipli”Il comando run-many esegue un target su più sotto-progetti elencati (--all li seleziona tutti). Questo garantisce che le dipendenze vengano eseguite nell’ordine corretto.
Puoi anche attivare una build (o qualsiasi altro task) per un singolo target di progetto eseguendo il target direttamente sul progetto. Ad esempio, per compilare il progetto @dungeon-adventure/infra, esegui il seguente comando:
pnpm nx run @dungeon-adventure/infra:buildyarn nx run @dungeon-adventure/infra:buildnpx nx run @dungeon-adventure/infra:buildbunx nx run @dungeon-adventure/infra:buildPuoi anche omettere lo scope e usare la sintassi abbreviata di Nx se preferisci:
pnpm nx build infrayarn nx build infranpx nx build infrabunx nx build infraVisualizzare le dipendenze
Sezione intitolata “Visualizzare le dipendenze”Per visualizzare le dipendenze, esegui:
pnpm nx graphyarn nx graphnpx nx graphbunx nx graph
Caching
Sezione intitolata “Caching”Nx si basa sul caching per riutilizzare gli artefatti delle build precedenti e velocizzare lo sviluppo. È necessaria una certa configurazione per farlo funzionare correttamente e potrebbero esserci casi in cui vuoi eseguire una build senza usare la cache. Per farlo, aggiungi semplicemente l’argomento --skip-nx-cache al comando. Ad esempio:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cacheyarn nx run @dungeon-adventure/infra:build --skip-nx-cachenpx nx run @dungeon-adventure/infra:build --skip-nx-cachebunx nx run @dungeon-adventure/infra:build --skip-nx-cacheSe per qualsiasi motivo volessi pulire la cache (memorizzata nella cartella .nx), puoi eseguire il seguente comando:
pnpm nx resetyarn nx resetnpx nx resetbunx nx resetDalla riga di comando, esegui:
pnpm nx run-many --target build --allyarn nx run-many --target build --allnpx nx run-many --target build --allbunx nx run-many --target build --allVedrai il seguente prompt:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Alcuni file di configurazione TypeScript mancano di riferimenti ai progetti dipendenti o contengono riferimenti obsoleti.
Questo causerà un errore in CI.
? Vuoi sincronizzare le modifiche identificate per aggiornare il workspace? …Sì, sincronizza le modifiche ed esegui i taskNo, esegui i task senza sincronizzare le modificheQuesto messaggio indica che NX ha rilevato alcuni file che possono essere aggiornati automaticamente. In questo caso, si riferisce ai file tsconfig.json che non hanno riferimenti TypeScript configurati sui progetti referenziati.
Seleziona l’opzione Sì, sincronizza le modifiche ed esegui i task per procedere. Dovresti notare che tutti gli errori di import dell’IDE vengono risolti automaticamente poiché il generatore sync aggiungerà automaticamente i riferimenti TypeScript mancanti!
Tutti gli artefatti compilati sono ora disponibili nella cartella dist/ situata alla radice del monorepo. Questa è una pratica standard quando si utilizzano progetti generati da @aws/nx-plugin poiché non inquina la struttura dei file con file generati. Nel caso in cui tu voglia pulire i file, elimina la cartella dist/ senza preoccuparti dei file generati sparsi nella struttura dei file.
Congratulazioni! Hai creato tutti i sotto-progetti necessari per iniziare a implementare il core del nostro gioco AI Dungeon Adventure. 🎉🎉🎉