@aws/nx-plugin-mcp 1.0.0-rc.9 → 1.0.0-rc.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aws-nx-mcp.js +13439 -11631
- package/docs/get_started/building-with-ai.mdx +116 -0
- package/docs/get_started/concepts.mdx +67 -0
- package/docs/get_started/existing-project.mdx +180 -0
- package/docs/get_started/graph-builder.mdx +39 -0
- package/docs/get_started/quick-start.mdx +277 -0
- package/docs/get_started/tutorials/contribute-generator.mdx +408 -0
- package/docs/get_started/tutorials/dungeon-game/1.mdx +1578 -0
- package/docs/get_started/tutorials/dungeon-game/2.mdx +245 -0
- package/docs/get_started/tutorials/dungeon-game/3.mdx +66 -0
- package/docs/get_started/tutorials/dungeon-game/4.mdx +164 -0
- package/docs/get_started/tutorials/dungeon-game/overview.mdx +145 -0
- package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +41 -0
- package/docs/get_started/upgrading.mdx +157 -0
- package/docs/guides/agentcore-gateway.mdx +490 -0
- package/docs/guides/agentcore-harness.mdx +312 -0
- package/docs/guides/astro-docs.mdx +8 -0
- package/docs/guides/connection/agentcore-gateway-agent.mdx +222 -0
- package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
- package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
- package/docs/guides/connection/py-agent-a2a.mdx +48 -16
- package/docs/guides/connection/py-agent-dynamodb.mdx +115 -0
- package/docs/guides/connection/py-agent-gateway.mdx +182 -0
- package/docs/guides/connection/py-agent-mcp.mdx +43 -14
- package/docs/guides/connection/py-agent-rdb.mdx +169 -0
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
- package/docs/guides/connection/py-fast-api-rdb.mdx +175 -0
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +115 -0
- package/docs/guides/connection/py-mcp-server-rdb.mdx +178 -0
- package/docs/guides/connection/react-agentcore-gateway.mdx +112 -0
- package/docs/guides/connection/react-agui.mdx +33 -14
- package/docs/guides/connection/react-fastapi.mdx +39 -3
- package/docs/guides/connection/react-py-agent.mdx +10 -16
- package/docs/guides/connection/react-smithy.mdx +4 -4
- package/docs/guides/connection/react-trpc.mdx +1 -1
- package/docs/guides/connection/react-ts-agent.mdx +9 -9
- package/docs/guides/connection/smithy-dynamodb.mdx +5 -5
- package/docs/guides/connection/smithy-rdb.mdx +9 -9
- package/docs/guides/connection/trpc-dynamodb.mdx +4 -4
- package/docs/guides/connection/trpc-rdb.mdx +5 -5
- package/docs/guides/connection/ts-agent-a2a.mdx +14 -11
- package/docs/guides/connection/ts-agent-dynamodb.mdx +35 -36
- package/docs/guides/connection/ts-agent-gateway.mdx +147 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +12 -9
- package/docs/guides/connection/ts-agent-rdb.mdx +61 -25
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +35 -36
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +59 -18
- package/docs/guides/connection.mdx +122 -5
- package/docs/guides/docker-bundling.mdx +82 -14
- package/docs/guides/fastapi.mdx +253 -12
- package/docs/guides/local-development.mdx +87 -0
- package/docs/guides/nx-generator.mdx +4 -3
- package/docs/guides/nx-migration.mdx +165 -0
- package/docs/guides/py-agent.mdx +332 -55
- package/docs/guides/py-dynamodb.mdx +476 -0
- package/docs/guides/py-mcp-server.mdx +57 -2
- package/docs/guides/py-rdb.mdx +269 -0
- package/docs/guides/python-lambda-function.mdx +1 -1
- package/docs/guides/python-project.mdx +31 -0
- package/docs/guides/react-website-auth.mdx +104 -5
- package/docs/guides/react-website.mdx +409 -95
- package/docs/guides/runtime-config.mdx +17 -4
- package/docs/guides/security.mdx +75 -0
- package/docs/guides/smithy-project.mdx +167 -0
- package/docs/guides/terraform-project.mdx +119 -8
- package/docs/guides/trpc.mdx +55 -14
- package/docs/guides/ts-agent.mdx +207 -23
- package/docs/guides/ts-dcr-proxy.mdx +569 -0
- package/docs/guides/ts-dynamodb.mdx +66 -242
- package/docs/guides/ts-lambda-function.mdx +1 -1
- package/docs/guides/ts-mcp-server.mdx +111 -29
- package/docs/guides/ts-nx-plugin.mdx +4 -4
- package/docs/guides/ts-rdb.mdx +116 -466
- package/docs/guides/ts-smithy-api.mdx +260 -16
- package/docs/guides/typescript-infrastructure.mdx +79 -25
- package/docs/guides/typescript-project.mdx +157 -24
- package/docs/guides/workspace.mdx +31 -2
- package/docs/snippets/agent/architecture.mdx +1 -1
- package/docs/snippets/agent/bedrock-deployment.mdx +9 -5
- package/docs/snippets/agent/runtime-arn.mdx +23 -2
- package/docs/snippets/agent/securing-your-agent.mdx +39 -0
- package/docs/snippets/api/access-logging.mdx +38 -0
- package/docs/snippets/api/cors-configuration-cdk-note.mdx +1 -1
- package/docs/snippets/api/cors-configuration-terraform-note.mdx +1 -1
- package/docs/snippets/api/type-safe-api-integrations.mdx +218 -373
- package/docs/snippets/api/waf-configuration.mdx +3 -3
- package/docs/snippets/connection/a2a-infrastructure.mdx +1 -1
- package/docs/snippets/connection/dynamodb-local-development.mdx +2 -2
- package/docs/snippets/connection/lambda-dynamodb-access.mdx +2 -2
- package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
- package/docs/snippets/connection/py-lambda-rdb-ssl-requirements.mdx +7 -0
- package/docs/snippets/connection/rdb-api-infrastructure.mdx +42 -19
- package/docs/snippets/dynamodb/deploying-table.mdx +176 -0
- package/docs/snippets/dynamodb/encryption-options.mdx +168 -0
- package/docs/snippets/dynamodb/gsi-config.mdx +38 -0
- package/docs/snippets/dynamodb/infrastructure.mdx +33 -0
- package/docs/snippets/dynamodb/local-dev-start.mdx +13 -0
- package/docs/snippets/dynamodb/local-dev-windows.mdx +15 -0
- package/docs/snippets/experimental-generator.mdx +8 -0
- package/docs/snippets/lambda-function/deploying-your-function.mdx +3 -3
- package/docs/snippets/mcp/architecture.mdx +1 -1
- package/docs/snippets/mcp/bedrock-deployment.mdx +9 -5
- package/docs/snippets/mcp/config.mdx +3 -2
- package/docs/snippets/mcp/shared-constructs.mdx +4 -5
- package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +7 -5
- package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +29 -10
- package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +4 -4
- package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +5 -111
- package/docs/snippets/prerequisites.mdx +1 -4
- package/docs/snippets/rdb/admin-credentials.mdx +42 -0
- package/docs/snippets/rdb/architecture.mdx +39 -0
- package/docs/snippets/rdb/cluster-instances.mdx +31 -0
- package/docs/snippets/rdb/deletion-protection.mdx +64 -0
- package/docs/snippets/rdb/deploying.mdx +178 -0
- package/docs/snippets/rdb/encryption-key-rotation.mdx +30 -0
- package/docs/snippets/rdb/engine-version.mdx +63 -0
- package/docs/snippets/rdb/infrastructure.mdx +35 -0
- package/docs/snippets/rdb/logging-mysql.mdx +5 -0
- package/docs/snippets/rdb/logging-postgres.mdx +5 -0
- package/docs/snippets/rdb/performance-insights.mdx +36 -0
- package/docs/snippets/rdb/rds-proxy.mdx +50 -0
- package/docs/snippets/rdb/removal-policy.mdx +57 -0
- package/docs/snippets/rdb/serverless-capacity.mdx +32 -0
- package/docs/snippets/recommended-prerequisites.mdx +10 -0
- package/docs/snippets/required-prerequisites.mdx +1 -4
- package/docs/snippets/runtime-config-app-id-note.mdx +8 -0
- package/docs/snippets/shared-constructs.mdx +1 -1
- package/docs/snippets/trivy-image-scan.mdx +37 -0
- package/generators.json +170 -10
- package/package.json +1 -1
- package/src/agentcore-gateway/agent-connection/schema.json +31 -0
- package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
- package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
- package/src/agentcore-gateway/react-connection/schema.json +31 -0
- package/src/agentcore-gateway/schema.json +72 -0
- package/src/agentcore-harness/schema.json +53 -0
- package/src/connection/schema.json +5 -0
- package/src/infra/app/schema.json +5 -0
- package/src/init/schema.json +35 -0
- package/src/internal/test-matrix/schema.json +21 -0
- package/src/license/schema.json +5 -0
- package/src/open-api/json-metadata/schema.json +20 -0
- package/src/preset/schema.json +16 -5
- package/src/py/agent/a2a-connection/schema.json +5 -0
- package/src/py/agent/gateway-connection/schema.json +31 -0
- package/src/py/agent/mcp-connection/schema.json +5 -0
- package/src/py/agent/react-connection/schema.json +5 -0
- package/src/py/agent/schema.json +15 -1
- package/src/py/api/schema.json +5 -0
- package/src/py/dynamodb/agent-connection/schema.json +27 -0
- package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
- package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
- package/src/py/dynamodb/schema.json +76 -0
- package/src/py/fast-api/react/schema.json +5 -0
- package/src/py/fast-api/schema.json +6 -0
- package/src/py/lambda-function/schema.json +6 -1
- package/src/py/mcp-server/schema.json +6 -0
- package/src/py/project/schema.json +6 -0
- package/src/py/rdb/agent-connection/schema.json +27 -0
- package/src/py/rdb/fast-api-connection/schema.json +23 -0
- package/src/py/rdb/mcp-server-connection/schema.json +27 -0
- package/src/py/rdb/schema.json +78 -0
- package/src/smithy/project/schema.json +28 -1
- package/src/smithy/react-connection/schema.json +5 -0
- package/src/smithy/ts/api/schema.json +6 -0
- package/src/terraform/project/schema.json +5 -0
- package/src/trpc/backend/schema.json +6 -0
- package/src/trpc/react/schema.json +5 -0
- package/src/ts/agent/a2a-connection/schema.json +5 -0
- package/src/ts/agent/gateway-connection/schema.json +31 -0
- package/src/ts/agent/mcp-connection/schema.json +5 -0
- package/src/ts/agent/react-connection/schema.json +5 -0
- package/src/ts/agent/schema.json +14 -0
- package/src/ts/api/schema.json +5 -0
- package/src/ts/astro-docs/schema.json +3 -3
- package/src/ts/dcr-proxy/schema.json +44 -0
- package/src/ts/docs/schema.json +3 -3
- package/src/ts/dynamodb/agent-connection/schema.json +5 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.json +5 -0
- package/src/ts/dynamodb/schema.json +26 -2
- package/src/ts/dynamodb/smithy-connection/schema.json +5 -0
- package/src/ts/dynamodb/trpc-connection/schema.json +5 -0
- package/src/ts/lambda-function/schema.json +5 -0
- package/src/ts/lib/schema.json +5 -0
- package/src/ts/mcp-server/schema.json +6 -0
- package/src/ts/nx-generator/schema.json +5 -0
- package/src/ts/nx-migration/schema.json +63 -0
- package/src/ts/nx-plugin/schema.json +5 -0
- package/src/ts/rdb/agent-connection/schema.json +5 -0
- package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
- package/src/ts/rdb/schema.json +7 -1
- package/src/ts/rdb/smithy-connection/schema.json +5 -0
- package/src/ts/rdb/trpc-connection/schema.json +5 -0
- package/src/ts/react-website/app/schema.json +12 -6
- package/src/ts/react-website/cognito-auth/schema.json +5 -0
- package/src/ts/react-website/runtime-config/schema.json +5 -0
- package/src/ts/website/app/schema.json +11 -6
- package/src/ts/website/auth/schema.json +5 -0
- /package/docs/snippets/connection/{lambda-rdb-ssl-requirements.mdx → ts-lambda-rdb-ssl-requirements.mdx} +0 -0
- /package/docs/snippets/connection/{mcp-server-rdb-ssl-requirements.mdx → ts-mcp-server-rdb-ssl-requirements.mdx} +0 -0
|
@@ -15,7 +15,7 @@ A CDK construct is generated for your MCP Server, named based on the `name` you
|
|
|
15
15
|
You can use this CDK construct in a CDK application:
|
|
16
16
|
|
|
17
17
|
```ts {6}
|
|
18
|
-
import { MyProjectMcpServer } from '
|
|
18
|
+
import { MyProjectMcpServer } from '@my-scope/common-constructs';
|
|
19
19
|
|
|
20
20
|
export class ExampleStack extends Stack {
|
|
21
21
|
constructor(scope: Construct, id: string) {
|
|
@@ -64,7 +64,7 @@ By default, your MCP server will be secured using IAM authentication, simply dep
|
|
|
64
64
|
<Infrastructure>
|
|
65
65
|
<Fragment slot="cdk">
|
|
66
66
|
```ts {5}
|
|
67
|
-
import { MyProjectMcpServer } from '
|
|
67
|
+
import { MyProjectMcpServer } from '@my-scope/common-constructs';
|
|
68
68
|
|
|
69
69
|
export class ExampleStack extends Stack {
|
|
70
70
|
constructor(scope: Construct, id: string) {
|
|
@@ -76,7 +76,7 @@ export class ExampleStack extends Stack {
|
|
|
76
76
|
You can grant access to invoke your MCP server on Bedrock AgentCore Runtime using the `grantInvokeAccess` method. For example you may wish for an agent generated with the <Link path="/guides/py-agent">`py#agent`</Link> generator to call your MCP server:
|
|
77
77
|
|
|
78
78
|
```ts {8}
|
|
79
|
-
import { MyProjectAgent, MyProjectMcpServer } from '
|
|
79
|
+
import { MyProjectAgent, MyProjectMcpServer } from '@my-scope/common-constructs';
|
|
80
80
|
|
|
81
81
|
export class ExampleStack extends Stack {
|
|
82
82
|
constructor(scope: Construct, id: string) {
|
|
@@ -126,7 +126,7 @@ When you select `Cognito` authentication, the generator configures the MCP serve
|
|
|
126
126
|
The generated construct accepts an `identity` prop which configures Cognito authentication:
|
|
127
127
|
|
|
128
128
|
```ts {8}
|
|
129
|
-
import { MyProjectMcpServer, UserIdentity } from '
|
|
129
|
+
import { MyProjectMcpServer, UserIdentity } from '@my-scope/common-constructs';
|
|
130
130
|
|
|
131
131
|
export class ExampleStack extends Stack {
|
|
132
132
|
constructor(scope: Construct, id: string) {
|
|
@@ -144,7 +144,7 @@ The `UserIdentity` construct can be generated using the <Link path="/guides/reac
|
|
|
144
144
|
<Fragment slot="terraform">
|
|
145
145
|
The generated module accepts `user_pool_id` and `user_pool_client_ids` variables for Cognito authentication:
|
|
146
146
|
|
|
147
|
-
```terraform {
|
|
147
|
+
```terraform {11-12}
|
|
148
148
|
module "user_identity" {
|
|
149
149
|
source = "../../common/terraform/src/core/user-identity"
|
|
150
150
|
}
|
|
@@ -164,4 +164,8 @@ module "my_project_mcp_server" {
|
|
|
164
164
|
|
|
165
165
|
:::note[Custom OIDC Providers]
|
|
166
166
|
If you require custom JWT authentication with a non-Cognito OIDC provider, you can modify the generated CDK construct or Terraform module for your MCP server directly. Note that the connection generator will only support `IAM` or `Cognito` authentication.
|
|
167
|
+
:::
|
|
168
|
+
|
|
169
|
+
:::caution[Security Best Practices]
|
|
170
|
+
When implementing your MCP server's business logic, review the [Bedrock AgentCore Runtime security best practices](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-security-best-practices.html).
|
|
167
171
|
:::
|
|
@@ -12,9 +12,8 @@ For deploying your MCP Server, the following files are generated:
|
|
|
12
12
|
- packages/common/constructs/src
|
|
13
13
|
- app
|
|
14
14
|
- mcp-servers
|
|
15
|
-
- \<
|
|
16
|
-
- \<
|
|
17
|
-
- Dockerfile Passthrough docker file used by the CDK construct
|
|
15
|
+
- \<mcp-server-name>
|
|
16
|
+
- \<mcp-server-name>.ts CDK construct for deploying your MCP Server
|
|
18
17
|
</FileTree>
|
|
19
18
|
</Fragment>
|
|
20
19
|
<Fragment slot="terraform">
|
|
@@ -22,8 +21,8 @@ For deploying your MCP Server, the following files are generated:
|
|
|
22
21
|
- packages/common/terraform/src
|
|
23
22
|
- app
|
|
24
23
|
- mcp-servers
|
|
25
|
-
- \<
|
|
26
|
-
- \<
|
|
24
|
+
- \<mcp-server-name>
|
|
25
|
+
- \<mcp-server-name>.tf Module for deploying your MCP Server
|
|
27
26
|
- core
|
|
28
27
|
- agent-core
|
|
29
28
|
- runtime.tf Generic module for deploying to Bedrock AgentCore Runtime
|
|
@@ -23,9 +23,9 @@ If you used OpenAPI or TypeSpec as your modelling language, or have handlers imp
|
|
|
23
23
|
|
|
24
24
|
#### Generate a TypeScript Smithy API
|
|
25
25
|
|
|
26
|
-
Run the <Link path="/guides/ts-smithy-api">`ts#
|
|
26
|
+
Run the <Link path="/guides/ts-smithy-api">`ts#api` generator</Link> with `framework` set to `smithy` to set up your api project in `packages/api`:
|
|
27
27
|
|
|
28
|
-
<RunGenerator generator="ts#
|
|
28
|
+
<RunGenerator generator="ts#api" noInteractive requiredParameters={{ name: 'api', framework: 'smithy', namespace: 'com.aws', auth: 'iam' }} />
|
|
29
29
|
|
|
30
30
|
You will notice this generates a `model` project, as well as a `backend` project. The `model` project contains your Smithy model, and `backend` contains your server implementation.
|
|
31
31
|
|
|
@@ -128,12 +128,14 @@ You can consider the `api/backend` project as somewhat equivalent to Type Safe A
|
|
|
128
128
|
|
|
129
129
|
One of the main differences between Type Safe API and the `ts#smithy-api` generator is that handlers are implemented using the [Smithy Server Generator for TypeScript](https://smithy.io/2.0/languages/typescript/ts-ssdk/index.html), rather than Type Safe API's own generated handler wrappers (found in the `api/generated/typescript/runtime` project).
|
|
130
130
|
|
|
131
|
-
The shopping list application's lambda handlers rely on the `@aws-sdk/client-dynamodb` package, so let's install that
|
|
131
|
+
The shopping list application's lambda handlers rely on the `@aws-sdk/client-dynamodb` package, so let's install that into the `@shopping-list/api` project:
|
|
132
132
|
|
|
133
|
-
<InstallCommand pkg="@aws-sdk/client-dynamodb" />
|
|
133
|
+
<InstallCommand pkg="@aws-sdk/client-dynamodb" project="@shopping-list/api" />
|
|
134
134
|
|
|
135
135
|
Then, let's copy the `handlers/src/dynamo-client.ts` file from the PDK project to `backend/src/operations` so it's available for our handlers.
|
|
136
136
|
|
|
137
|
+
The `ts#smithy-api` generator scaffolds an example `Echo` operation. Since we removed this from our model, delete the corresponding handler in `backend/src/operations/echo.ts`. We'll register our migrated operations in `service.ts` further below.
|
|
138
|
+
|
|
137
139
|
To migrate the handlers, you can follow these general steps:
|
|
138
140
|
|
|
139
141
|
<Steps>
|
|
@@ -583,7 +585,7 @@ Additionally, update `packages/api/backend/project.json` and update `metadata.ap
|
|
|
583
585
|
"generator": "ts#smithy-api",
|
|
584
586
|
- "apiName": "api",
|
|
585
587
|
+ "apiName": "my-api",
|
|
586
|
-
"auth": "
|
|
588
|
+
"auth": "iam",
|
|
587
589
|
"modelProject": "@shopping-list/api-model",
|
|
588
590
|
"ports": [3001]
|
|
589
591
|
},
|
|
@@ -11,21 +11,21 @@ import InstallCommand from '@components/install-command.astro';
|
|
|
11
11
|
|
|
12
12
|
The `CloudscapeReactTsWebsiteProject` used in the shopping list application configured a React website with CloudScape and Cognito authentication built in.
|
|
13
13
|
|
|
14
|
-
This project type leveraged [`create-react-app`](https://github.com/facebook/create-react-app), which is now deprecated. For migrating the website in this guide, we will use the <Link path="/guides/react-website">`ts#
|
|
14
|
+
This project type leveraged [`create-react-app`](https://github.com/facebook/create-react-app), which is now deprecated. For migrating the website in this guide, we will use the <Link path="/guides/react-website">`ts#website` generator</Link>, which uses more modern and supported technologies, namely [Vite](https://vite.dev/).
|
|
15
15
|
|
|
16
16
|
As part of the migration, we will also move from PDK's configured React Router to [TanStack Router](https://tanstack.com/router), which adds additional type-safety to website routing.
|
|
17
17
|
|
|
18
18
|
#### Generate a React Website
|
|
19
19
|
|
|
20
|
-
Run the <Link path="/guides/react-website">`ts#
|
|
20
|
+
Run the <Link path="/guides/react-website">`ts#website` generator</Link> with `framework` set to `react` to set up your website project in `packages/website`. Since the shopping list application is built with CloudScape components, we also set `ux` to `cloudscape` (the default is `shadcn`):
|
|
21
21
|
|
|
22
|
-
<RunGenerator generator="ts#
|
|
22
|
+
<RunGenerator generator="ts#website" noInteractive requiredParameters={{ name: 'website', framework: 'react', ux: 'cloudscape' }} />
|
|
23
23
|
|
|
24
24
|
#### Add Cognito Authentication
|
|
25
25
|
|
|
26
|
-
The React website generator above doesn't bundle cognito authentication by default like `CloudscapeReactTsWebsiteProject`, instead it's added explicitly via the <Link path="/guides/react-website-auth">`ts#
|
|
26
|
+
The React website generator above doesn't bundle cognito authentication by default like `CloudscapeReactTsWebsiteProject`, instead it's added explicitly via the <Link path="/guides/react-website-auth">`ts#website#auth` generator</Link>.
|
|
27
27
|
|
|
28
|
-
<RunGenerator generator="ts#
|
|
28
|
+
<RunGenerator generator="ts#website#auth" noInteractive requiredParameters={{ project: 'website', cognitoDomain: 'shopping-list' }} />
|
|
29
29
|
|
|
30
30
|
This adds React components which manage the appropriate redirects to ensure users log in using the Cognito hosted UI. This also adds a CDK construct to deploy the Cognito resources in `packages/common/constructs`, called `UserIdentity`.
|
|
31
31
|
|
|
@@ -57,9 +57,26 @@ This generates the necessary client providers and build targets for your website
|
|
|
57
57
|
|
|
58
58
|
#### Add AWS Northstar Dependency
|
|
59
59
|
|
|
60
|
-
The `CloudscapeReactTsWebsiteProject` automatically included a dependency on `@aws-northstar/ui` which is used in our shopping list application, so we add it
|
|
60
|
+
The `CloudscapeReactTsWebsiteProject` automatically included a dependency on `@aws-northstar/ui` which is used in our shopping list application, so we add it to the `@shopping-list/website` project:
|
|
61
61
|
|
|
62
|
-
<InstallCommand pkg="@aws-northstar/ui" />
|
|
62
|
+
<InstallCommand pkg="@aws-northstar/ui" project="@shopping-list/website" />
|
|
63
|
+
|
|
64
|
+
`@aws-northstar/ui` bundles a code editor component which depends on `ace-builds`, using a webpack-specific import that Vite cannot resolve. Since our shopping list application doesn't use this component, we exclude it from the bundle by adding it to the `external` configuration within the existing `build` options in `packages/website/vite.config.mts`:
|
|
65
|
+
|
|
66
|
+
```diff lang="ts"
|
|
67
|
+
// packages/website/vite.config.mts
|
|
68
|
+
build: {
|
|
69
|
+
outDir: '../../dist/packages/website/bundle',
|
|
70
|
+
emptyOutDir: true,
|
|
71
|
+
reportCompressedSize: true,
|
|
72
|
+
commonjsOptions: {
|
|
73
|
+
transformMixedEsModules: true,
|
|
74
|
+
},
|
|
75
|
+
+ rollupOptions: {
|
|
76
|
+
+ external: ['ace-builds/webpack-resolver'],
|
|
77
|
+
+ },
|
|
78
|
+
},
|
|
79
|
+
```
|
|
63
80
|
|
|
64
81
|
#### Move the Components and Pages
|
|
65
82
|
|
|
@@ -79,12 +96,14 @@ Note that you'll now have some build errors visible in your IDE, we'll need to m
|
|
|
79
96
|
|
|
80
97
|
#### Migrate from React Router to TanStack Router
|
|
81
98
|
|
|
82
|
-
Since we're using [file-based routing](https://tanstack.com/router/latest/docs/framework/react/routing/file-based-routing), we can use the website local development server to manage automatically generating route configuration.
|
|
99
|
+
Since we're using [file-based routing](https://tanstack.com/router/latest/docs/framework/react/routing/file-based-routing), we can use the website local development server to manage automatically generating route configuration.
|
|
100
|
+
|
|
101
|
+
Let's start the local website server:
|
|
83
102
|
|
|
84
|
-
<NxCommands commands={["
|
|
103
|
+
<NxCommands commands={["dev website"]} />
|
|
85
104
|
|
|
86
105
|
:::tip
|
|
87
|
-
We're using the `
|
|
106
|
+
We're using the `dev` target here, which also starts local servers for any APIs which have been connected with `connection`, and hot-reloads if your website, model, or backend changes! This allows us to test our API and website locally before we've even written any CDK code.
|
|
88
107
|
:::
|
|
89
108
|
|
|
90
109
|
You'll see some errors, but the local website server should start on port `4200`, as well as the local Smithy API server on port `3001`.
|
|
@@ -46,7 +46,7 @@ Since the Nx Plugin for AWS uses [Checkov](https://www.checkov.io/) for security
|
|
|
46
46
|
|
|
47
47
|
```diff lang="ts"
|
|
48
48
|
// constructs/database.ts
|
|
49
|
-
+import { suppressRules } from '
|
|
49
|
+
+import { suppressRules } from '@shopping-list/common-constructs';
|
|
50
50
|
...
|
|
51
51
|
+suppressRules(
|
|
52
52
|
+ this.shoppingListTable,
|
|
@@ -69,7 +69,7 @@ The `UserIdentity` construct can generally be swapped out without changes by adj
|
|
|
69
69
|
|
|
70
70
|
```diff lang="ts"
|
|
71
71
|
-import { UserIdentity } from "@aws/pdk/identity";
|
|
72
|
-
+import { UserIdentity } from '
|
|
72
|
+
+import { UserIdentity } from '@shopping-list/common-constructs';
|
|
73
73
|
...
|
|
74
74
|
const userIdentity = new UserIdentity(this, `${id}UserIdentity`);
|
|
75
75
|
```
|
|
@@ -93,7 +93,7 @@ Follow the below steps:
|
|
|
93
93
|
```diff lang="ts"
|
|
94
94
|
// stacks/application-stack.ts
|
|
95
95
|
-import { MyApi } from "../constructs/apis/myapi";
|
|
96
|
-
+import { Api } from '
|
|
96
|
+
+import { Api } from '@shopping-list/common-constructs';
|
|
97
97
|
...
|
|
98
98
|
-const myapi = new MyApi(this, "MyApi", {
|
|
99
99
|
- databaseConstruct,
|
|
@@ -139,7 +139,7 @@ Finally, we add the `Website` construct from `packages/common/constructs/src/app
|
|
|
139
139
|
|
|
140
140
|
```diff lang="ts"
|
|
141
141
|
-import { Website } from "../constructs/websites/website";
|
|
142
|
-
+import { Website } from '
|
|
142
|
+
+import { Website } from '@shopping-list/common-constructs';
|
|
143
143
|
...
|
|
144
144
|
-new Website(this, "Website", {
|
|
145
145
|
- userIdentity,
|
|
@@ -26,7 +26,7 @@ For Python, the [python-fastapi](https://openapi-generator.tech/docs/generators/
|
|
|
26
26
|
|
|
27
27
|
##### Client
|
|
28
28
|
|
|
29
|
-
For TypeScript clients, you can use the <Link path="/guides/react-website">`ts#
|
|
29
|
+
For TypeScript clients, you can use the <Link path="/guides/react-website">`ts#website` generator</Link> and <Link path="/guides/connection">`connection` generator</Link> with an example `ts#api` (with `framework` set to `smithy`) to see how clients are generated and integrated with a website. This configures build targets which generate clients by invoking our `open-api#ts-client` or `open-api#ts-hooks` generators. You can use these generators yourself by pointing them at your OpenAPI Specification.
|
|
30
30
|
|
|
31
31
|
For other languages, you can also see if any of the generators from [OpenAPI Generator](https://openapi-generator.tech/docs/generators#client-generators) fit your needs.
|
|
32
32
|
|
|
@@ -77,117 +77,11 @@ For TypeScript, check out [Smithy TypeScript](https://github.com/smithy-lang/smi
|
|
|
77
77
|
|
|
78
78
|
Type Safe API provided a Projen project type named `SmithyShapeLibraryProject` which configured a project which contained Smithy models which could be reused by multiple Smithy-based APIs.
|
|
79
79
|
|
|
80
|
-
The
|
|
80
|
+
The equivalent is the <Link path="/guides/smithy-project">`smithy#project` generator</Link> with `type` set to `shapes`:
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
<RunGenerator generator="smithy#project" requiredParameters={{ name: 'my-shapes', type: 'shapes' }} />
|
|
83
83
|
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
1. Create your shape library using the `smithy#project` generator:
|
|
87
|
-
|
|
88
|
-
<RunGenerator generator="smithy#project" />
|
|
89
|
-
|
|
90
|
-
Specify any name for the `serviceName` option, as we will remove the `service` shape.
|
|
91
|
-
|
|
92
|
-
:::note
|
|
93
|
-
This generator is hidden at the time of writing and so you will need to execute it via the CLI.
|
|
94
|
-
:::
|
|
95
|
-
|
|
96
|
-
1. Replace the default model in `src` with the shapes you wish to define
|
|
97
|
-
|
|
98
|
-
1. Update `smithy-build.json` to remove the `plugins` and any unused maven dependencies
|
|
99
|
-
|
|
100
|
-
1. Replace `build.Dockerfile` with minimal build steps:
|
|
101
|
-
|
|
102
|
-
```docker
|
|
103
|
-
// build.Dockerfile
|
|
104
|
-
FROM public.ecr.aws/docker/library/node:24 AS builder
|
|
105
|
-
|
|
106
|
-
# Output directory
|
|
107
|
-
RUN mkdir /out
|
|
108
|
-
|
|
109
|
-
# Install Smithy CLI
|
|
110
|
-
# https://smithy.io/2.0/guides/smithy-cli/cli_installation.html
|
|
111
|
-
WORKDIR /smithy
|
|
112
|
-
ARG TARGETPLATFORM
|
|
113
|
-
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCH="aarch64"; else ARCH="x86_64"; fi && \
|
|
114
|
-
mkdir -p smithy-install/smithy && \
|
|
115
|
-
curl -L https://github.com/smithy-lang/smithy/releases/download/1.61.0/smithy-cli-linux-$ARCH.zip -o smithy-install/smithy-cli-linux-$ARCH.zip && \
|
|
116
|
-
unzip -qo smithy-install/smithy-cli-linux-$ARCH.zip -d smithy-install && \
|
|
117
|
-
mv smithy-install/smithy-cli-linux-$ARCH/* smithy-install/smithy
|
|
118
|
-
RUN smithy-install/smithy/install
|
|
119
|
-
|
|
120
|
-
# Copy project files
|
|
121
|
-
COPY smithy-build.json .
|
|
122
|
-
COPY src src
|
|
123
|
-
|
|
124
|
-
# Smithy build with Maven cache mount
|
|
125
|
-
RUN --mount=type=cache,target=/root/.m2/repository,id=maven-cache \
|
|
126
|
-
smithy build
|
|
127
|
-
|
|
128
|
-
RUN cp -r build/* /out/
|
|
129
|
-
|
|
130
|
-
# Export the /out directory
|
|
131
|
-
FROM scratch AS export
|
|
132
|
-
COPY --from=builder /out /
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
</Steps>
|
|
136
|
-
|
|
137
|
-
###### Consume the Shape Library
|
|
138
|
-
|
|
139
|
-
In your service model project(s), make the following changes to consume the shape library:
|
|
140
|
-
|
|
141
|
-
<Steps>
|
|
142
|
-
|
|
143
|
-
1. Update the `compile` target in `project.json` to add the workspace as build context, and a dependency on the shape library's `build` target
|
|
144
|
-
|
|
145
|
-
```json {10,15} "--build-context workspace=." "@my-project/shapes:build"
|
|
146
|
-
// project.json
|
|
147
|
-
{
|
|
148
|
-
"cache": true,
|
|
149
|
-
"outputs": ["{workspaceRoot}/dist/{projectRoot}/build"],
|
|
150
|
-
"executor": "nx:run-commands",
|
|
151
|
-
"options": {
|
|
152
|
-
"commands": [
|
|
153
|
-
"rimraf dist/packages/api/model/build",
|
|
154
|
-
"make-dir dist/packages/api/model/build",
|
|
155
|
-
"docker build --build-context workspace=. -f packages/api/model/build.Dockerfile --target export --output type=local,dest=dist/packages/api/model/build packages/api/model"
|
|
156
|
-
],
|
|
157
|
-
"parallel": false,
|
|
158
|
-
"cwd": "{workspaceRoot}"
|
|
159
|
-
},
|
|
160
|
-
"dependsOn": ["@my-project/shapes:build"]
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
1. Update the `build.Dockerfile` to copy the `src` directory from your shape library. For example, assuming the shape library is located in `packages/shapes`:
|
|
165
|
-
|
|
166
|
-
```docker {5}
|
|
167
|
-
// build.Dockerfile
|
|
168
|
-
# Copy project files
|
|
169
|
-
COPY smithy-build.json .
|
|
170
|
-
COPY src src
|
|
171
|
-
COPY --from=workspace packages/shapes/src shapes
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
1. Update `smithy-build.json` to add the shapes directory to its `sources`:
|
|
175
|
-
|
|
176
|
-
```json {4} "shapes/"
|
|
177
|
-
// smithy-build.json
|
|
178
|
-
{
|
|
179
|
-
"version": "1.0",
|
|
180
|
-
"sources": ["src/", "shapes/"],
|
|
181
|
-
"plugins": {
|
|
182
|
-
...
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
</Steps>
|
|
187
|
-
|
|
188
|
-
:::note
|
|
189
|
-
Please express your interest on the [GitHub issue here](https://github.com/awslabs/nx-plugin-for-aws/issues/304) if you have a use case for a dedicated Smithy shape library generator.
|
|
190
|
-
:::
|
|
84
|
+
Move the shapes from your `SmithyShapeLibraryProject` into the generated project's `src` folder, then refer to the <Link path="/guides/smithy-project#depending-on-a-shape-library">Smithy project guide</Link> for how to wire the library up as a dependency of your API's model.
|
|
191
85
|
|
|
192
86
|
#### Interceptors
|
|
193
87
|
|
|
@@ -273,7 +167,7 @@ new MyApi(this, 'MyApi', {
|
|
|
273
167
|
|
|
274
168
|
You will need to "stub" your service/router for your service to compile if using the `ts#smithy-api` and the TypeScript Server SDK, eg:
|
|
275
169
|
|
|
276
|
-
```ts {
|
|
170
|
+
```ts {3}
|
|
277
171
|
// service.ts
|
|
278
172
|
export const Service: ApiService<ServiceContext> = {
|
|
279
173
|
...
|
|
@@ -10,10 +10,7 @@ import Snippet from '@components/snippet.astro';
|
|
|
10
10
|
|
|
11
11
|
### Recommended
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- [Terraform >= 1.12](https://developer.hashicorp.com/terraform/install) is required if you choose to use this for infrastructure as code instead of CDK
|
|
15
|
-
- verify by running `terraform --version`
|
|
16
|
-
- If you are using [VSCode](https://code.visualstudio.com/), we recommend installing the [Nx Console VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console).
|
|
13
|
+
<Snippet name="recommended-prerequisites" parentHeading="Recommended" />
|
|
17
14
|
|
|
18
15
|
:::tip[AI Assistant Setup]
|
|
19
16
|
If you use an AI Assistant such as Kiro, Kiro CLI, Cursor, Claude Code or Cline, you may also wish to <Link path="/get_started/building-with-ai">install the Nx Plugin for AWS MCP server.</Link>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Admin Credentials
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
|
|
6
|
+
The Aurora admin (master) user's password is generated and rotated by Aurora itself in AWS Secrets Manager, using [RDS-managed master user passwords](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-secrets-manager.html). Your infrastructure code never receives the password, so it cannot leak into a CloudFormation template or Terraform state file. Aurora rotates the secret every 7 days without any rotation function to deploy or maintain.
|
|
7
|
+
|
|
8
|
+
The secret is encrypted with the same customer-managed KMS key as the cluster.
|
|
9
|
+
|
|
10
|
+
Your application never uses these credentials. It connects as a least-privilege database user using IAM authentication — only the migration and create-db-user handlers read the admin secret, and each is granted access to just that secret and its KMS key.
|
|
11
|
+
|
|
12
|
+
<Infrastructure>
|
|
13
|
+
<Fragment slot="cdk">
|
|
14
|
+
|
|
15
|
+
The admin secret is exposed as `secret.secretArn` on the underlying cluster, and `grantSecretRead` grants a consumer read access to it:
|
|
16
|
+
|
|
17
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
18
|
+
import { MyDatabase } from '@my-scope/common-constructs';
|
|
19
|
+
|
|
20
|
+
const db = new MyDatabase(this, 'Db', { ... });
|
|
21
|
+
|
|
22
|
+
db.grantSecretRead(myFunction);
|
|
23
|
+
```
|
|
24
|
+
</Fragment>
|
|
25
|
+
<Fragment slot="terraform">
|
|
26
|
+
|
|
27
|
+
The admin secret's ARN is exposed as the `secret_arn` output, alongside the `kms_key_arn` needed to decrypt it:
|
|
28
|
+
|
|
29
|
+
```hcl title="packages/infra/src/main.tf"
|
|
30
|
+
module "my_database" {
|
|
31
|
+
source = "../../common/terraform/src/app/dbs/my-database"
|
|
32
|
+
...
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
output "database_secret_arn" {
|
|
36
|
+
value = module.my_database.secret_arn
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
An RDS-managed secret holds a JSON object with `username` and `password` only — no connection details. Take the host, port and database name from the module's `writer_endpoint`, `cluster_port` and `database_name` outputs.
|
|
41
|
+
</Fragment>
|
|
42
|
+
</Infrastructure>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: RDB Architecture
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The deployed database has the following architecture. By default, an [Amazon RDS Proxy](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html) sits in front of the Aurora cluster to pool connections and to enable [IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) — see [Disable RDS Proxy](#disable-rds-proxy) for the alternative. The architecture is the same whether you select the PostgreSQL or MySQL engine; only the Aurora engine flavor differs.
|
|
6
|
+
|
|
7
|
+
```d2 inline=true
|
|
8
|
+
direction: right
|
|
9
|
+
|
|
10
|
+
app: Application\n(Lambda, Agent, ...) {
|
|
11
|
+
shape: hexagon
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
proxy: RDS Proxy {
|
|
15
|
+
shape: image
|
|
16
|
+
icon: /nx-plugin-for-aws/icons/aws/rds.svg
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
migrations: Migrations Lambda {
|
|
20
|
+
shape: image
|
|
21
|
+
icon: /nx-plugin-for-aws/icons/aws/lambda.svg
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
aurora: Aurora\n(PostgreSQL or MySQL) {
|
|
25
|
+
shape: image
|
|
26
|
+
icon: /nx-plugin-for-aws/icons/aws/aurora.svg
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
secrets: Secrets Manager\n(DB credentials) {
|
|
30
|
+
shape: image
|
|
31
|
+
icon: /nx-plugin-for-aws/icons/aws/secrets-manager.svg
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
app -> proxy: SQL (IAM auth)
|
|
35
|
+
proxy -> aurora
|
|
36
|
+
migrations -> aurora: Schema migrations
|
|
37
|
+
migrations -> secrets: Admin credentials
|
|
38
|
+
aurora -> secrets: Generates and rotates\nadmin credentials
|
|
39
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cluster Instances
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
|
|
6
|
+
Configure the writer and reader instances for your Aurora cluster.
|
|
7
|
+
|
|
8
|
+
<Infrastructure>
|
|
9
|
+
<Fragment slot="cdk">
|
|
10
|
+
|
|
11
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
12
|
+
import { MyDatabase } from '@my-scope/common-constructs';
|
|
13
|
+
|
|
14
|
+
const db = new MyDatabase(this, 'Db', {
|
|
15
|
+
...
|
|
16
|
+
writer: ClusterInstance.serverlessV2('writer'),
|
|
17
|
+
readers: [ClusterInstance.serverlessV2('reader')],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
</Fragment>
|
|
21
|
+
<Fragment slot="terraform">
|
|
22
|
+
|
|
23
|
+
```hcl title="packages/infra/src/main.tf"
|
|
24
|
+
module "my_database" {
|
|
25
|
+
source = "../../common/terraform/src/app/dbs/my-database"
|
|
26
|
+
...
|
|
27
|
+
instance_count = 2 # 1 writer + 1 reader
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
</Fragment>
|
|
31
|
+
</Infrastructure>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deletion Protection
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
|
|
6
|
+
The Aurora cluster is protected by two independent guards, so that turning off either one alone cannot delete your data:
|
|
7
|
+
|
|
8
|
+
<Infrastructure>
|
|
9
|
+
<Fragment slot="cdk">
|
|
10
|
+
- `deletionProtection`, enforced by RDS.
|
|
11
|
+
- `RemovalPolicy.RETAIN`, enforced by CloudFormation, which leaves the cluster in place when it is removed from the stack.
|
|
12
|
+
</Fragment>
|
|
13
|
+
<Fragment slot="terraform">
|
|
14
|
+
- `deletion_protection`, enforced by RDS.
|
|
15
|
+
- `lifecycle { prevent_destroy = true }` on the cluster in `common/terraform/src/core/rdb/aurora/aurora.tf`, enforced by Terraform, which fails any plan that would destroy the cluster.
|
|
16
|
+
</Fragment>
|
|
17
|
+
</Infrastructure>
|
|
18
|
+
|
|
19
|
+
#### Deleting the Database
|
|
20
|
+
|
|
21
|
+
You can disable protection for environments where database deletion is expected, such as short-lived development or preview stacks.
|
|
22
|
+
|
|
23
|
+
<Infrastructure>
|
|
24
|
+
<Fragment slot="cdk">
|
|
25
|
+
|
|
26
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
27
|
+
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
28
|
+
import { MyDatabase } from '@my-scope/common-constructs';
|
|
29
|
+
|
|
30
|
+
const db = new MyDatabase(this, 'Db', {
|
|
31
|
+
...
|
|
32
|
+
deletionProtection: false,
|
|
33
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
</Fragment>
|
|
37
|
+
<Fragment slot="terraform">
|
|
38
|
+
|
|
39
|
+
```hcl title="packages/infra/src/main.tf"
|
|
40
|
+
module "my_database" {
|
|
41
|
+
source = "../../common/terraform/src/app/dbs/my-database"
|
|
42
|
+
...
|
|
43
|
+
deletion_protection = false
|
|
44
|
+
skip_final_snapshot = true
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`prevent_destroy` must be a literal — Terraform does not allow it to reference a variable — so it cannot be turned off from `main.tf`. Also remove the `lifecycle` block from the cluster in `common/terraform/src/core/rdb/aurora/aurora.tf`:
|
|
49
|
+
|
|
50
|
+
```hcl title="packages/common/terraform/src/core/rdb/aurora/aurora.tf" del={4-6}
|
|
51
|
+
resource "aws_rds_cluster" "database" {
|
|
52
|
+
# ...
|
|
53
|
+
|
|
54
|
+
lifecycle {
|
|
55
|
+
prevent_destroy = true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
:::caution
|
|
61
|
+
This module is shared by every database in your workspace, so removing the block removes that guard for all of them.
|
|
62
|
+
:::
|
|
63
|
+
</Fragment>
|
|
64
|
+
</Infrastructure>
|