@aws/nx-plugin-mcp 1.0.0-rc.8 → 1.0.0-rc.80
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 +14210 -13550
- 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 +147 -0
- package/docs/guides/agentcore-gateway.mdx +490 -0
- package/docs/guides/agentcore-harness.mdx +275 -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 +116 -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 +178 -0
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
- package/docs/guides/connection/py-fast-api-rdb.mdx +184 -0
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
- package/docs/guides/connection/py-mcp-server-rdb.mdx +187 -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 +5 -5
- package/docs/guides/connection/react-trpc.mdx +2 -2
- package/docs/guides/connection/react-ts-agent.mdx +9 -9
- package/docs/guides/connection/smithy-dynamodb.mdx +6 -6
- package/docs/guides/connection/smithy-rdb.mdx +10 -10
- package/docs/guides/connection/trpc-dynamodb.mdx +5 -5
- package/docs/guides/connection/trpc-rdb.mdx +6 -6
- package/docs/guides/connection/ts-agent-a2a.mdx +14 -11
- package/docs/guides/connection/ts-agent-dynamodb.mdx +5 -5
- 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 +70 -25
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +5 -5
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +68 -18
- package/docs/guides/connection.mdx +122 -5
- package/docs/guides/docker-bundling.mdx +81 -12
- package/docs/guides/fastapi.mdx +252 -15
- 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 +265 -0
- package/docs/guides/python-lambda-function.mdx +1 -1
- package/docs/guides/react-website-auth.mdx +104 -5
- package/docs/guides/react-website.mdx +413 -99
- package/docs/guides/runtime-config.mdx +1 -1
- package/docs/guides/security.mdx +75 -0
- package/docs/guides/smithy-project.mdx +167 -0
- package/docs/guides/terraform-project.mdx +2 -2
- package/docs/guides/trpc.mdx +54 -17
- 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 +114 -468
- package/docs/guides/ts-smithy-api.mdx +259 -19
- package/docs/guides/typescript-infrastructure.mdx +46 -24
- package/docs/guides/typescript-project.mdx +134 -27
- package/docs/guides/workspace.mdx +10 -3
- 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 +69 -50
- 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 +1 -1
- 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 +51 -19
- package/docs/snippets/dynamodb/deploying-table.mdx +145 -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/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/architecture.mdx +38 -0
- package/docs/snippets/rdb/cluster-instances.mdx +31 -0
- package/docs/snippets/rdb/deletion-protection.mdx +34 -0
- package/docs/snippets/rdb/deploying.mdx +187 -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 +34 -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 +162 -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/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
|
@@ -3,15 +3,40 @@ title: Docker Bundling
|
|
|
3
3
|
description: Build and deploy Docker images for TypeScript and Python projects in an Nx Plugin for AWS workspace.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
6
|
+
import { FileTree, Tabs, TabItem, Code } from '@astrojs/starlight/components';
|
|
7
7
|
import NxCommands from '@components/nx-commands.astro';
|
|
8
|
+
import PackageManagerShortCommand from '@components/package-manager-short-command.astro';
|
|
8
9
|
import Link from '@components/link.astro';
|
|
9
10
|
import Infrastructure from '@components/infrastructure.astro';
|
|
11
|
+
import TrivyVersion from '@components/trivy-version.astro';
|
|
12
|
+
import { CONTAINER_VERSIONS } from '../../../../../../packages/nx-plugin/src/utils/versions';
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
export const trivyTarget = `{
|
|
15
|
+
"targets": {
|
|
16
|
+
"trivy": {
|
|
17
|
+
"cache": true,
|
|
18
|
+
"inputs": ["default", "^production"],
|
|
19
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}/trivy"],
|
|
20
|
+
"executor": "nx:run-commands",
|
|
21
|
+
"options": {
|
|
22
|
+
"commands": [
|
|
23
|
+
"rimraf dist/packages/my-project/trivy",
|
|
24
|
+
"make-dir dist/packages/my-project/trivy",
|
|
25
|
+
"ncp packages/my-project/.trivyignore dist/packages/my-project/trivy/.trivyignore",
|
|
26
|
+
"docker save -o dist/packages/my-project/trivy/image.tar my-scope-my-project:latest",
|
|
27
|
+
"docker run --rm -v \\"./dist/packages/my-project/trivy\\":/scan public.ecr.aws/aquasecurity/trivy:${CONTAINER_VERSIONS.trivy} image --input /scan/image.tar --ignorefile /scan/.trivyignore --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q"
|
|
28
|
+
],
|
|
29
|
+
"parallel": false
|
|
30
|
+
},
|
|
31
|
+
"dependsOn": ["docker"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}`;
|
|
35
|
+
|
|
36
|
+
Several generators (such as <Link path="/guides/ts-agent">`ts#agent`</Link> and <Link path="/guides/py-agent">`py#agent`</Link>) produce a Docker image that is pushed to Amazon ECR and consumed by AWS infrastructure. This guide describes the pattern they follow so that you can apply it to other use cases — for example, running a <Link path="/guides/fastapi">FastAPI</Link> project on Amazon ECS, or deploying a containerised Express server.
|
|
12
37
|
|
|
13
38
|
:::tip[Docker or Finch]
|
|
14
|
-
The container engine used to build images is chosen at workspace creation time via the `--
|
|
39
|
+
The container engine used to build images is chosen at workspace creation time via the `--containers` flag (`docker`, `finch`, or `infer` — the default — which auto-detects what's installed). [Finch](https://runfinch.com/) is an open-source, drop-in alternative to Docker. The selection is recorded in `aws-nx-plugin.config.mts` and applied to every generator that emits container build commands. CDK image asset builds honour the choice via the `CDK_DOCKER` environment variable.
|
|
15
40
|
:::
|
|
16
41
|
|
|
17
42
|
## The Pattern
|
|
@@ -84,7 +109,7 @@ export default defineConfig([
|
|
|
84
109
|
output: {
|
|
85
110
|
file: '../../dist/packages/my-project/bundle/index.js',
|
|
86
111
|
format: 'cjs',
|
|
87
|
-
|
|
112
|
+
codeSplitting: false,
|
|
88
113
|
},
|
|
89
114
|
platform: 'node',
|
|
90
115
|
},
|
|
@@ -253,6 +278,44 @@ This clears the output directory, then copies both the bundle contents and the `
|
|
|
253
278
|
|
|
254
279
|
<NxCommands commands={['docker my-project']} />
|
|
255
280
|
|
|
281
|
+
## Scanning Images with Trivy
|
|
282
|
+
|
|
283
|
+
It's good practice to scan your images for known vulnerabilities. The generators that follow this pattern add a `trivy` target which scans the built image with [Trivy](https://trivy.dev/), running from the [ECR-hosted Trivy image](https://gallery.ecr.aws/aquasecurity/trivy), and exits non-zero on `HIGH` or `CRITICAL` findings.
|
|
284
|
+
|
|
285
|
+
Add a `trivy` target which `dependsOn` your `docker` target. It saves the built image to a tarball and scans it via a workspace-relative bind mount, so the same command works under both `docker` and `finch`:
|
|
286
|
+
|
|
287
|
+
<Code lang="json" code={trivyTarget} />
|
|
288
|
+
|
|
289
|
+
Each project's per-image scan targets are aggregated under a workspace-wide `trivy` target, which the vended `trivy` root script runs (`nx run-many --target trivy`).
|
|
290
|
+
|
|
291
|
+
<PackageManagerShortCommand commands={['trivy']} />
|
|
292
|
+
|
|
293
|
+
:::tip[Run Trivy in CI]
|
|
294
|
+
The scan is intentionally not wired into `build` since this can introduce unnecessary friction when iterating during development, as Trivy's vulnerabilty database is continually updated with new CVEs.
|
|
295
|
+
|
|
296
|
+
Instead we recommend running the above command as a dedicated step in your CI pipeline prior to deployment to production stages.
|
|
297
|
+
:::
|
|
298
|
+
|
|
299
|
+
:::tip[Caching skips unchanged images]
|
|
300
|
+
Because the image is fully determined by your project source, declaring `inputs` (here `default` and `^production`, matching the `bundle` target) lets Nx cache the scan and skip re-scanning an image that hasn't changed. Pin the Trivy image version (e.g. <code>trivy:<TrivyVersion /></code>) so scans are reproducible.
|
|
301
|
+
:::
|
|
302
|
+
|
|
303
|
+
:::note[Suppressing findings]
|
|
304
|
+
Trivy reads a `.trivyignore` file (a list of vulnerability IDs, one per line) from the root of your project. `--ignore-unfixed` skips vulnerabilities with no available fix, so the scan only fails on issues you can action by upgrading tooling in your `Dockerfile`. See the [Trivy filtering documentation](https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids) for details.
|
|
305
|
+
:::
|
|
306
|
+
|
|
307
|
+
:::tip[Apply the distribution's security updates]
|
|
308
|
+
A base image tag lags behind its distribution's security suite, so its OS packages carry vulnerabilities that already have a published fix. Upgrade them as the first step of your build, ahead of any other instruction so later layers install against the upgraded packages. For a Debian-derived base image (as used by the Node and Python images above):
|
|
309
|
+
|
|
310
|
+
```dockerfile
|
|
311
|
+
RUN apt-get update && \
|
|
312
|
+
apt-get upgrade -y --no-install-recommends && \
|
|
313
|
+
rm -rf /var/lib/apt/lists/*
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The generated `Dockerfile`s do this for you.
|
|
317
|
+
:::
|
|
318
|
+
|
|
256
319
|
## Infrastructure
|
|
257
320
|
|
|
258
321
|
Wiring the resulting build-context directory to infrastructure as code is the same for both TypeScript and Python — only the path to the build-context directory differs (`dist/packages/my-project/bundle` for TypeScript, `dist/packages/my-project/docker` for Python).
|
|
@@ -291,7 +354,7 @@ infra.asset -> ecr: cdk deploy\nbuilds + pushes
|
|
|
291
354
|
|
|
292
355
|
```ts
|
|
293
356
|
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';
|
|
294
|
-
import { findWorkspaceRoot } from '
|
|
357
|
+
import { findWorkspaceRoot } from '@my-scope/common-constructs';
|
|
295
358
|
import * as path from 'path';
|
|
296
359
|
import * as url from 'url';
|
|
297
360
|
|
|
@@ -305,7 +368,7 @@ const image = new DockerImageAsset(this, 'MyImage', {
|
|
|
305
368
|
});
|
|
306
369
|
```
|
|
307
370
|
|
|
308
|
-
The `findWorkspaceRoot` helper is generated by the <Link path="/guides/typescript-infrastructure">`ts#infra`</Link> generator and exported from
|
|
371
|
+
The `findWorkspaceRoot` helper is generated by the <Link path="/guides/typescript-infrastructure">`ts#infra`</Link> generator and exported from `@my-scope/common-constructs`. If you are not using shared constructs, you can hardcode the path to the `dist` directory relative to where `cdk` is invoked from — typically the workspace root — and omit the `findWorkspaceRoot` call entirely.
|
|
309
372
|
|
|
310
373
|
:::note[Running bundle before synth]
|
|
311
374
|
CDK does not run the `bundle`/`docker` targets automatically — you must run `nx build my-project` (or wire the deploy target to depend on `build`) before `cdk deploy`. The generators that use this pattern declare `docker` and `bundle` as dependencies of `build` so this happens transparently.
|
|
@@ -318,8 +381,8 @@ Terraform's AWS provider does not have a first-class "build and push a Docker im
|
|
|
318
381
|
|
|
319
382
|
1. The project's `build` target runs `docker build`, producing a local image tagged `my-scope-my-project:latest`.
|
|
320
383
|
2. An `aws_ecr_repository` to hold the image.
|
|
321
|
-
3. A `null_resource` with a `local-exec` provisioner that authenticates to ECR, re-tags the locally-built image, and pushes it.
|
|
322
|
-
4. The downstream resource (e.g. `aws_ecs_task_definition`) references
|
|
384
|
+
3. A `null_resource` with a `local-exec` provisioner that authenticates to ECR, re-tags the locally-built image with its content digest, and pushes it.
|
|
385
|
+
4. The downstream resource (e.g. `aws_ecs_task_definition`) references the image by its immutable, digest-based tag.
|
|
323
386
|
|
|
324
387
|
```d2
|
|
325
388
|
direction: down
|
|
@@ -361,7 +424,7 @@ infra.repo -> ecr
|
|
|
361
424
|
```hcl
|
|
362
425
|
resource "aws_ecr_repository" "repo" {
|
|
363
426
|
name = "my-project-repository"
|
|
364
|
-
image_tag_mutability = "
|
|
427
|
+
image_tag_mutability = "IMMUTABLE"
|
|
365
428
|
force_delete = true
|
|
366
429
|
}
|
|
367
430
|
|
|
@@ -370,24 +433,30 @@ data "external" "docker_digest" {
|
|
|
370
433
|
program = ["sh", "-c", "echo '{\"digest\":\"'$(docker inspect my-scope-my-project:latest --format '{{.Id}}')'\"}'"]
|
|
371
434
|
}
|
|
372
435
|
|
|
436
|
+
locals {
|
|
437
|
+
# Content-based, immutable image tag derived from the local image digest
|
|
438
|
+
image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
|
|
439
|
+
}
|
|
440
|
+
|
|
373
441
|
resource "null_resource" "docker_publish" {
|
|
374
442
|
triggers = {
|
|
375
443
|
docker_digest = data.external.docker_digest.result.digest
|
|
376
444
|
repository_url = aws_ecr_repository.repo.repository_url
|
|
445
|
+
image_tag = local.image_tag
|
|
377
446
|
}
|
|
378
447
|
|
|
379
448
|
provisioner "local-exec" {
|
|
380
449
|
command = <<-EOT
|
|
381
450
|
aws ecr get-login-password --region ${data.aws_region.current.id} \
|
|
382
451
|
| docker login --username AWS --password-stdin ${self.triggers.repository_url}
|
|
383
|
-
docker tag my-scope-my-project:latest ${self.triggers.repository_url}
|
|
384
|
-
docker push ${self.triggers.repository_url}
|
|
452
|
+
docker tag my-scope-my-project:latest ${self.triggers.repository_url}:${self.triggers.image_tag}
|
|
453
|
+
docker push ${self.triggers.repository_url}:${self.triggers.image_tag}
|
|
385
454
|
EOT
|
|
386
455
|
}
|
|
387
456
|
}
|
|
388
457
|
```
|
|
389
458
|
|
|
390
|
-
The `data.external.docker_digest` block ensures the `null_resource` re-runs whenever the local image hash changes, triggering a new push on every meaningful code change.
|
|
459
|
+
The `data.external.docker_digest` block ensures the `null_resource` re-runs whenever the local image hash changes, triggering a new push on every meaningful code change. The image is pushed under an immutable, content-based tag derived from its digest, so the ECR repository can use `IMMUTABLE` tag mutability and reject any attempt to overwrite an existing tag.
|
|
391
460
|
|
|
392
461
|
:::note[Running bundle before apply]
|
|
393
462
|
`nx apply <project>` requires the image tag `my-scope-my-project:latest` to already exist locally. Run `nx build my-project` (or `nx docker my-project`) before `nx apply <project>`.
|
package/docs/guides/fastapi.mdx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FastAPI
|
|
3
3
|
description: Reference documentation for FastAPI
|
|
4
|
-
generator: py#
|
|
4
|
+
generator: py#api
|
|
5
5
|
when:
|
|
6
6
|
framework: [fastapi]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
9
|
+
import { FileTree, Tabs, TabItem, CardGrid } from '@astrojs/starlight/components';
|
|
10
|
+
import Astro from '@astrojs/react';
|
|
11
|
+
import ConnectionCard from '@components/connection-card.astro';
|
|
10
12
|
import Link from '@components/link.astro';
|
|
11
13
|
import RunGenerator from '@components/run-generator.astro';
|
|
12
14
|
import GeneratorParameters from '@components/generator-parameters.astro';
|
|
@@ -26,11 +28,11 @@ The FastAPI generator creates a new FastAPI with AWS CDK or Terraform infrastruc
|
|
|
26
28
|
|
|
27
29
|
You can generate a new FastAPI in two ways:
|
|
28
30
|
|
|
29
|
-
<RunGenerator generator="py#
|
|
31
|
+
<RunGenerator generator="py#api" requiredParameters={{ framework: 'fastapi' }} />
|
|
30
32
|
|
|
31
33
|
### Options
|
|
32
34
|
|
|
33
|
-
<GeneratorParameters generator="py#
|
|
35
|
+
<GeneratorParameters generator="py#api" />
|
|
34
36
|
|
|
35
37
|
<Snippet name="api/api-choice-note" />
|
|
36
38
|
|
|
@@ -119,9 +121,7 @@ def read_item(item_id: int):
|
|
|
119
121
|
The logger automatically includes:
|
|
120
122
|
|
|
121
123
|
- Correlation IDs for request tracing
|
|
122
|
-
- Request path and method
|
|
123
|
-
- Lambda context information
|
|
124
|
-
- Cold start indicators
|
|
124
|
+
- Request path, matched route and method
|
|
125
125
|
|
|
126
126
|
#### Tracing
|
|
127
127
|
|
|
@@ -157,8 +157,7 @@ Default metrics include:
|
|
|
157
157
|
|
|
158
158
|
- Request counts
|
|
159
159
|
- Success/failure counts
|
|
160
|
-
-
|
|
161
|
-
- Per-route metrics
|
|
160
|
+
- Per-route metrics (via a `route` dimension of `<method> <path>`)
|
|
162
161
|
|
|
163
162
|
### Error Handling
|
|
164
163
|
|
|
@@ -185,6 +184,216 @@ Unhandled exceptions are caught by the middleware and:
|
|
|
185
184
|
It's recommended to specify response models for your API operations for better code generation if using the `connection` generator. <Link path="guides/connection/react-fastapi#errors">See here for more details</Link>.
|
|
186
185
|
:::
|
|
187
186
|
|
|
187
|
+
### Accessing the Calling User
|
|
188
|
+
|
|
189
|
+
When your API is protected by authentication, your route handlers often need to know who is calling. The generated FastAPI runs inside AWS Lambda via the [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter), which forwards the API Gateway request context as JSON on the `x-amzn-request-context` header. You can read it from the FastAPI `Request` to extract the caller's identity.
|
|
190
|
+
|
|
191
|
+
As an example, let's add a `/me` endpoint that returns details about the calling user. We'll implement the extraction as a [FastAPI dependency](https://fastapi.tiangolo.com/tutorial/dependencies/) so it can be reused across routes. The shape of the request context — and therefore how you extract the identity — depends on both your selected `auth` method and whether you deployed a REST or HTTP API.
|
|
192
|
+
|
|
193
|
+
<OptionFilter when={{ auth: 'iam' }} description="Identity extraction for IAM-authenticated APIs">
|
|
194
|
+
For `IAM` authentication, we look up the caller in Cognito using the sub extracted from the API Gateway request context. Create `identity.py` alongside `main.py`:
|
|
195
|
+
|
|
196
|
+
<Tabs syncKey="http-rest">
|
|
197
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
198
|
+
```python
|
|
199
|
+
import json
|
|
200
|
+
import os
|
|
201
|
+
from typing import Annotated
|
|
202
|
+
|
|
203
|
+
from boto3 import client
|
|
204
|
+
from fastapi import Depends, HTTPException, Request
|
|
205
|
+
from pydantic import BaseModel
|
|
206
|
+
|
|
207
|
+
cognito = client("cognito-idp")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class Identity(BaseModel):
|
|
211
|
+
sub: str
|
|
212
|
+
username: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_identity(request: Request) -> Identity:
|
|
216
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
217
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
218
|
+
if not request_context_header:
|
|
219
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
220
|
+
|
|
221
|
+
request_context = json.loads(request_context_header)
|
|
222
|
+
provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
|
|
223
|
+
|
|
224
|
+
sub = provider.split(":")[-1] if provider else None
|
|
225
|
+
if not sub:
|
|
226
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
227
|
+
|
|
228
|
+
users = cognito.list_users(
|
|
229
|
+
# Assumes user pool id is configured in lambda environment
|
|
230
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
231
|
+
Limit=1,
|
|
232
|
+
Filter=f'sub="{sub}"',
|
|
233
|
+
).get("Users", [])
|
|
234
|
+
|
|
235
|
+
if len(users) != 1:
|
|
236
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
237
|
+
|
|
238
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
242
|
+
```
|
|
243
|
+
</TabItem>
|
|
244
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
245
|
+
```python
|
|
246
|
+
import json
|
|
247
|
+
import os
|
|
248
|
+
from typing import Annotated
|
|
249
|
+
|
|
250
|
+
from boto3 import client
|
|
251
|
+
from fastapi import Depends, HTTPException, Request
|
|
252
|
+
from pydantic import BaseModel
|
|
253
|
+
|
|
254
|
+
cognito = client("cognito-idp")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class Identity(BaseModel):
|
|
258
|
+
sub: str
|
|
259
|
+
username: str
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def get_identity(request: Request) -> Identity:
|
|
263
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
264
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
265
|
+
if not request_context_header:
|
|
266
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
267
|
+
|
|
268
|
+
request_context = json.loads(request_context_header)
|
|
269
|
+
amr = (
|
|
270
|
+
request_context.get("authorizer", {})
|
|
271
|
+
.get("iam", {})
|
|
272
|
+
.get("cognitoIdentity", {})
|
|
273
|
+
.get("amr", [])
|
|
274
|
+
)
|
|
275
|
+
sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None)
|
|
276
|
+
sub = sign_in.split(":")[-1] if sign_in else None
|
|
277
|
+
|
|
278
|
+
if not sub:
|
|
279
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
280
|
+
|
|
281
|
+
users = cognito.list_users(
|
|
282
|
+
# Assumes user pool id is configured in lambda environment
|
|
283
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
284
|
+
Limit=1,
|
|
285
|
+
Filter=f'sub="{sub}"',
|
|
286
|
+
).get("Users", [])
|
|
287
|
+
|
|
288
|
+
if len(users) != 1:
|
|
289
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
290
|
+
|
|
291
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
295
|
+
```
|
|
296
|
+
</TabItem>
|
|
297
|
+
</Tabs>
|
|
298
|
+
</OptionFilter>
|
|
299
|
+
|
|
300
|
+
<OptionFilter when={{ auth: 'cognito' }} description="Identity extraction for Cognito-authenticated APIs">
|
|
301
|
+
With `auth: 'cognito'`, the API Gateway Cognito User Pools authorizer verifies the JWT that the caller supplies in the `Authorization` header and places the verified claims on the request context.
|
|
302
|
+
|
|
303
|
+
Create `identity.py` alongside `main.py`:
|
|
304
|
+
|
|
305
|
+
<Tabs syncKey="http-rest">
|
|
306
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
307
|
+
```python
|
|
308
|
+
import json
|
|
309
|
+
from typing import Annotated
|
|
310
|
+
|
|
311
|
+
from fastapi import Depends, HTTPException, Request
|
|
312
|
+
from pydantic import BaseModel
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class Identity(BaseModel):
|
|
316
|
+
sub: str
|
|
317
|
+
username: str
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def get_identity(request: Request) -> Identity:
|
|
321
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
322
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
323
|
+
if not request_context_header:
|
|
324
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
325
|
+
|
|
326
|
+
request_context = json.loads(request_context_header)
|
|
327
|
+
claims = request_context.get("authorizer", {}).get("claims", {})
|
|
328
|
+
|
|
329
|
+
sub = claims.get("sub")
|
|
330
|
+
username = claims.get("username")
|
|
331
|
+
|
|
332
|
+
if not sub or not username:
|
|
333
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
334
|
+
|
|
335
|
+
return Identity(sub=sub, username=username)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
339
|
+
```
|
|
340
|
+
</TabItem>
|
|
341
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
342
|
+
HTTP APIs use a JWT authorizer which places the verified claims under `authorizer.jwt.claims`:
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
import json
|
|
346
|
+
from typing import Annotated
|
|
347
|
+
|
|
348
|
+
from fastapi import Depends, HTTPException, Request
|
|
349
|
+
from pydantic import BaseModel
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class Identity(BaseModel):
|
|
353
|
+
sub: str
|
|
354
|
+
username: str
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def get_identity(request: Request) -> Identity:
|
|
358
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
359
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
360
|
+
if not request_context_header:
|
|
361
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
362
|
+
|
|
363
|
+
request_context = json.loads(request_context_header)
|
|
364
|
+
claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
|
|
365
|
+
|
|
366
|
+
sub = claims.get("sub")
|
|
367
|
+
username = claims.get("username")
|
|
368
|
+
|
|
369
|
+
if not sub or not username:
|
|
370
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
371
|
+
|
|
372
|
+
return Identity(sub=sub, username=username)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
376
|
+
```
|
|
377
|
+
</TabItem>
|
|
378
|
+
</Tabs>
|
|
379
|
+
|
|
380
|
+
:::tip[No token verification required]
|
|
381
|
+
You don't need any JWT-verification library here — the API Gateway Cognito User Pools authorizer has already verified the signature, issuer, scopes, and expiry by the time your Lambda runs. If any of those checks fail, API Gateway returns `401 Unauthorized` and your handler is never invoked.
|
|
382
|
+
:::
|
|
383
|
+
</OptionFilter>
|
|
384
|
+
|
|
385
|
+
You can then inject the `CurrentUser` dependency into any route that needs the caller's identity:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from .identity import CurrentUser, Identity
|
|
389
|
+
from .init import app, tracer
|
|
390
|
+
|
|
391
|
+
@app.get("/me")
|
|
392
|
+
@tracer.capture_method
|
|
393
|
+
def me(identity: CurrentUser) -> Identity:
|
|
394
|
+
return identity
|
|
395
|
+
```
|
|
396
|
+
|
|
188
397
|
<OptionFilter when={{ infra: 'rest-lambda' }} description="Streaming — REST API only">
|
|
189
398
|
### Streaming
|
|
190
399
|
|
|
@@ -226,14 +435,14 @@ To consume a stream of responses, you can make use of the <Link path="guides/con
|
|
|
226
435
|
|
|
227
436
|
## Deploying your FastAPI
|
|
228
437
|
|
|
229
|
-
The FastAPI generator creates CDK or Terraform infrastructure as code based on your selected `
|
|
438
|
+
The FastAPI generator creates CDK or Terraform infrastructure as code based on your selected `iac`. You can use this to deploy your FastAPI.
|
|
230
439
|
|
|
231
440
|
<Infrastructure>
|
|
232
441
|
<Fragment slot="cdk">
|
|
233
442
|
The CDK construct for deploying your API in the `common/constructs` folder. You can use this in a CDK application:
|
|
234
443
|
|
|
235
444
|
```ts {6-8}
|
|
236
|
-
import { MyApi } from '
|
|
445
|
+
import { MyApi } from '@my-scope/common-constructs';
|
|
237
446
|
|
|
238
447
|
export class ExampleStack extends Stack {
|
|
239
448
|
constructor(scope: Construct, id: string) {
|
|
@@ -261,7 +470,7 @@ This sets up:
|
|
|
261
470
|
If you selected to use `Cognito` authentication, you will need to supply the `identity` property to the API construct:
|
|
262
471
|
|
|
263
472
|
```ts {9}
|
|
264
|
-
import { MyApi, UserIdentity } from '
|
|
473
|
+
import { MyApi, UserIdentity } from '@my-scope/common-constructs';
|
|
265
474
|
|
|
266
475
|
export class ExampleStack extends Stack {
|
|
267
476
|
constructor(scope: Construct, id: string) {
|
|
@@ -330,7 +539,7 @@ This sets up:
|
|
|
330
539
|
:::note[Cognito Authentication]
|
|
331
540
|
If you selected to use `Cognito` authentication, you will need to supply the Cognito configuration:
|
|
332
541
|
|
|
333
|
-
```hcl {
|
|
542
|
+
```hcl {6-7}
|
|
334
543
|
module "my_api" {
|
|
335
544
|
source = "../../common/terraform/src/app/apis/my-api"
|
|
336
545
|
|
|
@@ -406,6 +615,12 @@ When using `Custom` auth, your API is protected by a Lambda Authorizer that **de
|
|
|
406
615
|
<Snippet name="api/waf-configuration" parentHeading="WAF" />
|
|
407
616
|
</OptionFilter>
|
|
408
617
|
|
|
618
|
+
<OptionFilter when={{ infra: 'rest-lambda' }} description="Access logging — REST APIs log requests to CloudWatch by default">
|
|
619
|
+
### Access logging
|
|
620
|
+
|
|
621
|
+
<Snippet name="api/access-logging" parentHeading="Access logging" />
|
|
622
|
+
</OptionFilter>
|
|
623
|
+
|
|
409
624
|
### Integrations
|
|
410
625
|
|
|
411
626
|
<Snippet name="api/type-safe-api-integrations" parentHeading="Integrations" />
|
|
@@ -430,14 +645,14 @@ If you are actively working on both your CDK infrastructure and FastAPI together
|
|
|
430
645
|
<NxCommands
|
|
431
646
|
commands={[
|
|
432
647
|
'watch --projects=<FastAPIProject> -- \\ ',
|
|
433
|
-
'run
|
|
648
|
+
'run @<scope>/common-constructs:"generate:<ApiName>-metadata"',
|
|
434
649
|
]}
|
|
435
650
|
/>
|
|
436
651
|
:::
|
|
437
652
|
</Fragment>
|
|
438
653
|
<Fragment slot="terraform">
|
|
439
654
|
:::note[Terraform Limitations]
|
|
440
|
-
We do not support type-safe integrations for Terraform, and therefore no code generation targets are configured if you selected Terraform for your `
|
|
655
|
+
We do not support type-safe integrations for Terraform, and therefore no code generation targets are configured if you selected Terraform for your `iac`.
|
|
441
656
|
:::
|
|
442
657
|
</Fragment>
|
|
443
658
|
</Infrastructure>
|
|
@@ -509,3 +724,25 @@ This starts a local FastAPI development server with:
|
|
|
509
724
|
## Invoking your FastAPI
|
|
510
725
|
|
|
511
726
|
To invoke your API from a React website, you can use the <Link path="guides/connection/react-fastapi">`connection` generator</Link>.
|
|
727
|
+
|
|
728
|
+
## Connections
|
|
729
|
+
|
|
730
|
+
Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
|
|
731
|
+
|
|
732
|
+
<CardGrid>
|
|
733
|
+
<ConnectionCard
|
|
734
|
+
title="React to FastAPI"
|
|
735
|
+
description="Call a Python FastAPI from a React website"
|
|
736
|
+
href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/react-fastapi`}
|
|
737
|
+
source="react"
|
|
738
|
+
target="fastapi"
|
|
739
|
+
/>
|
|
740
|
+
<ConnectionCard
|
|
741
|
+
title="FastAPI to Python DynamoDB"
|
|
742
|
+
description="Connect a FastAPI to a DynamoDB table"
|
|
743
|
+
href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-fast-api-dynamodb`}
|
|
744
|
+
source="fastapi"
|
|
745
|
+
target="dynamodb"
|
|
746
|
+
targetBadge="python"
|
|
747
|
+
/>
|
|
748
|
+
</CardGrid>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Local Development
|
|
3
|
+
description: How local development works with the serve and dev targets
|
|
4
|
+
---
|
|
5
|
+
import Link from '@components/link.astro';
|
|
6
|
+
import NxCommands from '@components/nx-commands.astro';
|
|
7
|
+
import PackageManagerShortCommand from '@components/package-manager-short-command.astro';
|
|
8
|
+
|
|
9
|
+
Connected projects expose two targets for running them on your machine: `serve` and `dev`. The difference is one of **scope** — how much of your application runs locally versus pointing at deployed AWS infrastructure.
|
|
10
|
+
|
|
11
|
+
Consider a workspace with the following connections: a website that calls a tRPC `api`, and also calls an `agent` which in turn calls an `mcp` server.
|
|
12
|
+
|
|
13
|
+
## `serve`
|
|
14
|
+
|
|
15
|
+
The `serve` target runs **only the targeted project** locally. Every other project it connects to is expected to be **deployed**, and is reached at its real AWS URL.
|
|
16
|
+
|
|
17
|
+
<NxCommands commands={['serve website']} />
|
|
18
|
+
|
|
19
|
+
Because the targeted project talks to deployed resources, it needs to know where they are. For a website this is provided by its `runtime-config.json` (see <Link path="guides/react-website#local-runtime-config">Local Runtime Config</Link>), which you load from a deployed application. For server-side projects (APIs and agents) that read <Link path="guides/runtime-config">Runtime Configuration</Link>, set the `RUNTIME_CONFIG_APP_ID` environment variable to point at your deployed AppConfig application.
|
|
20
|
+
|
|
21
|
+
```d2
|
|
22
|
+
direction: right
|
|
23
|
+
|
|
24
|
+
local: Local {
|
|
25
|
+
style.stroke-dash: 3
|
|
26
|
+
website: website
|
|
27
|
+
}
|
|
28
|
+
deployed: Deployed {
|
|
29
|
+
style.stroke-dash: 3
|
|
30
|
+
api: api
|
|
31
|
+
agent: agent
|
|
32
|
+
mcp: mcp
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
local.website -> deployed.api
|
|
36
|
+
local.website -> deployed.agent
|
|
37
|
+
deployed.agent -> deployed.mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use `serve` when you want to iterate on a single project against the "real", deployed versions of everything it depends on.
|
|
41
|
+
|
|
42
|
+
## `dev`
|
|
43
|
+
|
|
44
|
+
The `dev` target runs the **targeted project** and **every project connected to it transitively**, all on your machine. The connection generator wires this up automatically — running `dev` on the website also starts local servers for the `api`, the `agent`, and the `mcp` server it reaches through the agent.
|
|
45
|
+
|
|
46
|
+
<NxCommands commands={['dev website']} />
|
|
47
|
+
|
|
48
|
+
When run this way, the website's `runtime-config.json` is automatically overridden (via [Vite's `MODE`](https://vite.dev/guide/env-and-mode), set to `local-dev`) so that it points at your locally running servers instead of deployed URLs.
|
|
49
|
+
|
|
50
|
+
```d2
|
|
51
|
+
direction: right
|
|
52
|
+
|
|
53
|
+
local: Local {
|
|
54
|
+
style.stroke-dash: 3
|
|
55
|
+
website: website
|
|
56
|
+
api: api
|
|
57
|
+
agent: agent
|
|
58
|
+
mcp: mcp
|
|
59
|
+
|
|
60
|
+
website -> api
|
|
61
|
+
website -> agent
|
|
62
|
+
agent -> mcp
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Every project runs locally, so there are no deployed dependencies.
|
|
67
|
+
|
|
68
|
+
Use `dev` when you are working across several connected projects at once and want to iterate quickly without deploying your infrastructure.
|
|
69
|
+
|
|
70
|
+
### Projects with multiple components
|
|
71
|
+
|
|
72
|
+
Some project types can hold multiple components in a single project (for example a TypeScript or Python project containing several agents and MCP servers). For these:
|
|
73
|
+
|
|
74
|
+
- The project-level `dev` target starts **all** of the project's components together. Each component is added to `dev` as it is generated:
|
|
75
|
+
|
|
76
|
+
<NxCommands commands={['dev my-project']} />
|
|
77
|
+
- Each component also exposes a `<component-name>-dev` target, so you can run a single component on its own:
|
|
78
|
+
|
|
79
|
+
<NxCommands commands={['my-component-dev my-project']} />
|
|
80
|
+
|
|
81
|
+
:::tip[Workspace `dev` script]
|
|
82
|
+
Your workspace's root `package.json` includes a `dev` script that runs `nx run-many --target dev`, starting the `dev` target for every project that has one. Spin up your whole workspace's local development servers with:
|
|
83
|
+
|
|
84
|
+
<PackageManagerShortCommand commands={["dev"]} />
|
|
85
|
+
:::
|
|
86
|
+
|
|
87
|
+
For more detail on each project type's local development server, see the relevant guide — for example the <Link path="guides/react-website#local-development-server">React Website</Link> guide.
|
|
@@ -286,7 +286,7 @@ import { generateFiles, joinPathFragments } from '@nx/devkit';
|
|
|
286
286
|
// Generate files from templates
|
|
287
287
|
generateFiles(
|
|
288
288
|
tree,
|
|
289
|
-
joinPathFragments(
|
|
289
|
+
joinPathFragments(import.meta.dirname, 'files'), // Template directory
|
|
290
290
|
'path/to/output', // Output directory
|
|
291
291
|
{
|
|
292
292
|
// Variables to replace in templates
|
|
@@ -459,7 +459,7 @@ export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
|
|
|
459
459
|
|
|
460
460
|
generateFiles(
|
|
461
461
|
tree,
|
|
462
|
-
joinPathFragments(
|
|
462
|
+
joinPathFragments(import.meta.dirname, 'files'), // Template directory
|
|
463
463
|
'path/to/output', // Output directory
|
|
464
464
|
data,
|
|
465
465
|
);
|
|
@@ -497,7 +497,7 @@ Unit tests for generators are straightforward to implement. Here's a typical pat
|
|
|
497
497
|
|
|
498
498
|
```typescript
|
|
499
499
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
500
|
-
import { yourGenerator } from './generator';
|
|
500
|
+
import { yourGenerator } from './generator.js';
|
|
501
501
|
|
|
502
502
|
describe('your generator', () => {
|
|
503
503
|
let tree;
|
|
@@ -583,6 +583,7 @@ When this generator is run in our repository, it'll generate the following files
|
|
|
583
583
|
- docs/src/content/docs/guides/
|
|
584
584
|
- \<name>.mdx Documentation page for your generator
|
|
585
585
|
- packages/nx-plugin/generators.json Updated to include your generator
|
|
586
|
+
- packages/nx-plugin/sdk/\<prefix>.ts Updated to expose your generator from the SDK (for `ts#` and `py#` generators)
|
|
586
587
|
</FileTree>
|
|
587
588
|
|
|
588
589
|
You can then start to implement your generator.
|