Configurar um monorepo
Tarefa 1: Criar um monorepo
Seção intitulada “Tarefa 1: Criar um monorepo”Para criar um novo monorepo, dentro do diretório desejado, execute o seguinte comando:
npx create-nx-workspace@21.6.8 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.8 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.8 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsnpx create-nx-workspace@21.6.8 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgentsIsso configurará um monorepo NX dentro do diretório dungeon-adventure. Quando você abrir o diretório no VSCode, verá esta estrutura de arquivos:
Directory.nx/
- …
Directory.vscode/
- …
Directorynode_modules/
- …
Directorypackages/ aqui residirão seus subprojetos
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json configura o CLI do NX e padrões do monorepo
- package.json todas as dependências Node são definidas aqui
- pnpm-lock.yaml ou bun.lock, yarn.lock, package-lock.json dependendo do gerenciador de pacotes
- pnpm-workspace.yaml se usar pnpm
- README.md
- tsconfig.base.json todos os subprojetos baseados em Node estendem este
- tsconfig.json
- aws-nx-plugin.config.mts configuração do Nx Plugin para AWS
Agora podemos começar a criar nossos diferentes subprojetos usando o @aws/nx-plugin.
Tarefa 2: Criar uma Game API
Seção intitulada “Tarefa 2: Criar uma Game API”Primeiro, vamos criar nossa Game API. Para isso, crie uma API tRPC chamada GameApi usando estas etapas:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#trpc-api - Preencha os parâmetros obrigatórios
- name: GameApi
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do ts#trpc-api
Abaixo está uma lista de todos os arquivos gerados pelo gerador ts#trpc-api. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ constructs CDK específicos do aplicativo
Directoryapis/
- game-api.ts construct CDK para criar sua API tRPC
- index.ts
- …
- index.ts
Directorycore/ constructs CDK genéricos
Directoryapi/
- rest-api.ts construct CDK base para uma API Gateway Rest API
- trpc-utils.ts utilitários para constructs CDK de API tRPC
- utils.ts utilitários para constructs de API
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Directorytypes/ tipos compartilhados
Directorysrc/
- index.ts
- runtime-config.ts definição de interface usada por CDK e website
- project.json
- …
Directorygame-api/ API tRPC
Directorysrc/
Directoryclient/ cliente vanilla tipicamente usado para chamadas máquina a máquina em TS
- index.ts
- sigv4.ts
Directorymiddleware/ instrumentação com powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Directoryschema/ definições de inputs e outputs da API
- echo.ts
Directoryprocedures/ implementações específicas dos procedimentos/rotas da API
- echo.ts
- index.ts
- init.ts configura contexto e middleware
- local-server.ts usado ao executar o servidor tRPC localmente
- router.ts ponto de entrada para o lambda handler que define todos os procedimentos
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
Vamos analisar estes arquivos-chave:
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;O router define o ponto de entrada para sua API tRPC e é onde você declarará todos os métodos da API. Como visto acima, temos um método chamado echo com sua implementação no arquivo ./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 }));Este arquivo é a implementação do método echo e como visto é fortemente tipado declarando suas estruturas de dados de entrada e saída.
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>;Todas as definições de schema tRPC são definidas usando Zod e exportadas como tipos TypeScript via sintaxe 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 de strings para todos os nomes de operações da APItype Operations = Procedures<AppRouter>;
/** * Propriedades para criar um construct GameApi * * @template TIntegrations - Mapa de nomes de operação para suas integrações */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * Mapa de nomes de operação para suas integrações no API Gateway */ integrations: TIntegrations;}
/** * Um construct CDK que cria e configura uma AWS API Gateway REST API * especificamente para GameApi. * @template TIntegrations - Mapa de nomes de operação para suas integrações */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * Cria integrações padrão para todas as operações, implementando cada operação como * sua própria função lambda individual. * * @param scope - O escopo do construct CDK * @returns Um IntegrationBuilder com integrações lambda padrão */ 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: [ // Aqui concedemos a qualquer credencial AWS da conta em que o projeto é implantado para chamar a API. // Acesso refinado máquina a máquina pode ser definido aqui usando principals mais específicos (ex: roles ou // usuários) e recursos (ex: quais caminhos da API podem ser invocados por qual principal) se necessário. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // Abre OPTIONS para permitir que navegadores façam requisições preflight não autenticadas new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}Este é o construct CDK que define nossa GameApi. Ele fornece um método defaultIntegrations que cria automaticamente uma função Lambda para cada procedimento em nossa API tRPC, apontando para a implementação da API empacotada. Isso significa que no tempo de cdk synth, o empacotamento não ocorre (diferente de usar NodeJsFunction) pois já o empacotamos como parte do target de build do projeto backend.
Tarefa 3: Criar agentes de história
Seção intitulada “Tarefa 3: Criar agentes de história”Agora vamos criar nossos Story Agents.
Agente de história: Projeto Python
Seção intitulada “Agente de história: Projeto Python”Para criar um projeto Python:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - py#project - Preencha os parâmetros obrigatórios
- name: story
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do py#project
O py#project gera estes arquivos:
Directory.venv/ único ambiente virtual para o monorepo
- …
Directorypackages/
Directorystory/
Directorydungeon_adventure_story/ módulo Python
- hello.py exemplo de arquivo Python (vamos ignorar este)
Directorytests/
- …
- .python-version
- pyproject.toml
- project.json
- .python-version versão Python fixada
- pyproject.toml
- uv.lock
Isso configurou um projeto Python e UV Workspace com ambiente virtual compartilhado.
Agente de história: Agente Strands
Seção intitulada “Agente de história: Agente Strands”Para adicionar um agente Strands ao projeto com o gerador py#strands-agent:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - py#strands-agent - Preencha os parâmetros obrigatórios
- project: story
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do py#strands-agent
O py#strands-agent gera estes arquivos:
Directorypackages/
Directorystory/
Directorydungeon_adventure_story/ módulo Python
Directoryagent/
- main.py ponto de entrada do agente no Bedrock AgentCore Runtime
- agent.py define um agente e ferramentas de exemplo
- agentcore_mcp_client.py utilitário para criar clientes para interagir com servidores MCP
- Dockerfile define a imagem Docker para implantação no AgentCore Runtime
Directorycommon/constructs/
Directorysrc
Directorycore/agent-core/
- runtime.ts construct genérico para implantação no AgentCore Runtime
Directoryapp/agents/story-agent/
- story-agent.ts construct para implantar seu agente Story no AgentCore Runtime
Vamos analisar alguns dos arquivos em detalhes:
from contextlib import contextmanager
from strands import Agent, toolfrom strands_tools import current_time
# Define uma ferramenta customizada@tooldef add(a: int, b: int) -> int: return a + b
@contextmanagerdef get_agent(session_id: str): yield Agent( system_prompt="""Você é um mago da adição.Use a ferramenta 'add' para tarefas de adição.Refira-se às ferramentas como seu 'grimório'.""", tools=[add, current_time], )Isso cria um agente Strands de exemplo e define uma ferramenta de adição.
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
@app.entrypointasync def invoke(payload, context): """Handler para invocação do agente""" prompt = payload.get( "prompt", "Nenhum prompt encontrado no input, por favor oriente o usuário " "a criar um payload json com a chave 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()Este é o ponto de entrada do agente, configurado usando o Amazon Bedrock AgentCore SDK. Ele usa o suporte do Strands para streaming e transmite eventos de volta ao cliente conforme ocorrem.
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, }); }}Isso configura um DockerImageAsset do CDK que envia sua imagem Docker do agente para o ECR e a hospeda usando o AgentCore Runtime.
Você pode notar um Dockerfile extra, que referencia a imagem Docker do projeto story, permitindo que co-localizemos o Dockerfile e o código-fonte do agente.
Tarefa 4: Configurar ferramentas de inventário
Seção intitulada “Tarefa 4: Configurar ferramentas de inventário”Inventário: Projeto TypeScript
Seção intitulada “Inventário: Projeto TypeScript”Vamos criar um servidor MCP para fornecer ferramentas para nosso Story Agent gerenciar o inventário de um jogador.
Primeiro, criamos um projeto TypeScript:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#project - Preencha os parâmetros obrigatórios
- name: inventory
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runIsso criará um projeto TypeScript vazio.
Arquivos atualizados do ts#project
O gerador ts#project gera estes arquivos.
Directorypackages/
Directoryinventory/
Directorysrc/
- index.ts ponto de entrada com função de exemplo
- project.json configuração do projeto
- eslint.config.mjs configuração de lint
- vite.config.ts configuração de testes
- tsconfig.json configuração base TypeScript para o projeto
- tsconfig.lib.json configuração TypeScript para compilação e empacotamento
- tsconfig.spec.json configuração TypeScript para testes
- tsconfig.base.json atualizado para configurar um alias para outros projetos referenciarem este
Inventário: Servidor MCP
Seção intitulada “Inventário: Servidor MCP”Em seguida, adicionaremos um servidor MCP ao nosso projeto TypeScript:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#mcp-server - Preencha os parâmetros obrigatórios
- project: inventory
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runIsso adicionará um servidor MCP.
Arquivos atualizados do ts#mcp-server
O gerador ts#mcp-server gera estes arquivos.
Directorypackages/
Directoryinventory/
Directorysrc/mcp-server/
- server.ts cria o servidor MCP
Directorytools/
- add.ts ferramenta de exemplo
Directoryresources/
- sample-guidance.ts recurso de exemplo
- stdio.ts ponto de entrada para MCP com transporte STDIO
- http.ts ponto de entrada para MCP com transporte HTTP streamable
- Dockerfile constrói a imagem para AgentCore Runtime
- rolldown.config.ts configuração para empacotar o servidor MCP para implantação no AgentCore
Directorycommon/constructs/
Directorysrc
Directoryapp/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts construct para implantar seu servidor MCP de inventário no AgentCore Runtime
Tarefa 5: Criar a Interface do Usuário (UI)
Seção intitulada “Tarefa 5: Criar a Interface do Usuário (UI)”Nesta tarefa, criaremos a UI que permitirá interagir com o jogo.
UI do Jogo: Website
Seção intitulada “UI do Jogo: Website”Para criar a UI, crie um website chamado GameUI usando estas etapas:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#react-website - Preencha os parâmetros obrigatórios
- name: GameUI
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do ts#react-website
O ts#react-website gera estes arquivos. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ constructs CDK específicos do aplicativo
Directorystatic-websites/
- game-ui.ts construct CDK para criar sua Game UI
Directorycore/
- static-website.ts construct genérico para website estático
Directorygame-ui/
Directorypublic/
- …
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.ts layout geral da página: cabeçalho, rodapé, sidebar, etc
- navitems.ts itens de navegação da sidebar
Directoryhooks/
- useAppLayout.tsx permite definir dinamicamente notificações, estilo da página, etc
Directoryroutes/ rotas baseadas em arquivo do @tanstack/react-router
- index.tsx página raiz ’/’ redireciona para ‘/welcome’
- __root.tsx todas as páginas usam este componente como base
Directorywelcome/
- index.tsx
- config.ts
- main.tsx ponto de entrada do React
- routeTree.gen.ts atualizado automaticamente pelo @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, ), ), }); }}Este é o construct CDK que define nossa GameUI. Ele já configurou o caminho para o bundle gerado para nossa UI baseada em Vite. Isso significa que no tempo de build, o empacotamento ocorre dentro do target de build do projeto game-ui e a saída é usada aqui.
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 a instância do router para 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>, );Este é o ponto de entrada onde o React é montado. Como mostrado, inicialmente apenas configura um @tanstack/react-router em uma configuração de roteamento baseado em arquivo. Enquanto o servidor de desenvolvimento estiver em execução, você pode criar arquivos na pasta routes e o @tanstack/react-router criará a configuração de arquivo boilerplate para você, atualizando o arquivo routeTree.gen.ts. Este arquivo mantém todas as rotas de forma type-safe, o que significa que ao usar <Link>, a opção to só mostrará rotas válidas.
Para mais informações, consulte a documentação do @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> );}Um componente será renderizado ao navegar para a rota /welcome. O @tanstack/react-router gerenciará a Route para você sempre que criar/mover este arquivo (desde que o servidor de desenvolvimento esteja em execução).
UI do Jogo: Autenticação
Seção intitulada “UI do Jogo: Autenticação”Vamos configurar nossa Game UI para exigir acesso autenticado via Amazon Cognito usando estas etapas:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#react-website#auth - Preencha os parâmetros obrigatórios
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem/mudarem na sua árvore de arquivos.
Arquivos atualizados do ts#react-website#auth
O gerador ts#react-website#auth atualiza/gera estes arquivos. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- user-identity.ts construct CDK para criar pools de usuário/identidade
Directorytypes/
Directorysrc/
- runtime-config.ts atualizado para adicionar cognitoProps
Directorygame-ui/
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.tsx adiciona o usuário logado/logout ao cabeçalho
DirectoryCognitoAuth/
- index.ts gerencia login no Cognito
DirectoryRuntimeConfig/
- index.tsx busca o
runtime-config.jsone o fornece aos filhos via contexto
- index.tsx busca o
Directoryhooks/
- useRuntimeConfig.tsx
- main.tsx Atualizado para adicionar 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 a instância do router para 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>, );Os componentes RuntimeConfigProvider e CognitoAuth foram adicionados ao arquivo main.tsx via transformação AST. Isso permite que o componente CognitoAuth autentique com o Amazon Cognito buscando o runtime-config.json que contém a configuração de conexão do Cognito necessária para fazer chamadas backend para o destino correto.
UI do Jogo: Conectar à Game API
Seção intitulada “UI do Jogo: Conectar à Game API”Vamos configurar nossa Game UI para se conectar à nossa Game API criada anteriormente.
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - api-connection - Preencha os parâmetros obrigatórios
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem/mudarem na sua árvore de arquivos.
Arquivos atualizados da conexão UI -> tRPC
O gerador api-connection gera/atualiza estes arquivos. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directorycomponents/
- GameApiClientProvider.tsx configura o cliente GameAPI
Directoryhooks/
- useGameApi.tsx hooks para chamar a GameApi
- main.tsx injeta os providers do cliente trpc
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;Este hook usa a integração mais recente do React Query do tRPC, permitindo que usuários interajam com @tanstack/react-query diretamente sem camadas adicionais de abstração. Para exemplos de como chamar APIs tRPC, consulte o guia de uso do 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 a instância do router para 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>, );O arquivo main.tsx foi atualizado via transformação AST para injetar os providers do tRPC.
UI do Jogo: Infraestrutura
Seção intitulada “UI do Jogo: Infraestrutura”Vamos criar o subprojeto final para a infraestrutura CDK.
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#infra - Preencha os parâmetros obrigatórios
- name: infra
- Clique em
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-interactiveVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runVocê verá alguns novos arquivos aparecerem/mudarem na sua árvore de arquivos.
Arquivos atualizados do ts#infra
O gerador ts#infra gera/atualiza estes. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- checkov.ts
- index.ts
Directoryinfra
Directorysrc/
Directorystages/
- application-stage.ts stacks CDK definidas aqui
Directorystacks/
- application-stack.ts recursos CDK definidos aqui
- index.ts
- main.ts ponto de entrada que define todos os stages
- cdk.json
- project.json
- …
- package.json
- tsconfig.json adiciona referências
- tsconfig.base.json adiciona alias
import { ApplicationStage } from './stacks/application-stage.js';import { App } from ':dungeon-adventure/common-constructs';
const app = new App();
// Use isto para implantar seu próprio ambiente sandbox (assume suas credenciais CLI)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();Este é o ponto de entrada para sua aplicação 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);
// O código que define sua stack vai aqui }}Vamos instanciar nossos constructs CDK para construir nosso jogo dungeon adventure.
Tarefa 6: Atualizar nossa infraestrutura
Seção intitulada “Tarefa 6: Atualizar nossa infraestrutura”Vamos atualizar packages/infra/src/stacks/application-stack.ts para instanciar alguns de nossos constructs gerados:
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'); }}Tarefa 7: Construir o código
Seção intitulada “Tarefa 7: Construir o código”Comandos Nx
Targets únicos vs múltiplos
Seção intitulada “Targets únicos vs múltiplos”O comando run-many executará um target em múltiplos subprojetos listados (--all os seleciona todos). Isso garante que dependências sejam executadas na ordem correta.
Você também pode disparar um build (ou qualquer outra tarefa) para um target de projeto único executando o target diretamente no projeto. Por exemplo, para buildar o projeto @dungeon-adventure/infra, execute o seguinte 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:buildVocê também pode omitir o escopo e usar a sintaxe abreviada do Nx se preferir:
pnpm nx build infrayarn nx build infranpx nx build infrabunx nx build infraVisualizando suas dependências
Seção intitulada “Visualizando suas dependências”Para visualizar suas dependências, execute:
pnpm nx graphyarn nx graphnpx nx graphbunx nx graph
O Nx depende de cache para reutilizar artefatos de builds anteriores e acelerar o desenvolvimento. É necessária alguma configuração para que isso funcione corretamente e pode haver casos onde você queira executar um build sem usar o cache. Para isso, simplesmente adicione o argumento --skip-nx-cache ao seu comando. Por exemplo:
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 por qualquer motivo você quiser limpar seu cache (armazenado na pasta .nx), você pode executar o seguinte comando:
pnpm nx resetyarn nx resetnpx nx resetbunx nx resetUsando a linha de comando, execute:
pnpm nx run-many --target build --allyarn nx run-many --target build --allnpx nx run-many --target build --allbunx nx run-many --target build --allVocê será questionado com o seguinte:
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 changesEsta mensagem indica que o NX detectou alguns arquivos que podem ser atualizados automaticamente. Neste caso, está se referindo aos arquivos tsconfig.json que não têm referências de projeto TypeScript configuradas em projetos dependentes.
Selecione a opção Yes, sync the changes and run the tasks para prosseguir. Você deve notar que todos os erros de importação relacionados à IDE são resolvidos automaticamente, pois o gerador de sincronização adicionará as referências TypeScript faltantes automaticamente!
Todos os artefatos construídos estão agora disponíveis dentro da pasta dist/ na raiz do monorepo. Esta é uma prática padrão ao usar projetos gerados pelo @aws/nx-plugin, pois não polui sua árvore de arquivos com arquivos gerados. Caso queira limpar seus arquivos, delete a pasta dist/ sem se preocupar com arquivos gerados espalhados pela árvore de arquivos.
Parabéns! Você criou todos os subprojetos necessários para começar a implementar o núcleo do nosso jogo AI Dungeon Adventure. 🎉🎉🎉