设置 monorepo
任务 1:创建 monorepo
Section titled “任务 1:创建 monorepo”要创建新的 monorepo,在目标目录中运行以下命令:
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 --aiAgents这将在 dungeon-adventure 目录中设置 NX monorepo。在 VSCode 中打开该目录时,您将看到此文件结构:
文件夹.nx/
- …
文件夹.vscode/
- …
文件夹node_modules/
- …
文件夹packages/ 此处存放所有子项目
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json 配置 Nx CLI 和 monorepo 默认值
- package.json 定义所有 node 依赖项
- pnpm-lock.yaml 或 bun.lock, yarn.lock, package-lock.json(取决于包管理器)
- pnpm-workspace.yaml(如果使用 pnpm)
- README.md
- tsconfig.base.json 所有基于 node 的子项目继承此配置
- tsconfig.json
- aws-nx-plugin.config.mts 配置 Nx Plugin for AWS
现在我们可以使用 @aws/nx-plugin 开始创建不同的子项目。
任务 2:创建游戏 API
Section titled “任务 2:创建游戏 API”首先创建游戏 API。通过以下步骤创建名为 GameApi 的 tRPC API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#trpc-api - 填写必需参数
- name: GameApi
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件。
ts#trpc-api 更新文件
以下是 ts#trpc-api 生成器生成的所有文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定 CDK 构造体
文件夹apis/
- game-api.ts 创建 tRPC API 的 CDK 构造体
- index.ts
- …
- index.ts
文件夹core/ 通用 CDK 构造体
文件夹api/
- rest-api.ts API Gateway Rest API 的基础 CDK 构造体
- trpc-utils.ts tRPC API CDK 构造体的工具
- utils.ts API 构造体的工具
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
文件夹types/ 共享类型
文件夹src/
- index.ts
- runtime-config.ts CDK 和网站共用的接口定义
- project.json
- …
文件夹game-api/ tRPC API
文件夹src/
文件夹client/ 用于 TS 机器间调用的原生客户端
- index.ts
- sigv4.ts
文件夹middleware/ Powertools 工具链
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
文件夹schema/ API 输入输出定义
- echo.ts
文件夹procedures/ API 过程/路由的具体实现
- echo.ts
- index.ts
- init.ts 设置上下文和中间件
- local-server.ts 本地运行 tRPC 服务器时使用
- router.ts Lambda 处理程序的入口点,定义所有过程
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
查看部分关键文件:
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;路由文件定义了 tRPC API 的入口点,您需要在此声明所有 API 方法。如上所示,我们有一个名为 echo 的方法,其实现位于 ./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 }));该文件是 echo 方法的实现,通过声明输入输出数据结构实现强类型。
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>;所有 tRPC 模式定义均使用 Zod 定义,并通过 z.TypeOf 语法导出为 TypeScript 类型。
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';
// 所有 API 操作名称的字符串联合类型type Operations = Procedures<AppRouter>;
/** * 创建 GameApi 构造体的属性 * * @template TIntegrations - 操作名称到集成的映射 */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 操作名称到 API Gateway 集成的映射 */ integrations: TIntegrations;}
/** * 用于创建和配置 AWS API Gateway REST API 的 CDK 构造体 * @template TIntegrations - 操作名称到集成的映射 */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 为所有操作创建默认集成,将每个操作实现为独立的 lambda 函数 * * @param scope - CDK 构造体作用域 * @returns 包含默认 lambda 集成的 IntegrationBuilder */ 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: [ // 允许部署账户中的 AWS 凭证调用 API // 如需要,可使用更具体的主体(如角色或用户)和资源(如哪个主体可以调用哪个 API 路径)定义机器间细粒度访问 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 开放 OPTIONS 方法以允许浏览器进行未经身份验证的预检请求 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}这是定义 GameApi 的 CDK 构造体。其 defaultIntegrations 方法会自动为 tRPC API 中的每个过程创建 Lambda 函数,指向已打包的 API 实现。这意味着在 cdk synth 时不会进行打包(与使用 NodeJsFunction 不同),因为我们已经通过后端项目的构建目标完成了打包。
任务 3:创建故事代理
Section titled “任务 3:创建故事代理”现在创建故事代理。
故事代理:Python 项目
Section titled “故事代理:Python 项目”要创建 Python 项目:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#project - 填写必需参数
- name: story
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件。
py#project 更新文件
py#project 生成器生成这些文件:
文件夹.venv/ monorepo 共享虚拟环境
- …
文件夹packages/
文件夹story/
文件夹dungeon_adventure_story/ Python 模块
- hello.py 示例 Python 文件(可忽略)
文件夹tests/
- …
- .python-version
- pyproject.toml
- project.json
- .python-version 固定 UV Python 版本
- pyproject.toml
- uv.lock
这配置了一个使用 UV Workspace 和共享虚拟环境的 Python 项目。
故事代理:Strands 代理
Section titled “故事代理:Strands 代理”使用 py#strands-agent 生成器为项目添加 Strands 代理:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#strands-agent - 填写必需参数
- project: story
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件。
py#strands-agent 更新文件
py#strands-agent 生成器生成这些文件:
文件夹packages/
文件夹story/
文件夹dungeon_adventure_story/ Python 模块
文件夹agent/
- main.py Bedrock AgentCore Runtime 的代理入口点
- agent.py 定义示例代理和工具
- agentcore_mcp_client.py 与 MCP 服务器交互的客户端工具
- Dockerfile 部署到 AgentCore Runtime 的 Docker 镜像定义
文件夹common/constructs/
文件夹src
文件夹core/agent-core/
- runtime.ts 部署到 AgentCore Runtime 的通用构造体
文件夹app/agents/story-agent/
- story-agent.ts 将故事代理部署到 AgentCore Runtime 的构造体
查看部分关键文件:
from contextlib import contextmanager
from strands import Agent, toolfrom strands_tools import current_time
# 定义自定义工具@tooldef add(a: int, b: int) -> int: return a + b
@contextmanagerdef get_agent(session_id: str): yield Agent( system_prompt="""你是加法巫师。使用 'add' 工具处理加法任务。将工具称为你的 '法术书'。""", tools=[add, current_time], )创建了一个示例 Strands 代理并定义了加法工具。
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
@app.entrypointasync def invoke(payload, context): """代理调用的处理程序""" prompt = payload.get( "prompt", "输入中未找到提示,请引导用户创建包含 prompt 键的 json 有效载荷" )
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()这是代理的入口点,使用 Amazon Bedrock AgentCore SDK 配置。利用 Strands 的流式支持,在事件发生时将事件流式传输回客户端。
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, }); }}该配置使用 CDK 的 DockerImageAsset 将代理 Docker 镜像上传到 ECR,并通过 AgentCore Runtime 托管。
您可能注意到额外的 Dockerfile,它引用了 story 项目的 Docker 镜像,允许我们将 Dockerfile 与代理源代码放在一起。
任务 4:设置库存工具
Section titled “任务 4:设置库存工具”库存:TypeScript 项目
Section titled “库存:TypeScript 项目”创建 MCP 服务器,为故事代理提供管理玩家库存的工具。
首先创建 TypeScript 项目:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#project - 填写必需参数
- name: inventory
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run这将创建一个空的 TypeScript 项目。
ts#project 更新文件
ts#project 生成器生成这些文件:
文件夹packages/
文件夹inventory/
文件夹src/
- index.ts 包含示例函数的入口点
- project.json 项目配置
- eslint.config.mjs 代码检查配置
- vite.config.ts 测试配置
- tsconfig.json 项目基础 TypeScript 配置
- tsconfig.lib.json 针对编译和打包的 TypeScript 配置
- tsconfig.spec.json 测试的 TypeScript 配置
- tsconfig.base.json 更新以配置其他项目引用该项目的别名
库存:MCP 服务器
Section titled “库存:MCP 服务器”接下来为 TypeScript 项目添加 MCP 服务器:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#mcp-server - 填写必需参数
- project: inventory
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run这将添加 MCP 服务器。
ts#mcp-server 更新文件
ts#mcp-server 生成器生成这些文件:
文件夹packages/
文件夹inventory/
文件夹src/mcp-server/
- server.ts 创建 MCP 服务器
文件夹tools/
- add.ts 示例工具
文件夹resources/
- sample-guidance.ts 示例资源
- stdio.ts 使用 STDIO 传输的 MCP 入口点
- http.ts 使用可流式 HTTP 传输的 MCP 入口点
- Dockerfile 构建部署到 AgentCore Runtime 的镜像
- rolldown.config.ts 配置 MCP 服务器打包以部署到 AgentCore
文件夹common/constructs/
文件夹src
文件夹app/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts 将库存 MCP 服务器部署到 AgentCore Runtime 的构造体
任务 5:创建用户界面 (UI)
Section titled “任务 5:创建用户界面 (UI)”在此任务中,我们将创建与游戏交互的 UI。
游戏 UI:网站
Section titled “游戏 UI:网站”要创建 UI,通过以下步骤创建名为 GameUI 的网站:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website - 填写必需参数
- name: GameUI
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件。
ts#react-website 更新文件
ts#react-website 生成器生成这些文件。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定 CDK 构造体
文件夹static-websites/
- game-ui.ts 创建游戏 UI 的 CDK 构造体
文件夹core/
- static-website.ts 通用静态网站构造体
文件夹game-ui/
文件夹public/
- …
文件夹src/
文件夹components/
文件夹AppLayout/
- index.ts 整体页面布局:页头、页脚、侧边栏等
- navitems.ts 侧边栏导航项
文件夹hooks/
- useAppLayout.tsx 动态设置通知、页面样式等
文件夹routes/ @tanstack/react-router 基于文件的路由
- index.tsx 根 ’/’ 页面重定向到 ‘/welcome’
- __root.tsx 所有页面使用此组件作为基础
文件夹welcome/
- index.tsx
- config.ts
- main.tsx React 入口点
- routeTree.gen.ts 由 @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, ), ), }); }}这是定义 GameUI 的 CDK 构造体。已配置指向 Vite 构建产出的文件路径,这意味着在 build 时,打包发生在 game-ui 项目的构建目标中,其输出在此处使用。
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 });
// 为类型安全注册路由实例declare 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>, );这是挂载 React 的入口点。配置了 @tanstack/react-router 的文件路由,只要开发服务器运行,在 routes 文件夹中创建文件时,@tanstack/react-router 会自动生成样板文件并更新 routeTree.gen.ts 文件。该文件以类型安全的方式维护所有路由,使用 <Link> 时 to 选项仅显示有效路由。
更多信息请参考 @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>欢迎来到新的 Cloudscape 网站!</Container> </SpaceBetween> </ContentLayout> );}导航到 /welcome 路由时将渲染该组件。只要开发服务器运行,@tanstack/react-router 会在创建/移动此文件时管理 Route。
游戏 UI:身份验证
Section titled “游戏 UI:身份验证”通过以下步骤配置游戏 UI 要求通过 Amazon Cognito 进行身份验证:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website#auth - 填写必需参数
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件或变更。
ts#react-website#auth 更新文件
ts#react-website#auth 生成器更新/生成这些文件。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- user-identity.ts 创建用户/身份池的 CDK 构造体
文件夹types/
文件夹src/
- runtime-config.ts 更新添加 cognitoProps
文件夹game-ui/
文件夹src/
文件夹components/
文件夹AppLayout/
- index.tsx 在页头添加已登录用户/注销功能
文件夹CognitoAuth/
- index.ts 管理 Cognito 登录
文件夹RuntimeConfig/
- index.tsx 获取
runtime-config.json并通过上下文提供给子组件
- index.tsx 获取
文件夹hooks/
- useRuntimeConfig.tsx
- main.tsx 更新添加 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 });// 为类型安全注册路由实例declare 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>, );通过 AST 转换在 main.tsx 文件中添加了 RuntimeConfigProvider 和 CognitoAuth 组件。这使得 CognitoAuth 组件可以通过获取包含 Cognito 连接配置的 runtime-config.json 来与 Amazon Cognito 进行身份验证,以便向正确的目标发起后端调用。
游戏 UI:连接游戏 API
Section titled “游戏 UI:连接游戏 API”配置游戏 UI 连接之前创建的游戏 API。
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - api-connection - 填写必需参数
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件或变更。
UI -> tRPC api-connection 更新文件
api-connection 生成器生成/更新这些文件。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹game-ui/
文件夹src/
文件夹components/
- GameApiClientProvider.tsx 设置 GameAPI 客户端
文件夹hooks/
- useGameApi.tsx 调用 GameApi 的钩子
- main.tsx 注入 trpc 客户端提供者
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;该钩子使用 tRPC 最新的 React Query 集成,允许用户直接与 @tanstack/react-query 交互而无需额外抽象层。有关如何调用 tRPC API 的示例,请参考 使用 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 });// 为类型安全注册路由实例declare 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>, );通过 AST 转换更新了 main.tsx 文件以注入 tRPC 提供者。
游戏 UI:基础设施
Section titled “游戏 UI:基础设施”创建 CDK 基础设施的最终子项目。
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#infra - 填写必需参数
- name: infra
- 点击
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-interactive您还可以执行试运行以查看哪些文件会被更改
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-run您将看到文件树中出现了新文件或变更。
ts#infra 更新文件
ts#infra 生成器生成/更新这些文件。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- checkov.ts
- index.ts
文件夹infra
文件夹src/
文件夹stages/
- application-stage.ts 定义 CDK 堆栈
文件夹stacks/
- application-stack.ts 定义 CDK 资源
- index.ts
- main.ts 定义所有阶段的入口点
- cdk.json
- project.json
- …
- package.json
- tsconfig.json 添加引用
- tsconfig.base.json 添加别名
import { ApplicationStage } from './stacks/application-stage.js';import { App } from ':dungeon-adventure/common-constructs';
const app = new App();
// 部署自己的沙盒环境(假设已配置 CLI 凭证)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();这是 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);
// 在此定义堆栈代码 }}实例化 CDK 构造体以构建地牢冒险游戏。
任务 6:更新基础设施
Section titled “任务 6:更新基础设施”更新 packages/infra/src/stacks/application-stack.ts 以实例化部分已生成的构造体:
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'); }}任务 7:构建代码
Section titled “任务 7:构建代码”Nx 命令
单目标 vs 多目标
Section titled “单目标 vs 多目标”run-many 命令将在多个列出的子项目上运行目标(--all 将针对所有项目)。这确保依赖项按正确顺序执行。
也可以通过直接在项目上运行目标来触发构建(或任何其他任务)。例如,要构建 @dungeon-adventure/infra 项目,运行以下命令:
pnpm nx run @dungeon-adventure/infra:buildyarn nx run @dungeon-adventure/infra:buildnpx nx run @dungeon-adventure/infra:buildbunx nx run @dungeon-adventure/infra:build也可以省略作用域,使用 Nx 简写语法:
pnpm nx build infrayarn nx build infranpx nx build infrabunx nx build infra可视化依赖关系
Section titled “可视化依赖关系”要可视化依赖关系,运行:
pnpm nx graphyarn nx graphnpx nx graphbunx nx graph
Nx 依赖缓存以重用先前构建产物加速开发。需要配置才能正常工作,有时可能需要不使用缓存进行构建。为此,只需在命令后添加 --skip-nx-cache 参数。例如:
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-cache如需清除缓存(存储在 .nx 文件夹),可运行:
pnpm nx resetyarn nx resetnpx nx resetbunx nx reset使用命令行运行:
pnpm nx run-many --target build --allyarn nx run-many --target build --allnpx nx run-many --target build --allbunx nx run-many --target build --all您将看到以下提示:
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该消息表示 NX 检测到可自动更新的文件。此处指未设置项目引用的 tsconfig.json 文件。
选择 Yes, sync the changes and run the tasks 选项继续。您会注意到所有 IDE 相关导入错误自动解决,因为同步生成器会自动添加缺失的 TypeScript 引用!
所有构建产物现在位于 monorepo 根目录的 dist/ 文件夹中。这是使用 @aws/nx-plugin 生成项目的标准做法,避免生成文件污染文件树。如需清理文件,删除 dist/ 文件夹即可,无需担心生成文件散布在文件树各处。
恭喜!您已创建开始实现 AI 地牢冒险游戏核心所需的所有子项目。🎉🎉🎉