모노레포 설정
작업 1: 모노레포 생성
섹션 제목: “작업 1: 모노레포 생성”새로운 모노레포를 생성하려면 원하는 디렉토리에서 다음 명령어를 실행하세요:
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 --aiAgents이 명령은 dungeon-adventure 디렉토리 내에 NX 모노레포를 설정합니다. VSCode에서 디렉토리를 열면 다음과 같은 파일 구조를 볼 수 있습니다:
디렉터리.nx/
- …
디렉터리.vscode/
- …
디렉터리node_modules/
- …
디렉터리packages/ 하위 프로젝트가 위치할 폴더
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json Nx CLI 및 모노레포 기본값 구성
- package.json 모든 노드 의존성 정의
- pnpm-lock.yaml 또는 bun.lock, yarn.lock, package-lock.json (패키지 매니저에 따라 다름)
- pnpm-workspace.yaml pnpm 사용 시
- README.md
- tsconfig.base.json 모든 노드 기반 하위 프로젝트가 상속할 기본 설정
- tsconfig.json
- aws-nx-plugin.config.mts AWS용 Nx 플러그인 구성
이제 @aws/nx-plugin을 사용하여 다양한 하위 프로젝트 생성을 시작할 수 있습니다.
작업 2: Game API 생성
섹션 제목: “작업 2: Game API 생성”먼저 Game 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/ 기계 간 통신용 바닐라 클라이언트
- 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 모든 프로시저를 정의하는 람다 핸들러 진입점
- 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;}
/** * GameApi 전용 AWS API Gateway REST API 생성 및 구성 CDK 구성 요소 * @template TIntegrations - 작업 이름과 통합 매핑 */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 모든 작업에 대한 기본 람다 함수 통합 생성 * * @param scope - CDK 구성 요소 범위 * @returns 기본 람다 통합이 포함된 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, }); }}이 CDK 구성 요소는 GameApi를 정의합니다. defaultIntegrations 메서드는 tRPC API의 각 프로시저에 대해 자동으로 Lambda 함수를 생성하며, 번들된 API 구현을 가리킵니다. 이는 cdk synth 시점에 번들링이 발생하지 않음을 의미합니다(NodeJsFunction 사용과 달리). 백엔드 프로젝트의 빌드 대상의 일부로 이미 번들링했기 때문입니다.
작업 3: 스토리 에이전트 생성
섹션 제목: “작업 3: 스토리 에이전트 생성”이제 스토리 에이전트를 생성해 보겠습니다.
스토리 에이전트: Python 프로젝트
섹션 제목: “스토리 에이전트: 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/ 모노레포용 단일 가상 환경
- …
디렉터리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 에이전트
섹션 제목: “스토리 에이전트: 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 런타임용 에이전트 진입점
- agent.py 예제 에이전트 및 도구 정의
- agentcore_mcp_client.py MCP 서버와 상호작용용 유틸리티
- Dockerfile AgentCore 런타임 배포용 Docker 이미지 정의
디렉터리common/constructs/
디렉터리src
디렉터리core/agent-core/
- runtime.ts AgentCore 런타임 배포용 일반 구성 요소
디렉터리app/agents/story-agent/
- story-agent.ts Story 에이전트를 AgentCore 런타임에 배포하는 구성 요소
일부 파일을 자세히 살펴보겠습니다:
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="""You are an addition wizard.Use the 'add' tool for addition tasks.Refer to tools as your 'spellbook'.""", 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", "No prompt found in input, please guide the user " "to create a json payload with prompt key" )
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를 사용한 에이전트의 진입점입니다. 스트리밍 지원을 사용하며 이벤트 발생 시 클라이언트로 스트리밍합니다.
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, }); }}이 코드는 ECR에 에이전트 Docker 이미지를 업로드하고 AgentCore 런타임을 사용하여 호스팅하는 CDK DockerImageAsset을 구성합니다.
추가 Dockerfile이 있는데, 이는 story 프로젝트의 Docker 이미지를 참조하여 Dockerfile과 에이전트 소스 코드를 함께 위치시킬 수 있도록 합니다.
작업 4: 인벤토리 도구 설정
섹션 제목: “작업 4: 인벤토리 도구 설정”인벤토리: TypeScript 프로젝트
섹션 제목: “인벤토리: 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 서버
섹션 제목: “인벤토리: 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 Streamable HTTP 전송을 사용한 MCP 진입점
- Dockerfile AgentCore 런타임 배포용 이미지 빌드
- rolldown.config.ts AgentCore 배포용 MCP 서버 번들 구성
디렉터리common/constructs/
디렉터리src
디렉터리app/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts 인벤토리 MCP 서버를 AgentCore 런타임에 배포하는 구성 요소
작업 5: 사용자 인터페이스(UI) 생성
섹션 제목: “작업 5: 사용자 인터페이스(UI) 생성”이 작업에서는 게임과 상호작용할 수 있는 UI를 생성합니다.
게임 UI: 웹사이트
섹션 제목: “게임 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 Game 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, ), ), }); }}이 CDK 구성 요소는 GameUI를 정의합니다. Vite 기반 UI의 번들 경로가 이미 구성되어 있습니다. 이는 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>Welcome to your new Cloudscape website!</Container> </SpaceBetween> </ContentLayout> );}/welcome 라우트로 이동할 때 컴포넌트가 렌더링됩니다. @tanstack/react-router는 파일 생성/이동 시 Route를 관리합니다(개발 서버가 실행 중인 경우).
게임 UI: 인증
섹션 제목: “게임 UI: 인증”Amazon Cognito를 통해 인증된 액세스가 필요한 Game UI를 구성해 보겠습니다. 다음 단계를 따르세요:
- 설치 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 사용자/ID 풀 생성용 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>, );RuntimeConfigProvider 및 CognitoAuth 컴포넌트가 AST 변환을 통해 main.tsx 파일에 추가되었습니다. 이를 통해 CognitoAuth 컴포넌트는 필요한 Cognito 연결 구성을 포함하는 runtime-config.json을 가져와 올바른 대상으로 백엔드 호출을 수행하여 Amazon Cognito로 인증할 수 있습니다.
게임 UI: Game API 연결
섹션 제목: “게임 UI: Game API 연결”이전에 생성한 Game API에 Game UI를 연결하도록 구성해 보겠습니다.
- 설치 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>, );main.tsx 파일이 AST 변환을 통해 tRPC 프로바이더를 주입하도록 업데이트되었습니다.
게임 UI: 인프라스트럭처
섹션 제목: “게임 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: 인프라스트럭처 업데이트
섹션 제목: “작업 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: 코드 빌드
섹션 제목: “작업 7: 코드 빌드”Nx 명령어
단일 vs 다중 대상
섹션 제목: “단일 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종속성 시각화
섹션 제목: “종속성 시각화”종속성을 시각화하려면 다음을 실행합니다:
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가 자동으로 업데이트할 수 있는 일부 파일을 감지했음을 나타냅니다. 이 경우 참조 프로젝트에 대한 TypeScript 참조가 설정되지 않은 tsconfig.json 파일을 가리킵니다.
Yes, sync the changes and run the tasks 옵션을 선택하여 진행합니다. 동기화 생성기가 누락된 TypeScript 참조를 자동으로 추가하므로 모든 IDE 관련 가져오기 오류가 자동으로 해결됩니다!
모든 빌드 아티팩트는 이제 모노레포 루트의 dist/ 폴더 내에서 사용할 수 있습니다. 이는 @aws/nx-plugin으로 생성된 프로젝트의 표준 관행으로, 생성된 파일이 파일 트리를 어지럽히지 않도록 합니다. 파일을 정리하려면 생성된 파일이 파일 트리 전체에 흩어져 있을 걱정 없이 dist/ 폴더를 삭제하면 됩니다.
축하합니다! AI 던전 어드벤처 게임의 핵심을 구현하기 위해 필요한 모든 하위 프로젝트를 성공적으로 생성했습니다. 🎉🎉🎉