Jeu de Donjon IA
Module 1 : Configuration du monorepo
Nous allons commencer par créer un nouveau monorepo. Depuis le répertoire de votre choix, exécutez la commande suivante :
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --ci=skip
Cela configurera un monorepo NX dans le répertoire dungeon-adventure
que vous pourrez ensuite ouvrir dans vscode. Il devrait ressembler à ceci :
Répertoire.nx/
- …
Répertoire.vscode/
- …
Répertoirenode_modules/
- …
Répertoirepackages/ c’est ici que résideront vos sous-projets
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json configure les paramètres par défaut du CLI Nx et du monorepo
- package.json toutes les dépendances Node sont définies ici
- pnpm-lock.yaml ou bun.lock, yarn.lock, package-lock.json selon le gestionnaire de paquets
- pnpm-workspace.yaml si vous utilisez pnpm
- README.md
- tsconfig.base.json étendu par tous les sous-projets basés sur Node
- tsconfig.json
Maintenant, nous sommes prêts à commencer la création de nos différents sous-projets en utilisant le @aws/nx-plugin
.
API de jeu
Commençons par créer notre API de jeu. Pour cela, créons une API tRPC appelée GameApi
en suivant les étapes ci-dessous :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#trpc-api
- Remplissez les paramètres requis
- name: GameApi
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
Vous devriez voir apparaître de nouveaux fichiers dans votre arborescence.
Fichiers mis à jour par ts#trpc-api
Voici la liste des fichiers générés par le générateur ts#trpc-api
. Nous allons examiner certains fichiers clés mis en évidence dans l’arborescence :
Répertoirepackages/
Répertoirecommon/
Répertoireconstructs/
Répertoiresrc/
Répertoireapp/ constructs CDK spécifiques à l’application
Répertoireapis/
- game-api.ts construct CDK pour créer votre API tRPC
- index.ts
- …
- index.ts
Répertoirecore/ constructs CDK génériques
Répertoireapi/
- rest-api.ts construct CDK de base pour une API Gateway Rest
- trpc-utils.ts utilitaires pour les constructs CDK d’API tRPC
- utils.ts utilitaires pour les constructs d’API
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Répertoiretypes/ types partagés
Répertoiresrc/
- index.ts
- runtime-config.ts définition d’interface utilisée par CDK et le site web
- project.json
- …
Répertoiregame-api/
Répertoirebackend/ code d’implémentation tRPC
Répertoiresrc/
Répertoireclient/ client vanilla typiquement utilisé pour les appels machine à machine
- index.ts
- sigv4.ts
Répertoiremiddleware/ instrumentation Powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Répertoireprocedures/ implémentations spécifiques des procédures/routes de l’API
- echo.ts
- index.ts
- init.ts configure le contexte et les middlewares
- local-server.ts utilisé pour exécuter le serveur tRPC localement
- router.ts point d’entrée du gestionnaire Lambda définissant toutes les procédures
- project.json
- …
Répertoireschema/
Répertoiresrc/
Répertoireprocedures/
- echo.ts
- index.ts
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
Examinons quelques fichiers clés :
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;
Le routeur définit le point d’entrée de votre API tRPC et est l’endroit où vous déclarez toutes vos méthodes d’API. Comme visible ci-dessus, nous avons une méthode echo
dont l’implémentation se trouve dans ./procedures/echo.ts
.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from ':dungeon-adventure/game-api-schema';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Ce fichier implémente la méthode echo
et est fortement typé en déclarant ses structures de données d’entrée et de sortie. Ces définitions sont importées depuis le projet :dungeon-adventure/game-api-schema
, qui est un alias pour le projet de schéma.
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>;
Toutes les définitions de schéma tRPC utilisent Zod et sont exportées comme types TypeScript via la syntaxe 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';
// Type union pour tous les noms d'opérations de l'APItype Operations = Procedures<AppRouter>;
/** * Propriétés pour créer un construct GameApi * * @template TIntegrations - Map des noms d'opérations vers leurs intégrations */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * Map des noms d'opérations vers leurs intégrations API Gateway */ integrations: TIntegrations;}
/** * Un construct CDK qui crée et configure une API Gateway REST API * spécifiquement pour GameApi. * @template TIntegrations - Map des noms d'opérations vers leurs intégrations */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * Crée des intégrations par défaut pour toutes les opérations, implémentant chaque opération * comme une fonction Lambda individuelle. * * @param scope - Le scope du construct CDK * @returns Un IntegrationBuilder avec des intégrations Lambda par défaut */ 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/backend/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: [ // Nous autorisons ici toutes les credentials AWS du compte de déploiement à appeler l'API. // Un accès fin machine à machine peut être défini ici avec des principaux spécifiques (rôles ou utilisateurs) // et des ressources (chemins d'API autorisés) si nécessaire. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // Autorise OPTIONS pour les prérequêtes CORS non authentifiées new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
Ce construct CDK définit notre GameApi. La méthode defaultIntegrations
crée automatiquement une fonction Lambda pour chaque procédure de l’API tRPC, pointant vers l’implémentation bundle. Ainsi, lors de cdk synth
, le bundling n’est pas effectué (contrairement à NodeJsFunction) car il est déjà réalisé lors du build du projet backend.
API d’histoire
Créons maintenant notre API d’histoire. Pour cela, créons une API FastAPI appelée StoryApi
en suivant les étapes :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - py#fast-api
- Remplissez les paramètres requis
- name: StoryApi
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
Vous devriez voir apparaître de nouveaux fichiers dans votre arborescence.
Fichiers mis à jour par py#fast-api
Voici la liste des fichiers générés par le générateur py#fast-api
:
Répertoire.venv/ environnement virtuel unique pour le monorepo
- …
Répertoirepackages/
Répertoirecommon/
Répertoireconstructs/
Répertoiresrc/
Répertoireapp/ constructs CDK spécifiques à l’application
Répertoireapis/
- story-api.ts construct CDK pour créer votre Fast API
- index.ts mis à jour pour exporter le nouveau story-api
- project.json mis à jour pour ajouter une dépendance de build sur story_api
Répertoiretypes/ types partagés
Répertoiresrc/
- runtime-config.ts mis à jour pour ajouter StoryApi
Répertoirestory_api/
Répertoirestory_api/ module Python
- init.py configure Powertools, FastAPI et les middlewares
- main.py point d’entrée du Lambda contenant toutes les routes
Répertoiretests/
- …
- .python-version
- project.json
- pyproject.toml
- project.json
- .python-version version Python figée par uv
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { OPERATION_DETAILS, Operations,} from '../../generated/story-api/metadata.gen.js';
/** * Propriétés pour créer un construct StoryApi * * @template TIntegrations - Map des noms d'opérations vers leurs intégrations */export interface StoryApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * Map des noms d'opérations vers leurs intégrations API Gateway */ integrations: TIntegrations;}
/** * Un construct CDK qui crée et configure une API Gateway REST API * spécifiquement pour StoryApi. * @template TIntegrations - Map des noms d'opérations vers leurs intégrations */export class StoryApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * Crée des intégrations par défaut pour toutes les opérations, implémentant chaque opération * comme une fonction Lambda individuelle. * * @param scope - Le scope du construct CDK * @returns Un IntegrationBuilder avec des intégrations Lambda par défaut */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: OPERATION_DETAILS, defaultIntegrationOptions: { runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `StoryApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: StoryApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'StoryApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // Autorise les credentials AWS du compte de déploiement à appeler l'API new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // Autorise OPTIONS pour les prérequêtes CORS new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: OPERATION_DETAILS, ...props, }); }}
Ce construct CDK définit notre StoryApi. La méthode defaultIntegrations
crée automatiquement une fonction Lambda pour chaque opération de l’API FastAPI, pointant vers l’implémentation bundle. Ainsi, lors de cdk synth
, le bundling n’est pas effectué (contrairement à PythonFunction) car il est déjà réalisé lors du build du projet backend.
export type ApiUrl = string;// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interfaceexport interface IRuntimeConfig { apis: { GameApi: ApiUrl; StoryApi: ApiUrl; };}
Exemple d’une transformation AST effectuée par le générateur, préservant le code existant et ajoutant StoryApi
à la définition IRuntimeConfig
. Cela garantit la sécurité des types lors de l’utilisation par le frontend.
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
C’est ici que vous définirez toutes vos méthodes d’API. L’exemple montre une méthode read_root
mappée sur la route GET /
. Pydantic peut être utilisé pour valider les entrées/sorties.
Interface de jeu : Site web
Créons maintenant l’interface utilisateur pour interagir avec le jeu. Pour cela, générons un site web GameUI
:
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#cloudscape-website
- Remplissez les paramètres requis
- name: GameUI
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
Vous devriez voir apparaître de nouveaux fichiers dans votre arborescence.
Fichiers mis à jour par ts#cloudscape-website
Voici les fichiers clés générés par le générateur ts#cloudscape-website
:
Répertoirepackages/
Répertoirecommon/
Répertoireconstructs/
Répertoiresrc/
Répertoireapp/ constructs CDK spécifiques à l’application
Répertoirestatic-websites/
- game-ui.ts construct CDK pour votre Game UI
Répertoirecore/
- static-website.ts construct générique de site web statique
Répertoiregame-ui/
Répertoirepublic/
- …
Répertoiresrc/
Répertoirecomponents/
RépertoireAppLayout/
- index.ts mise en page globale : en-tête, pied de page, sidebar, etc
- navitems.ts éléments de navigation de la sidebar
Répertoirehooks/
- useAppLayout.tsx permet de configurer dynamiquement notifications, style de page, etc
Répertoireroutes/ routes basées sur @tanstack/react-router
- index.tsx page racine ’/’ redirige vers ‘/welcome’
- __root.tsx composant de base pour toutes les pages
Répertoirewelcome/
- index.tsx
- config.ts
- main.tsx point d’entrée React
- routeTree.gen.ts généré automatiquement par @tanstack/react-router
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
Ce construct CDK définit notre GameUI. Le chemin pointe vers le bundle généré par Vite, donc le bundling est effectué lors du build du projet.
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 });
// Enregistrement du routeur pour la sécurité des typesdeclare 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>, );
Point d’entrée React configurant le routage basé sur les fichiers. Les routes sont gérées de manière type-safe. Voir la doc @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> );}
Composant affiché sur la route /welcome
. Le routage est géré automatiquement.
Interface de jeu : Authentification
Configurons l’authentification via Amazon Cognito pour notre Game UI :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#cloudscape-website#auth
- Remplissez les paramètres requis
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
Des fichiers ont été ajoutés/modifiés dans votre arborescence.
Fichiers mis à jour par ts#cloudscape-website#auth
Répertoirepackages/
Répertoirecommon/
Répertoireconstructs/
Répertoiresrc/
Répertoirecore/
- user-identity.ts construct CDK pour les pools d’utilisateurs/identités
Répertoiretypes/
Répertoiresrc/
- runtime-config.ts mis à jour avec cognitoProps
Répertoiregame-ui/
Répertoiresrc/
Répertoirecomponents/
RépertoireAppLayout/
- index.tsx ajoute l’utilisateur connecté/déconnexion dans l’en-tête
RépertoireCognitoAuth/
- index.ts gère la connexion à Cognito
RépertoireRuntimeConfig/
- index.tsx récupère
runtime-config.json
et le fournit via un contexte
- index.tsx récupère
Répertoirehooks/
- useRuntimeConfig.tsx
- main.tsx Mis à jour pour ajouter 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 });// Enregistrement du routeur pour la sécurité des typesdeclare 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>, );
Les composants RuntimeConfigProvider
et CognitoAuth
ont été ajoutés via une transformation AST. CognitoAuth
utilise runtime-config.json
pour l’authentification.
Interface de jeu : Connexion à l’API d’histoire
Connectons notre Game UI à l’API StoryApi :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - api-connection
- Remplissez les paramètres requis
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
Des fichiers ont été ajoutés/modifiés.
Fichiers mis à jour par la connexion UI -> FastAPI
Répertoirepackages/
Répertoiregame-ui/
Répertoiresrc/
Répertoirehooks/
- useSigV4.tsx utilisé par StoryApi pour signer les requêtes
- useStoryApiClient.tsx hook pour construire un client StoryApi
- useStoryApi.tsx hook pour interagir avec StoryApi via TanStack Query
Répertoirecomponents/
- QueryClientProvider.tsx fournisseur du client TanStack Query
- StoryApiProvider.tsx fournisseur du hook StoryApi
- main.tsx injection des fournisseurs
- .gitignore ignore les fichiers clients générés
- project.json mis à jour pour générer les hooks OpenAPI
- …
Répertoirestory_api/
Répertoirescripts/
- generate_open_api.py
- project.json mis à jour pour générer openapi.json
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.apis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
Ce hook permet des appels authentifiés à StoryApi. Le client est généré lors du build. Voir le guide React vers FastAPI.
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
export default StoryApiProvider;
Ce fournisseur utilise useStoryApiClient
et instancie StoryApiOptionsProxy
pour les hooks TanStack Query.
Interface de jeu : Connexion à l’API de jeu
Connectons maintenant notre Game UI à l’API GameApi :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - api-connection
- Remplissez les paramètres requis
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
Des fichiers ont été ajoutés/modifiés.
Fichiers mis à jour par la connexion UI -> tRPC
Répertoirepackages/
Répertoiregame-ui/
Répertoiresrc/
Répertoirecomponents/
RépertoireTrpcClients/
- index.tsx
- TrpcApis.tsx toutes les APIs tRPC configurées
- TrpcClientProviders.tsx fournisseurs de clients tRPC
- TrpcProvider.tsx
Répertoirehooks/
- useGameApi.tsx hooks pour appeler GameApi
- main.tsx injection des fournisseurs tRPC
- package.json
import { TrpcApis } from '../components/TrpcClients';
export const useGameApi = () => TrpcApis.GameApi.useTRPC();
Ce hook utilise l’intégration React Query de tRPC. Voir le guide d’utilisation des hooks tRPC.
import TrpcClientProviders from './components/TrpcClients';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 });// Enregistrement du routeur pour la sécurité des typesdeclare 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> <TrpcClientProviders> <RouterProvider router={router} /> </TrpcClientProviders> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
Les fournisseurs tRPC ont été injectés via transformation AST.
Infrastructure de l’interface de jeu
Créons le projet d’infrastructure CDK :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#infra
- Remplissez les paramètres requis
- name: infra
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
Des fichiers ont été ajoutés/modifiés.
Fichiers mis à jour par ts#infra
Répertoirepackages/
Répertoirecommon/
Répertoireconstructs/
Répertoiresrc/
Répertoirecore/
Répertoirecfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
Répertoireinfra
Répertoiresrc/
Répertoirestacks/
- application-stack.ts ressources CDK définies ici
- index.ts
- main.ts point d’entrée des stacks
- cdk.json
- project.json
- …
- package.json
- tsconfig.json références ajoutées
- tsconfig.base.json alias ajouté
import { ApplicationStack } from './stacks/application-stack.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// Déploie un environnement sandbox (utilise les credentials CLI)new ApplicationStack(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, crossRegionReferences: true,});
app.synth();
Point d’entrée CDK utilisant cfn-guard
pour la validation.
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);
// Le code définissant votre stack va ici }}
C’est ici que nous instancierons nos constructs CDK.
Mise à jour de l’infrastructure
Modifions packages/infra/src/stacks/application-stack.ts
pour instancier nos constructs :
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), }); const storyApi = new StoryApi(this, 'StoryApi', { integrations: StoryApi.defaultIntegrations(this).build(), });
// grant our authenticated role access to invoke our APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
Nous utilisons les intégrations par défaut pour nos APIs, chaque opération étant mappée à une fonction Lambda distincte.
Construction du code
Commandes Nx
Cibles uniques vs multiples
La commande run-many
exécute une cible sur plusieurs sous-projets (--all
pour tous). Les dépendances sont respectées.
Pour une cible unique :
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
Visualisation des dépendances
Visualisez les dépendances avec :
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Cache
Nx utilise le cache pour accélérer les builds. Utilisez --skip-nx-cache
pour ignorer le cache :
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
Effacer le cache :
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
Vous serez invité à :
NX The workspace is out of sync
[@nx/js:typescript-sync]: Certains fichiers de configuration TypeScript manquent des références de projet ou ont des références obsolètes.
Cela entraînera une erreur en CI.
? Souhaitez-vous synchroniser les changements identifiés pour mettre à jour votre espace de travail ? …Oui, synchroniser les changements et exécuter les tâchesNon, exécuter les tâches sans synchroniser
Sélectionnez Oui pour résoudre les erreurs d’import dans l’IDE.
Les artefacts de build sont dans dist/
. Vous pouvez supprimer ce dossier pour nettoyer.
Félicitations ! Vous avez créé tous les sous-projets nécessaires pour développer le cœur de notre jeu Dunegeon Adventure. 🎉🎉🎉