@aws/nx-plugin 1.0.0-rc.60 → 1.0.0-rc.61
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/migrations.json +31 -26
- package/package.json +1 -1
- package/src/migrations/latest/py-agent-lifespan-construction/metadata.json +3 -0
- package/src/migrations/latest/py-agent-lifespan-construction/migration.js +412 -0
- package/src/migrations/latest/py-agent-lifespan-construction/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.50/0001-modernize-function-props-cast/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.50/0002-restrict-cors-to-custom-domains/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.50/0003-terraform-bootstrap-adopt-existing-bucket/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.52/0001-user-identity-waf-allow-localhost-callback/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.54/0001-order-access-log-delivery-after-bucket-policy/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.55/0001-backfill-generator-metadata/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.56/0001-dynamodb-local-remove-optimize-flag/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.57/0001-ts-agent-session-management-support/migration.d.ts +6 -0
- package/src/migrations/v1.0.0-rc.57/0001-ts-agent-session-management-support/migration.js.map +1 -0
- package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +2 -10
- package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +12 -8
- package/src/py/agent/files/{ag-ui-langchain → langchain/ag-ui}/main.py.template +12 -8
- package/src/py/agent/files/{http → langchain/http}/init.py.template +10 -0
- package/src/py/agent/files/{http-langchain → langchain/http}/main.py.template +1 -4
- package/src/py/agent/files/strands/ag-ui/main.py.template +89 -0
- package/src/py/agent/files/strands/http/init.py.template +97 -0
- package/src/py/agent/files/{http → strands/http}/main.py.template +2 -10
- package/src/py/agent/generator.js +9 -19
- package/src/py/agent/generator.js.map +1 -1
- package/src/utils/migration-versions.d.ts +31 -2
- package/src/utils/migration-versions.js +74 -10
- package/src/utils/migration-versions.js.map +1 -1
- package/src/migrations/v1.0.0-rc.50/modernize-function-props-cast/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.50/restrict-cors-to-custom-domains/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.50/terraform-bootstrap-adopt-existing-bucket/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.52/user-identity-waf-allow-localhost-callback/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.54/order-access-log-delivery-after-bucket-policy/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.55/backfill-generator-metadata/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.56/dynamodb-local-remove-optimize-flag/migration.js.map +0 -1
- package/src/migrations/v1.0.0-rc.57/ts-agent-session-management-support/migration.js.map +0 -1
- package/src/py/agent/files/ag-ui/main.py.template +0 -37
- /package/src/migrations/{v1.0.0-rc.50/modernize-function-props-cast → latest/py-agent-lifespan-construction}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.50/{modernize-function-props-cast → 0001-modernize-function-props-cast}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.50/{restrict-cors-to-custom-domains → 0001-modernize-function-props-cast}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.50/{modernize-function-props-cast → 0001-modernize-function-props-cast}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.50/{restrict-cors-to-custom-domains → 0002-restrict-cors-to-custom-domains}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.50/{terraform-bootstrap-adopt-existing-bucket → 0002-restrict-cors-to-custom-domains}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.50/{restrict-cors-to-custom-domains → 0002-restrict-cors-to-custom-domains}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.50/{terraform-bootstrap-adopt-existing-bucket → 0003-terraform-bootstrap-adopt-existing-bucket}/metadata.json +0 -0
- /package/src/migrations/{v1.0.0-rc.57/ts-agent-session-management-support → v1.0.0-rc.50/0003-terraform-bootstrap-adopt-existing-bucket}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.50/{terraform-bootstrap-adopt-existing-bucket → 0003-terraform-bootstrap-adopt-existing-bucket}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.52/{user-identity-waf-allow-localhost-callback → 0001-user-identity-waf-allow-localhost-callback}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.52/{user-identity-waf-allow-localhost-callback → 0001-user-identity-waf-allow-localhost-callback}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.52/{user-identity-waf-allow-localhost-callback → 0001-user-identity-waf-allow-localhost-callback}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.54/{order-access-log-delivery-after-bucket-policy → 0001-order-access-log-delivery-after-bucket-policy}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.54/{order-access-log-delivery-after-bucket-policy → 0001-order-access-log-delivery-after-bucket-policy}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.54/{order-access-log-delivery-after-bucket-policy → 0001-order-access-log-delivery-after-bucket-policy}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.55/{backfill-generator-metadata → 0001-backfill-generator-metadata}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.55/{backfill-generator-metadata → 0001-backfill-generator-metadata}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.55/{backfill-generator-metadata → 0001-backfill-generator-metadata}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.56/{dynamodb-local-remove-optimize-flag → 0001-dynamodb-local-remove-optimize-flag}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.56/{dynamodb-local-remove-optimize-flag → 0001-dynamodb-local-remove-optimize-flag}/migration.d.ts +0 -0
- /package/src/migrations/v1.0.0-rc.56/{dynamodb-local-remove-optimize-flag → 0001-dynamodb-local-remove-optimize-flag}/migration.js +0 -0
- /package/src/migrations/v1.0.0-rc.57/{ts-agent-session-management-support → 0001-ts-agent-session-management-support}/metadata.json +0 -0
- /package/src/migrations/v1.0.0-rc.57/{ts-agent-session-management-support → 0001-ts-agent-session-management-support}/migration.js +0 -0
- /package/src/py/agent/files/{a2a-langchain → langchain/a2a}/main.py.template +0 -0
- /package/src/py/agent/files/{common-langchain → langchain/common}/agent.py.template +0 -0
- /package/src/py/agent/files/{a2a → strands/a2a}/main.py.template +0 -0
- /package/src/py/agent/files/{common → strands/common}/__init__.py.template +0 -0
- /package/src/py/agent/files/{common → strands/common}/agent.py.template +0 -0
package/migrations.json
CHANGED
|
@@ -7,75 +7,80 @@
|
|
|
7
7
|
"description": "Add agent runtime target support to the vended AgentCoreGateway construct and an agentName member to vended agent constructs",
|
|
8
8
|
"implementation": "./src/migrations/latest/gateway-agent-targets/migration"
|
|
9
9
|
},
|
|
10
|
-
"latest-gateway-cognito-passthrough": {
|
|
11
|
-
"version": "1.0.0-rc.60",
|
|
12
|
-
"description": "Front Cognito agents with an AgentCore Gateway via JWT passthrough: addAgent picks the target credential from the agent's auth, agent constructs gain an auth member, and Cognito runtimes allowlist the Authorization header",
|
|
13
|
-
"implementation": "./src/migrations/latest/gateway-cognito-passthrough/migration"
|
|
14
|
-
},
|
|
15
10
|
"latest-smithy-ssdk-bundle-pins": {
|
|
16
11
|
"version": "1.0.0-rc.58",
|
|
17
12
|
"description": "Bump the rolldown and dts plugin pins in the Smithy build.Dockerfile so the bundled SSDK type declaration resolves",
|
|
18
13
|
"implementation": "./src/migrations/latest/smithy-ssdk-bundle-pins/migration"
|
|
19
14
|
},
|
|
20
|
-
"latest-
|
|
15
|
+
"latest-gateway-cognito-passthrough": {
|
|
21
16
|
"version": "1.0.0-rc.60",
|
|
22
|
-
"description": "
|
|
23
|
-
"implementation": "./src/migrations/latest/
|
|
17
|
+
"description": "Front Cognito agents with an AgentCore Gateway via JWT passthrough: addAgent picks the target credential from the agent's auth, agent constructs gain an auth member, and Cognito runtimes allowlist the Authorization header",
|
|
18
|
+
"implementation": "./src/migrations/latest/gateway-cognito-passthrough/migration"
|
|
24
19
|
},
|
|
25
20
|
"latest-waf-log-group-removal": {
|
|
26
21
|
"version": "1.0.0-rc.60",
|
|
27
22
|
"description": "Set RemovalPolicy.DESTROY on WAF log groups in the vended AgentCoreGateway and REST API constructs so a rolled-back deploy does not orphan them",
|
|
28
23
|
"implementation": "./src/migrations/latest/waf-log-group-removal/migration"
|
|
29
24
|
},
|
|
30
|
-
"
|
|
25
|
+
"latest-strip-pip-from-python-images": {
|
|
26
|
+
"version": "1.0.0-rc.60",
|
|
27
|
+
"description": "Remove pip from vended Python agent / MCP server images so its vulnerable vendored packages are not present at runtime",
|
|
28
|
+
"implementation": "./src/migrations/latest/strip-pip-from-python-images/migration"
|
|
29
|
+
},
|
|
30
|
+
"latest-py-agent-lifespan-construction": {
|
|
31
|
+
"version": "1.0.0-rc.61",
|
|
32
|
+
"description": "Move py#agent HTTP/AG-UI agent construction out of module import time and into a FastAPI lifespan handler, stored on app.state",
|
|
33
|
+
"implementation": "./src/migrations/latest/py-agent-lifespan-construction/migration"
|
|
34
|
+
},
|
|
35
|
+
"v1.0.0-rc.50-0001-modernize-function-props-cast": {
|
|
31
36
|
"version": "1.0.0-rc.50",
|
|
32
37
|
"description": "Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs",
|
|
33
|
-
"implementation": "./src/migrations/v1.0.0-rc.50/modernize-function-props-cast/migration"
|
|
38
|
+
"implementation": "./src/migrations/v1.0.0-rc.50/0001-modernize-function-props-cast/migration"
|
|
34
39
|
},
|
|
35
|
-
"v1.0.0-rc.50-restrict-cors-to-custom-domains": {
|
|
40
|
+
"v1.0.0-rc.50-0002-restrict-cors-to-custom-domains": {
|
|
36
41
|
"version": "1.0.0-rc.50",
|
|
37
42
|
"description": "Include CloudFront custom domain aliases in restrictCorsTo and UserIdentity callback URLs",
|
|
38
|
-
"implementation": "./src/migrations/v1.0.0-rc.50/restrict-cors-to-custom-domains/migration"
|
|
43
|
+
"implementation": "./src/migrations/v1.0.0-rc.50/0002-restrict-cors-to-custom-domains/migration"
|
|
39
44
|
},
|
|
40
|
-
"v1.0.0-rc.50-terraform-bootstrap-adopt-existing-bucket": {
|
|
45
|
+
"v1.0.0-rc.50-0003-terraform-bootstrap-adopt-existing-bucket": {
|
|
41
46
|
"version": "1.0.0-rc.50",
|
|
42
47
|
"description": "Adopt an existing Terraform state bucket in the vended bootstrap script when its state object is missing",
|
|
43
|
-
"implementation": "./src/migrations/v1.0.0-rc.50/terraform-bootstrap-adopt-existing-bucket/migration"
|
|
48
|
+
"implementation": "./src/migrations/v1.0.0-rc.50/0003-terraform-bootstrap-adopt-existing-bucket/migration"
|
|
44
49
|
},
|
|
45
|
-
"v1.0.0-rc.52-user-identity-waf-allow-localhost-callback": {
|
|
50
|
+
"v1.0.0-rc.52-0001-user-identity-waf-allow-localhost-callback": {
|
|
46
51
|
"version": "1.0.0-rc.52",
|
|
47
52
|
"description": "Count the EC2MetaDataSSRF_QUERYARGUMENTS WAF rule on the UserIdentity Web ACL so sign-in from a local dev server is not blocked",
|
|
48
|
-
"implementation": "./src/migrations/v1.0.0-rc.52/user-identity-waf-allow-localhost-callback/migration"
|
|
53
|
+
"implementation": "./src/migrations/v1.0.0-rc.52/0001-user-identity-waf-allow-localhost-callback/migration"
|
|
49
54
|
},
|
|
50
|
-
"v1.0.0-rc.54-order-access-log-delivery-after-bucket-policy": {
|
|
55
|
+
"v1.0.0-rc.54-0001-order-access-log-delivery-after-bucket-policy": {
|
|
51
56
|
"version": "1.0.0-rc.54",
|
|
52
57
|
"description": "Order the S3 server access log delivery source after the bucket policy to avoid a 409 from concurrent bucket configuration writes",
|
|
53
|
-
"implementation": "./src/migrations/v1.0.0-rc.54/order-access-log-delivery-after-bucket-policy/migration"
|
|
58
|
+
"implementation": "./src/migrations/v1.0.0-rc.54/0001-order-access-log-delivery-after-bucket-policy/migration"
|
|
54
59
|
},
|
|
55
|
-
"v1.0.0-rc.55-backfill-generator-metadata": {
|
|
60
|
+
"v1.0.0-rc.55-0001-backfill-generator-metadata": {
|
|
56
61
|
"version": "1.0.0-rc.55",
|
|
57
62
|
"description": "Backfill the project metadata the version sync reads, recovered from the files the generators left behind",
|
|
58
|
-
"implementation": "./src/migrations/v1.0.0-rc.55/backfill-generator-metadata/migration"
|
|
63
|
+
"implementation": "./src/migrations/v1.0.0-rc.55/0001-backfill-generator-metadata/migration"
|
|
59
64
|
},
|
|
60
|
-
"v1.0.0-rc.56-dynamodb-local-remove-optimize-flag": {
|
|
65
|
+
"v1.0.0-rc.56-0001-dynamodb-local-remove-optimize-flag": {
|
|
61
66
|
"version": "1.0.0-rc.56",
|
|
62
67
|
"description": "Drop -optimizeDbBeforeStartup from the vended DynamoDB Local container script, which can corrupt shared-local-instance.db",
|
|
63
|
-
"implementation": "./src/migrations/v1.0.0-rc.56/dynamodb-local-remove-optimize-flag/migration"
|
|
68
|
+
"implementation": "./src/migrations/v1.0.0-rc.56/0001-dynamodb-local-remove-optimize-flag/migration"
|
|
64
69
|
},
|
|
65
|
-
"v1.0.0-rc.57-ts-agent-session-management-support": {
|
|
70
|
+
"v1.0.0-rc.57-0001-ts-agent-session-management-support": {
|
|
66
71
|
"version": "1.0.0-rc.57",
|
|
67
72
|
"description": "Add session management support to ts#agent",
|
|
68
|
-
"implementation": "./src/migrations/v1.0.0-rc.57/ts-agent-session-management-support/migration"
|
|
73
|
+
"implementation": "./src/migrations/v1.0.0-rc.57/0001-ts-agent-session-management-support/migration"
|
|
69
74
|
},
|
|
70
75
|
"sync-vended-versions": {
|
|
71
|
-
"version": "1.0.0-rc.
|
|
76
|
+
"version": "1.0.0-rc.61",
|
|
72
77
|
"description": "Sync vended dependency versions and the tracked plugin version to those vended by this release",
|
|
73
78
|
"implementation": "./src/utils/version-upgrade-migration/migration"
|
|
74
79
|
}
|
|
75
80
|
},
|
|
76
81
|
"packageJsonUpdates": {
|
|
77
82
|
"nx-23.1.1-nx-packages": {
|
|
78
|
-
"version": "1.0.0-rc.
|
|
83
|
+
"version": "1.0.0-rc.61",
|
|
79
84
|
"packages": {
|
|
80
85
|
"nx": {
|
|
81
86
|
"version": "23.1.1",
|
package/package.json
CHANGED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { PY_AGENT_GENERATOR_INFO } from "../../../py/agent/generator.js";
|
|
6
|
+
import { addPythonDestructuredImport, applyGritQL, captureGritQLVariable, matchGritQL } from "../../../utils/ast.js";
|
|
7
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
8
|
+
/**
|
|
9
|
+
* Move py#agent HTTP/AG-UI agent construction out of module import time and
|
|
10
|
+
* into a FastAPI `lifespan` handler, stored on `app.state`.
|
|
11
|
+
*
|
|
12
|
+
* Building the agent eagerly at import time breaks any codepath that imports
|
|
13
|
+
* the module before `RUNTIME_CONFIG_APP_ID`/AgentCore env vars are set (e.g.
|
|
14
|
+
* OpenAPI spec generation, tests) - and, for AgentCore, construction belongs
|
|
15
|
+
* at container/app startup rather than import, since AgentCore hands each
|
|
16
|
+
* session its own container. The A2A protocol is unaffected: `with_session_id`
|
|
17
|
+
* only touches the agent factory lazily on first use, so its eager
|
|
18
|
+
* `__enter__()` was never actually eager in practice.
|
|
19
|
+
*
|
|
20
|
+
* How to write a migration:
|
|
21
|
+
* - https://nx.dev/docs/kb/migration-generators
|
|
22
|
+
* - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject
|
|
23
|
+
*
|
|
24
|
+
* Guardrails:
|
|
25
|
+
* - Pattern-match before writing: every rewrite this migration makes to a file
|
|
26
|
+
* is preceded by a read-only `matchGritQL` check of every pattern it will
|
|
27
|
+
* need - so a diverged file is reported via `nextSteps` and left completely
|
|
28
|
+
* untouched, rather than partially rewritten into a broken state.
|
|
29
|
+
* - Idempotent: re-running must be a no-op (each rewrite's own shape change
|
|
30
|
+
* means its pattern no longer matches on a second pass).
|
|
31
|
+
* - Format what you write: finish with `formatFilesInSubtree`.
|
|
32
|
+
*/ /** A read-only GritQL match against a python file (no rewrite). */ const pyMatch = (snippet)=>`language python\n\`${snippet}\``;
|
|
33
|
+
/** A GritQL rewrite deleting the matched python snippet entirely. */ const pyDelete = (snippet)=>`${pyMatch(snippet)} => .`;
|
|
34
|
+
/**
|
|
35
|
+
* A GritQL rewrite of one python snippet into another. Multi-line
|
|
36
|
+
* replacements go through `raw` so GritQL's own reformatting doesn't fight
|
|
37
|
+
* the exact shape being written (formatFilesInSubtree normalizes it after).
|
|
38
|
+
*/ const pyRewrite = (from, to, where)=>{
|
|
39
|
+
const replacement = to.includes('\n') ? `raw\`${to}\`` : `\`${to}\``;
|
|
40
|
+
return `${pyMatch(from)} => ${replacement}${where ? ` where { ${where} }` : ''}`;
|
|
41
|
+
};
|
|
42
|
+
/** All checks must match before anything is written for that agent. */ const allMatch = async (tree, checks)=>{
|
|
43
|
+
for (const [filePath, pattern] of checks){
|
|
44
|
+
if (!await matchGritQL(tree, filePath, pattern)) return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
const AGENT_CONNECTION_MODULE_PATTERN = (names)=>pyMatch(`from $mod import ${names}`);
|
|
49
|
+
const STRANDS_HTTP_MARKER = '_agent_ctx = with_session_id(';
|
|
50
|
+
const LANGCHAIN_HTTP_MARKER = '_graph = get_agent()';
|
|
51
|
+
const STRANDS_AGUI_MARKER = 'create_strands_app';
|
|
52
|
+
const LANGCHAIN_AGUI_MARKER = '_graph = get_agent()';
|
|
53
|
+
// Shared by strands/http and langchain/http - only the lifespan body differs.
|
|
54
|
+
const HTTP_INIT_APP_OLD = `app = FastAPI(
|
|
55
|
+
title="$name",
|
|
56
|
+
responses={500: {"model": InternalServerErrorDetails}},
|
|
57
|
+
generate_unique_id_function=lambda route: route.name,
|
|
58
|
+
)`;
|
|
59
|
+
// --- Strands HTTP -----------------------------------------------------------
|
|
60
|
+
const STRANDS_HTTP_MAIN_CTX_OLD = `_agent_ctx = with_session_id(
|
|
61
|
+
get_agent,
|
|
62
|
+
name="$name",
|
|
63
|
+
description="A Strands Agent exposed via HTTP streaming.",
|
|
64
|
+
)`;
|
|
65
|
+
const STRANDS_HTTP_INIT_APP_NEW = `@asynccontextmanager
|
|
66
|
+
async def lifespan(app: FastAPI):
|
|
67
|
+
with with_session_id(
|
|
68
|
+
get_agent,
|
|
69
|
+
name="$name",
|
|
70
|
+
description="A Strands Agent exposed via HTTP streaming.",
|
|
71
|
+
) as agent:
|
|
72
|
+
app.state.agent = agent
|
|
73
|
+
yield
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
app = FastAPI(
|
|
77
|
+
title="$name",
|
|
78
|
+
responses={500: {"model": InternalServerErrorDetails}},
|
|
79
|
+
generate_unique_id_function=lambda route: route.name,
|
|
80
|
+
lifespan=lifespan,
|
|
81
|
+
)`;
|
|
82
|
+
const migrateStrandsHttpAgent = async (tree, dir, nextSteps)=>{
|
|
83
|
+
const initPath = joinPathFragments(dir, 'init.py');
|
|
84
|
+
const mainPath = joinPathFragments(dir, 'main.py');
|
|
85
|
+
if (!tree.exists(initPath) || !tree.exists(mainPath)) return;
|
|
86
|
+
const mainContents = tree.read(mainPath, 'utf-8') ?? '';
|
|
87
|
+
if (!mainContents.includes(STRANDS_HTTP_MARKER)) return;
|
|
88
|
+
const mod = await captureGritQLVariable(tree, mainPath, AGENT_CONNECTION_MODULE_PATTERN('session_id_context, with_session_id'), 'mod');
|
|
89
|
+
const ready = !!mod && await allMatch(tree, [
|
|
90
|
+
[
|
|
91
|
+
mainPath,
|
|
92
|
+
pyMatch(STRANDS_HTTP_MAIN_CTX_OLD)
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
mainPath,
|
|
96
|
+
pyMatch('_agent = _agent_ctx.__enter__()')
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
mainPath,
|
|
100
|
+
pyMatch('from .agent import get_agent')
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
mainPath,
|
|
104
|
+
pyMatch('_agent.stream_async($prompt)')
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
initPath,
|
|
108
|
+
pyMatch(HTTP_INIT_APP_OLD)
|
|
109
|
+
]
|
|
110
|
+
]);
|
|
111
|
+
if (!ready) {
|
|
112
|
+
nextSteps.push(`${mainPath}: diverged from the generated shape - left untouched, along with ${initPath}. Manually move agent construction into an \`init.py\` \`lifespan\` handler storing the agent on \`app.state.agent\` (see the py#agent generator's strands/http template).`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await addPythonDestructuredImport(tree, initPath, [
|
|
116
|
+
'asynccontextmanager'
|
|
117
|
+
], 'contextlib');
|
|
118
|
+
await addPythonDestructuredImport(tree, initPath, [
|
|
119
|
+
'get_agent'
|
|
120
|
+
], '.agent');
|
|
121
|
+
await addPythonDestructuredImport(tree, initPath, [
|
|
122
|
+
'with_session_id'
|
|
123
|
+
], mod);
|
|
124
|
+
await applyGritQL(tree, initPath, pyRewrite(HTTP_INIT_APP_OLD, STRANDS_HTTP_INIT_APP_NEW));
|
|
125
|
+
await applyGritQL(tree, mainPath, pyRewrite('from $mod import session_id_context, with_session_id', 'from $mod import session_id_context'));
|
|
126
|
+
await applyGritQL(tree, mainPath, pyDelete('from .agent import get_agent'));
|
|
127
|
+
await applyGritQL(tree, mainPath, pyDelete(STRANDS_HTTP_MAIN_CTX_OLD));
|
|
128
|
+
await applyGritQL(tree, mainPath, pyDelete('_agent = _agent_ctx.__enter__()'));
|
|
129
|
+
await applyGritQL(tree, mainPath, pyRewrite('_agent.stream_async($prompt)', 'app.state.agent.stream_async($prompt)'));
|
|
130
|
+
};
|
|
131
|
+
// --- LangChain HTTP ----------------------------------------------------------
|
|
132
|
+
const LANGCHAIN_HTTP_INIT_APP_NEW = `@asynccontextmanager
|
|
133
|
+
async def lifespan(app: FastAPI):
|
|
134
|
+
app.state.graph = get_agent()
|
|
135
|
+
yield
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
app = FastAPI(
|
|
139
|
+
title="$name",
|
|
140
|
+
responses={500: {"model": InternalServerErrorDetails}},
|
|
141
|
+
generate_unique_id_function=lambda route: route.name,
|
|
142
|
+
lifespan=lifespan,
|
|
143
|
+
)`;
|
|
144
|
+
const migrateLangchainHttpAgent = async (tree, dir, nextSteps)=>{
|
|
145
|
+
const initPath = joinPathFragments(dir, 'init.py');
|
|
146
|
+
const mainPath = joinPathFragments(dir, 'main.py');
|
|
147
|
+
if (!tree.exists(initPath) || !tree.exists(mainPath)) return;
|
|
148
|
+
const mainContents = tree.read(mainPath, 'utf-8') ?? '';
|
|
149
|
+
if (!mainContents.includes(LANGCHAIN_HTTP_MARKER)) return;
|
|
150
|
+
const ready = await allMatch(tree, [
|
|
151
|
+
[
|
|
152
|
+
mainPath,
|
|
153
|
+
pyMatch('_graph = get_agent()')
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
mainPath,
|
|
157
|
+
pyMatch('from .agent import get_agent')
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
mainPath,
|
|
161
|
+
pyMatch('_graph.astream($args)')
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
initPath,
|
|
165
|
+
pyMatch(HTTP_INIT_APP_OLD)
|
|
166
|
+
]
|
|
167
|
+
]);
|
|
168
|
+
if (!ready) {
|
|
169
|
+
nextSteps.push(`${mainPath}: diverged from the generated shape - left untouched, along with ${initPath}. Manually wrap \`get_agent()\` in an \`init.py\` \`lifespan\` handler storing the graph on \`app.state.graph\` (see the py#agent generator's langchain/http template).`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await addPythonDestructuredImport(tree, initPath, [
|
|
173
|
+
'asynccontextmanager'
|
|
174
|
+
], 'contextlib');
|
|
175
|
+
await addPythonDestructuredImport(tree, initPath, [
|
|
176
|
+
'get_agent'
|
|
177
|
+
], '.agent');
|
|
178
|
+
await applyGritQL(tree, initPath, pyRewrite(HTTP_INIT_APP_OLD, LANGCHAIN_HTTP_INIT_APP_NEW));
|
|
179
|
+
await applyGritQL(tree, mainPath, pyDelete('from .agent import get_agent'));
|
|
180
|
+
await applyGritQL(tree, mainPath, pyDelete('_graph = get_agent()'));
|
|
181
|
+
await applyGritQL(tree, mainPath, pyRewrite('_graph.astream($args)', 'app.state.graph.astream($args)'));
|
|
182
|
+
};
|
|
183
|
+
// --- LangChain AG-UI ---------------------------------------------------------
|
|
184
|
+
const LANGCHAIN_AGUI_AGENT_OLD = `agui_agent = LangGraphAgent(
|
|
185
|
+
name="$name",
|
|
186
|
+
graph=_graph,
|
|
187
|
+
description="A LangChain/LangGraph Agent exposed via the AG-UI protocol.",
|
|
188
|
+
)`;
|
|
189
|
+
const LANGCHAIN_AGUI_AGENT_NEW = `@asynccontextmanager
|
|
190
|
+
async def lifespan(app: FastAPI):
|
|
191
|
+
graph = get_agent()
|
|
192
|
+
app.state.agui_agent = LangGraphAgent(
|
|
193
|
+
name="$name",
|
|
194
|
+
graph=graph,
|
|
195
|
+
description="A LangChain/LangGraph Agent exposed via the AG-UI protocol.",
|
|
196
|
+
)
|
|
197
|
+
yield`;
|
|
198
|
+
const migrateLangchainAgUiAgent = async (tree, dir, nextSteps)=>{
|
|
199
|
+
const mainPath = joinPathFragments(dir, 'main.py');
|
|
200
|
+
if (!tree.exists(mainPath)) return;
|
|
201
|
+
const mainContents = tree.read(mainPath, 'utf-8') ?? '';
|
|
202
|
+
if (!mainContents.includes(LANGCHAIN_AGUI_MARKER)) return;
|
|
203
|
+
const ready = await allMatch(tree, [
|
|
204
|
+
[
|
|
205
|
+
mainPath,
|
|
206
|
+
pyMatch('_graph = get_agent()')
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
mainPath,
|
|
210
|
+
pyMatch(LANGCHAIN_AGUI_AGENT_OLD)
|
|
211
|
+
],
|
|
212
|
+
[
|
|
213
|
+
mainPath,
|
|
214
|
+
pyMatch('agui_agent.run($input)')
|
|
215
|
+
],
|
|
216
|
+
[
|
|
217
|
+
mainPath,
|
|
218
|
+
pyMatch('app = FastAPI(title="$name")')
|
|
219
|
+
]
|
|
220
|
+
]);
|
|
221
|
+
if (!ready) {
|
|
222
|
+
nextSteps.push(`${mainPath}: diverged from the generated shape - left untouched. Manually wrap graph + \`LangGraphAgent\` construction in a \`lifespan\` handler storing the wrapper on \`app.state.agui_agent\` (see the py#agent generator's langchain/ag-ui template).`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
226
|
+
'asynccontextmanager'
|
|
227
|
+
], 'contextlib');
|
|
228
|
+
await applyGritQL(tree, mainPath, pyDelete('_graph = get_agent()'));
|
|
229
|
+
await applyGritQL(tree, mainPath, pyRewrite(LANGCHAIN_AGUI_AGENT_OLD, LANGCHAIN_AGUI_AGENT_NEW));
|
|
230
|
+
await applyGritQL(tree, mainPath, pyRewrite('agui_agent.run($input)', 'request.app.state.agui_agent.run($input)'));
|
|
231
|
+
await applyGritQL(tree, mainPath, pyRewrite('app = FastAPI(title="$name")', 'app = FastAPI(title="$name", lifespan=lifespan)'));
|
|
232
|
+
};
|
|
233
|
+
// --- Strands AG-UI -----------------------------------------------------------
|
|
234
|
+
// The biggest of the four: `ag_ui_strands.create_strands_app` is dropped
|
|
235
|
+
// entirely for an inlined FastAPI app (matching upstream `ag_ui_strands`,
|
|
236
|
+
// plus a Content-Type-independent body-parsing fix and an explicit session
|
|
237
|
+
// re-bind `create_strands_app` was missing), so most of the file is new.
|
|
238
|
+
const STRANDS_AGUI_IMPORT_OLD = 'from ag_ui_strands import StrandsAgent, create_strands_app';
|
|
239
|
+
const STRANDS_AGUI_AGENT_OLD = `agui_agent = StrandsAgent(
|
|
240
|
+
agent=_agent,
|
|
241
|
+
name="$name",
|
|
242
|
+
description="A Strands Agent exposed via the AG-UI protocol.",
|
|
243
|
+
)`;
|
|
244
|
+
const STRANDS_AGUI_AGENT_NEW = `@asynccontextmanager
|
|
245
|
+
async def lifespan(app: FastAPI):
|
|
246
|
+
with get_agent() as agent:
|
|
247
|
+
app.state.agui_agent = StrandsAgent(
|
|
248
|
+
agent=agent,
|
|
249
|
+
name="$name",
|
|
250
|
+
description="A Strands Agent exposed via the AG-UI protocol.",
|
|
251
|
+
)
|
|
252
|
+
yield`;
|
|
253
|
+
const STRANDS_AGUI_APP_OLD = 'app = create_strands_app(agui_agent, path="/invocations")';
|
|
254
|
+
// `$name` isn't bound within this match (unlike STRANDS_AGUI_AGENT_OLD above),
|
|
255
|
+
// so the title is built from a name captured separately rather than reused as
|
|
256
|
+
// a metavariable - GritQL leaves a rewrite whose replacement references an
|
|
257
|
+
// unbound metavariable unapplied.
|
|
258
|
+
const strandsAguiAppNew = (name)=>`app = FastAPI(title="AWS Strands - ${name}", lifespan=lifespan)
|
|
259
|
+
app.add_middleware(
|
|
260
|
+
CORSMiddleware,
|
|
261
|
+
allow_origins=["*"],
|
|
262
|
+
allow_credentials=True,
|
|
263
|
+
allow_methods=["*"],
|
|
264
|
+
allow_headers=["*"],
|
|
265
|
+
)`;
|
|
266
|
+
const STRANDS_AGUI_MIDDLEWARE_OLD = 'app.add_middleware(_SessionIdMiddleware)';
|
|
267
|
+
const STRANDS_AGUI_ROUTES_NEW = `app.add_middleware(_SessionIdMiddleware)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@app.post("/invocations")
|
|
271
|
+
async def invocations(request: Request):
|
|
272
|
+
# Validate the body manually since AgentCore may omit Content-Type.
|
|
273
|
+
encoder = EventEncoder(accept=request.headers.get("accept") or "")
|
|
274
|
+
raw = await request.body()
|
|
275
|
+
try:
|
|
276
|
+
input_data = RunAgentInput.model_validate_json(raw)
|
|
277
|
+
except Exception as exc:
|
|
278
|
+
message = f"Invalid RunAgentInput: {str(exc)[:200]}"
|
|
279
|
+
|
|
280
|
+
async def _bad():
|
|
281
|
+
yield encoder.encode(RunErrorEvent(type=EventType.RUN_ERROR, message=message, code="BAD_REQUEST"))
|
|
282
|
+
|
|
283
|
+
return StreamingResponse(_bad(), media_type=encoder.get_content_type())
|
|
284
|
+
|
|
285
|
+
session_id = request.headers.get(SESSION_ID_HEADER) or get_current_session_id()
|
|
286
|
+
|
|
287
|
+
async def event_generator():
|
|
288
|
+
# Re-bind the session: the streaming body runs outside the middleware.
|
|
289
|
+
with session_id_context(session_id or str(uuid.uuid4())):
|
|
290
|
+
async for event in request.app.state.agui_agent.run(input_data):
|
|
291
|
+
try:
|
|
292
|
+
yield encoder.encode(event)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
error_event = RunErrorEvent(
|
|
295
|
+
type=EventType.RUN_ERROR,
|
|
296
|
+
message=f"Encoding error: {e}",
|
|
297
|
+
code="ENCODING_ERROR",
|
|
298
|
+
)
|
|
299
|
+
yield encoder.encode(error_event)
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
return StreamingResponse(event_generator(), media_type=encoder.get_content_type())
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@app.get("/ping")
|
|
306
|
+
async def ping():
|
|
307
|
+
return {"status": "healthy"}`;
|
|
308
|
+
const STRANDS_AGUI_COMMENTS_OLD = [
|
|
309
|
+
'# Create AG-UI agent wrapper',
|
|
310
|
+
'# Create FastAPI app with AG-UI endpoint and health check'
|
|
311
|
+
];
|
|
312
|
+
const migrateStrandsAgUiAgent = async (tree, dir, nextSteps)=>{
|
|
313
|
+
const mainPath = joinPathFragments(dir, 'main.py');
|
|
314
|
+
if (!tree.exists(mainPath)) return;
|
|
315
|
+
const mainContents = tree.read(mainPath, 'utf-8') ?? '';
|
|
316
|
+
if (!mainContents.includes(STRANDS_AGUI_MARKER)) return;
|
|
317
|
+
const mod = await captureGritQLVariable(tree, mainPath, AGENT_CONNECTION_MODULE_PATTERN('session_id_context'), 'mod');
|
|
318
|
+
const agentClassName = await captureGritQLVariable(tree, mainPath, pyMatch(STRANDS_AGUI_AGENT_OLD), 'name');
|
|
319
|
+
const ready = !!mod && !!agentClassName && await allMatch(tree, [
|
|
320
|
+
[
|
|
321
|
+
mainPath,
|
|
322
|
+
pyMatch(STRANDS_AGUI_IMPORT_OLD)
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
mainPath,
|
|
326
|
+
pyMatch('_agent_ctx = get_agent()')
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
mainPath,
|
|
330
|
+
pyMatch('_agent = _agent_ctx.__enter__()')
|
|
331
|
+
],
|
|
332
|
+
[
|
|
333
|
+
mainPath,
|
|
334
|
+
pyMatch(STRANDS_AGUI_AGENT_OLD)
|
|
335
|
+
],
|
|
336
|
+
[
|
|
337
|
+
mainPath,
|
|
338
|
+
pyMatch(STRANDS_AGUI_APP_OLD)
|
|
339
|
+
],
|
|
340
|
+
[
|
|
341
|
+
mainPath,
|
|
342
|
+
pyMatch(STRANDS_AGUI_MIDDLEWARE_OLD)
|
|
343
|
+
]
|
|
344
|
+
]);
|
|
345
|
+
if (!ready) {
|
|
346
|
+
nextSteps.push(`${mainPath}: diverged from the generated shape - left untouched. Manually rebuild this file without \`create_strands_app\`: a \`lifespan\` handler storing the wrapper on \`app.state.agui_agent\`, and inlined \`/invocations\`/\`/ping\` routes with Content-Type-independent body parsing (see the py#agent generator's strands/ag-ui template).`);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
350
|
+
'asynccontextmanager'
|
|
351
|
+
], 'contextlib');
|
|
352
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
353
|
+
'EventType',
|
|
354
|
+
'RunAgentInput',
|
|
355
|
+
'RunErrorEvent'
|
|
356
|
+
], 'ag_ui.core');
|
|
357
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
358
|
+
'EventEncoder'
|
|
359
|
+
], 'ag_ui.encoder');
|
|
360
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
361
|
+
'FastAPI'
|
|
362
|
+
], 'fastapi');
|
|
363
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
364
|
+
'CORSMiddleware'
|
|
365
|
+
], 'fastapi.middleware.cors');
|
|
366
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
367
|
+
'StreamingResponse'
|
|
368
|
+
], 'fastapi.responses');
|
|
369
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
370
|
+
'get_current_session_id'
|
|
371
|
+
], mod);
|
|
372
|
+
await applyGritQL(tree, mainPath, pyRewrite(STRANDS_AGUI_IMPORT_OLD, 'from ag_ui_strands import StrandsAgent'));
|
|
373
|
+
await applyGritQL(tree, mainPath, pyDelete('_agent_ctx = get_agent()'));
|
|
374
|
+
await applyGritQL(tree, mainPath, pyDelete('_agent = _agent_ctx.__enter__()'));
|
|
375
|
+
await applyGritQL(tree, mainPath, pyRewrite(STRANDS_AGUI_AGENT_OLD, STRANDS_AGUI_AGENT_NEW));
|
|
376
|
+
for (const comment of STRANDS_AGUI_COMMENTS_OLD){
|
|
377
|
+
await applyGritQL(tree, mainPath, pyDelete(comment));
|
|
378
|
+
}
|
|
379
|
+
await applyGritQL(tree, mainPath, pyRewrite(STRANDS_AGUI_APP_OLD, strandsAguiAppNew(agentClassName)));
|
|
380
|
+
await applyGritQL(tree, mainPath, pyRewrite(STRANDS_AGUI_MIDDLEWARE_OLD, STRANDS_AGUI_ROUTES_NEW));
|
|
381
|
+
};
|
|
382
|
+
/** This agent's ComponentMetadata entries. */ const findAgentComponents = (components)=>(components ?? []).filter((component)=>component.generator === PY_AGENT_GENERATOR_INFO.id);
|
|
383
|
+
export default async function migration(tree) {
|
|
384
|
+
const nextSteps = [];
|
|
385
|
+
for (const project of getProjects(tree).values()){
|
|
386
|
+
const components = findAgentComponents(project.metadata?.components);
|
|
387
|
+
for (const component of components){
|
|
388
|
+
if (!component.path) continue;
|
|
389
|
+
const dir = joinPathFragments(project.root, component.path);
|
|
390
|
+
const framework = component.framework ?? 'strands';
|
|
391
|
+
if (component.protocol === 'http') {
|
|
392
|
+
if (framework === 'langchain') {
|
|
393
|
+
await migrateLangchainHttpAgent(tree, dir, nextSteps);
|
|
394
|
+
} else {
|
|
395
|
+
await migrateStrandsHttpAgent(tree, dir, nextSteps);
|
|
396
|
+
}
|
|
397
|
+
} else if (component.protocol === 'ag-ui') {
|
|
398
|
+
if (framework === 'langchain') {
|
|
399
|
+
await migrateLangchainAgUiAgent(tree, dir, nextSteps);
|
|
400
|
+
} else {
|
|
401
|
+
await migrateStrandsAgUiAgent(tree, dir, nextSteps);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
await formatFilesInSubtree(tree);
|
|
407
|
+
return {
|
|
408
|
+
nextSteps
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/py-agent-lifespan-construction/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { PY_AGENT_GENERATOR_INFO } from '../../../py/agent/generator';\nimport {\n addPythonDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\n matchGritQL,\n} from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport type { ComponentMetadata } from '../../../utils/nx';\n\n/**\n * Move py#agent HTTP/AG-UI agent construction out of module import time and\n * into a FastAPI `lifespan` handler, stored on `app.state`.\n *\n * Building the agent eagerly at import time breaks any codepath that imports\n * the module before `RUNTIME_CONFIG_APP_ID`/AgentCore env vars are set (e.g.\n * OpenAPI spec generation, tests) - and, for AgentCore, construction belongs\n * at container/app startup rather than import, since AgentCore hands each\n * session its own container. The A2A protocol is unaffected: `with_session_id`\n * only touches the agent factory lazily on first use, so its eager\n * `__enter__()` was never actually eager in practice.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: every rewrite this migration makes to a file\n * is preceded by a read-only `matchGritQL` check of every pattern it will\n * need - so a diverged file is reported via `nextSteps` and left completely\n * untouched, rather than partially rewritten into a broken state.\n * - Idempotent: re-running must be a no-op (each rewrite's own shape change\n * means its pattern no longer matches on a second pass).\n * - Format what you write: finish with `formatFilesInSubtree`.\n */\n\n/** A read-only GritQL match against a python file (no rewrite). */\nconst pyMatch = (snippet: string) => `language python\\n\\`${snippet}\\``;\n\n/** A GritQL rewrite deleting the matched python snippet entirely. */\nconst pyDelete = (snippet: string) => `${pyMatch(snippet)} => .`;\n\n/**\n * A GritQL rewrite of one python snippet into another. Multi-line\n * replacements go through `raw` so GritQL's own reformatting doesn't fight\n * the exact shape being written (formatFilesInSubtree normalizes it after).\n */\nconst pyRewrite = (from: string, to: string, where?: string): string => {\n const replacement = to.includes('\\n') ? `raw\\`${to}\\`` : `\\`${to}\\``;\n return `${pyMatch(from)} => ${replacement}${where ? ` where { ${where} }` : ''}`;\n};\n\n/** All checks must match before anything is written for that agent. */\nconst allMatch = async (\n tree: Tree,\n checks: ReadonlyArray<readonly [filePath: string, pattern: string]>,\n): Promise<boolean> => {\n for (const [filePath, pattern] of checks) {\n if (!(await matchGritQL(tree, filePath, pattern))) return false;\n }\n return true;\n};\n\nconst AGENT_CONNECTION_MODULE_PATTERN = (names: string) =>\n pyMatch(`from $mod import ${names}`);\n\nconst STRANDS_HTTP_MARKER = '_agent_ctx = with_session_id(';\nconst LANGCHAIN_HTTP_MARKER = '_graph = get_agent()';\nconst STRANDS_AGUI_MARKER = 'create_strands_app';\nconst LANGCHAIN_AGUI_MARKER = '_graph = get_agent()';\n\n// Shared by strands/http and langchain/http - only the lifespan body differs.\nconst HTTP_INIT_APP_OLD = `app = FastAPI(\n title=\"$name\",\n responses={500: {\"model\": InternalServerErrorDetails}},\n generate_unique_id_function=lambda route: route.name,\n)`;\n\n// --- Strands HTTP -----------------------------------------------------------\n\nconst STRANDS_HTTP_MAIN_CTX_OLD = `_agent_ctx = with_session_id(\n get_agent,\n name=\"$name\",\n description=\"A Strands Agent exposed via HTTP streaming.\",\n)`;\n\nconst STRANDS_HTTP_INIT_APP_NEW = `@asynccontextmanager\nasync def lifespan(app: FastAPI):\n with with_session_id(\n get_agent,\n name=\"$name\",\n description=\"A Strands Agent exposed via HTTP streaming.\",\n ) as agent:\n app.state.agent = agent\n yield\n\n\napp = FastAPI(\n title=\"$name\",\n responses={500: {\"model\": InternalServerErrorDetails}},\n generate_unique_id_function=lambda route: route.name,\n lifespan=lifespan,\n)`;\n\nconst migrateStrandsHttpAgent = async (\n tree: Tree,\n dir: string,\n nextSteps: string[],\n): Promise<void> => {\n const initPath = joinPathFragments(dir, 'init.py');\n const mainPath = joinPathFragments(dir, 'main.py');\n if (!tree.exists(initPath) || !tree.exists(mainPath)) return;\n\n const mainContents = tree.read(mainPath, 'utf-8') ?? '';\n if (!mainContents.includes(STRANDS_HTTP_MARKER)) return;\n\n const mod = await captureGritQLVariable(\n tree,\n mainPath,\n AGENT_CONNECTION_MODULE_PATTERN('session_id_context, with_session_id'),\n 'mod',\n );\n\n const ready =\n !!mod &&\n (await allMatch(tree, [\n [mainPath, pyMatch(STRANDS_HTTP_MAIN_CTX_OLD)],\n [mainPath, pyMatch('_agent = _agent_ctx.__enter__()')],\n [mainPath, pyMatch('from .agent import get_agent')],\n [mainPath, pyMatch('_agent.stream_async($prompt)')],\n [initPath, pyMatch(HTTP_INIT_APP_OLD)],\n ]));\n\n if (!ready) {\n nextSteps.push(\n `${mainPath}: diverged from the generated shape - left untouched, along with ${initPath}. Manually move agent construction into an \\`init.py\\` \\`lifespan\\` handler storing the agent on \\`app.state.agent\\` (see the py#agent generator's strands/http template).`,\n );\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n initPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n await addPythonDestructuredImport(tree, initPath, ['get_agent'], '.agent');\n await addPythonDestructuredImport(tree, initPath, ['with_session_id'], mod);\n await applyGritQL(\n tree,\n initPath,\n pyRewrite(HTTP_INIT_APP_OLD, STRANDS_HTTP_INIT_APP_NEW),\n );\n\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n 'from $mod import session_id_context, with_session_id',\n 'from $mod import session_id_context',\n ),\n );\n await applyGritQL(tree, mainPath, pyDelete('from .agent import get_agent'));\n await applyGritQL(tree, mainPath, pyDelete(STRANDS_HTTP_MAIN_CTX_OLD));\n await applyGritQL(\n tree,\n mainPath,\n pyDelete('_agent = _agent_ctx.__enter__()'),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n '_agent.stream_async($prompt)',\n 'app.state.agent.stream_async($prompt)',\n ),\n );\n};\n\n// --- LangChain HTTP ----------------------------------------------------------\n\nconst LANGCHAIN_HTTP_INIT_APP_NEW = `@asynccontextmanager\nasync def lifespan(app: FastAPI):\n app.state.graph = get_agent()\n yield\n\n\napp = FastAPI(\n title=\"$name\",\n responses={500: {\"model\": InternalServerErrorDetails}},\n generate_unique_id_function=lambda route: route.name,\n lifespan=lifespan,\n)`;\n\nconst migrateLangchainHttpAgent = async (\n tree: Tree,\n dir: string,\n nextSteps: string[],\n): Promise<void> => {\n const initPath = joinPathFragments(dir, 'init.py');\n const mainPath = joinPathFragments(dir, 'main.py');\n if (!tree.exists(initPath) || !tree.exists(mainPath)) return;\n\n const mainContents = tree.read(mainPath, 'utf-8') ?? '';\n if (!mainContents.includes(LANGCHAIN_HTTP_MARKER)) return;\n\n const ready = await allMatch(tree, [\n [mainPath, pyMatch('_graph = get_agent()')],\n [mainPath, pyMatch('from .agent import get_agent')],\n [mainPath, pyMatch('_graph.astream($args)')],\n [initPath, pyMatch(HTTP_INIT_APP_OLD)],\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${mainPath}: diverged from the generated shape - left untouched, along with ${initPath}. Manually wrap \\`get_agent()\\` in an \\`init.py\\` \\`lifespan\\` handler storing the graph on \\`app.state.graph\\` (see the py#agent generator's langchain/http template).`,\n );\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n initPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n await addPythonDestructuredImport(tree, initPath, ['get_agent'], '.agent');\n await applyGritQL(\n tree,\n initPath,\n pyRewrite(HTTP_INIT_APP_OLD, LANGCHAIN_HTTP_INIT_APP_NEW),\n );\n\n await applyGritQL(tree, mainPath, pyDelete('from .agent import get_agent'));\n await applyGritQL(tree, mainPath, pyDelete('_graph = get_agent()'));\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite('_graph.astream($args)', 'app.state.graph.astream($args)'),\n );\n};\n\n// --- LangChain AG-UI ---------------------------------------------------------\n\nconst LANGCHAIN_AGUI_AGENT_OLD = `agui_agent = LangGraphAgent(\n name=\"$name\",\n graph=_graph,\n description=\"A LangChain/LangGraph Agent exposed via the AG-UI protocol.\",\n)`;\n\nconst LANGCHAIN_AGUI_AGENT_NEW = `@asynccontextmanager\nasync def lifespan(app: FastAPI):\n graph = get_agent()\n app.state.agui_agent = LangGraphAgent(\n name=\"$name\",\n graph=graph,\n description=\"A LangChain/LangGraph Agent exposed via the AG-UI protocol.\",\n )\n yield`;\n\nconst migrateLangchainAgUiAgent = async (\n tree: Tree,\n dir: string,\n nextSteps: string[],\n): Promise<void> => {\n const mainPath = joinPathFragments(dir, 'main.py');\n if (!tree.exists(mainPath)) return;\n\n const mainContents = tree.read(mainPath, 'utf-8') ?? '';\n if (!mainContents.includes(LANGCHAIN_AGUI_MARKER)) return;\n\n const ready = await allMatch(tree, [\n [mainPath, pyMatch('_graph = get_agent()')],\n [mainPath, pyMatch(LANGCHAIN_AGUI_AGENT_OLD)],\n [mainPath, pyMatch('agui_agent.run($input)')],\n [mainPath, pyMatch('app = FastAPI(title=\"$name\")')],\n ]);\n\n if (!ready) {\n nextSteps.push(\n `${mainPath}: diverged from the generated shape - left untouched. Manually wrap graph + \\`LangGraphAgent\\` construction in a \\`lifespan\\` handler storing the wrapper on \\`app.state.agui_agent\\` (see the py#agent generator's langchain/ag-ui template).`,\n );\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n\n await applyGritQL(tree, mainPath, pyDelete('_graph = get_agent()'));\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(LANGCHAIN_AGUI_AGENT_OLD, LANGCHAIN_AGUI_AGENT_NEW),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n 'agui_agent.run($input)',\n 'request.app.state.agui_agent.run($input)',\n ),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n 'app = FastAPI(title=\"$name\")',\n 'app = FastAPI(title=\"$name\", lifespan=lifespan)',\n ),\n );\n};\n\n// --- Strands AG-UI -----------------------------------------------------------\n// The biggest of the four: `ag_ui_strands.create_strands_app` is dropped\n// entirely for an inlined FastAPI app (matching upstream `ag_ui_strands`,\n// plus a Content-Type-independent body-parsing fix and an explicit session\n// re-bind `create_strands_app` was missing), so most of the file is new.\n\nconst STRANDS_AGUI_IMPORT_OLD =\n 'from ag_ui_strands import StrandsAgent, create_strands_app';\n\nconst STRANDS_AGUI_AGENT_OLD = `agui_agent = StrandsAgent(\n agent=_agent,\n name=\"$name\",\n description=\"A Strands Agent exposed via the AG-UI protocol.\",\n)`;\n\nconst STRANDS_AGUI_AGENT_NEW = `@asynccontextmanager\nasync def lifespan(app: FastAPI):\n with get_agent() as agent:\n app.state.agui_agent = StrandsAgent(\n agent=agent,\n name=\"$name\",\n description=\"A Strands Agent exposed via the AG-UI protocol.\",\n )\n yield`;\n\nconst STRANDS_AGUI_APP_OLD =\n 'app = create_strands_app(agui_agent, path=\"/invocations\")';\n\n// `$name` isn't bound within this match (unlike STRANDS_AGUI_AGENT_OLD above),\n// so the title is built from a name captured separately rather than reused as\n// a metavariable - GritQL leaves a rewrite whose replacement references an\n// unbound metavariable unapplied.\nconst strandsAguiAppNew = (\n name: string,\n) => `app = FastAPI(title=\"AWS Strands - ${name}\", lifespan=lifespan)\napp.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)`;\n\nconst STRANDS_AGUI_MIDDLEWARE_OLD = 'app.add_middleware(_SessionIdMiddleware)';\n\nconst STRANDS_AGUI_ROUTES_NEW = `app.add_middleware(_SessionIdMiddleware)\n\n\n@app.post(\"/invocations\")\nasync def invocations(request: Request):\n # Validate the body manually since AgentCore may omit Content-Type.\n encoder = EventEncoder(accept=request.headers.get(\"accept\") or \"\")\n raw = await request.body()\n try:\n input_data = RunAgentInput.model_validate_json(raw)\n except Exception as exc:\n message = f\"Invalid RunAgentInput: {str(exc)[:200]}\"\n\n async def _bad():\n yield encoder.encode(RunErrorEvent(type=EventType.RUN_ERROR, message=message, code=\"BAD_REQUEST\"))\n\n return StreamingResponse(_bad(), media_type=encoder.get_content_type())\n\n session_id = request.headers.get(SESSION_ID_HEADER) or get_current_session_id()\n\n async def event_generator():\n # Re-bind the session: the streaming body runs outside the middleware.\n with session_id_context(session_id or str(uuid.uuid4())):\n async for event in request.app.state.agui_agent.run(input_data):\n try:\n yield encoder.encode(event)\n except Exception as e:\n error_event = RunErrorEvent(\n type=EventType.RUN_ERROR,\n message=f\"Encoding error: {e}\",\n code=\"ENCODING_ERROR\",\n )\n yield encoder.encode(error_event)\n break\n\n return StreamingResponse(event_generator(), media_type=encoder.get_content_type())\n\n\n@app.get(\"/ping\")\nasync def ping():\n return {\"status\": \"healthy\"}`;\n\nconst STRANDS_AGUI_COMMENTS_OLD = [\n '# Create AG-UI agent wrapper',\n '# Create FastAPI app with AG-UI endpoint and health check',\n];\n\nconst migrateStrandsAgUiAgent = async (\n tree: Tree,\n dir: string,\n nextSteps: string[],\n): Promise<void> => {\n const mainPath = joinPathFragments(dir, 'main.py');\n if (!tree.exists(mainPath)) return;\n\n const mainContents = tree.read(mainPath, 'utf-8') ?? '';\n if (!mainContents.includes(STRANDS_AGUI_MARKER)) return;\n\n const mod = await captureGritQLVariable(\n tree,\n mainPath,\n AGENT_CONNECTION_MODULE_PATTERN('session_id_context'),\n 'mod',\n );\n const agentClassName = await captureGritQLVariable(\n tree,\n mainPath,\n pyMatch(STRANDS_AGUI_AGENT_OLD),\n 'name',\n );\n\n const ready =\n !!mod &&\n !!agentClassName &&\n (await allMatch(tree, [\n [mainPath, pyMatch(STRANDS_AGUI_IMPORT_OLD)],\n [mainPath, pyMatch('_agent_ctx = get_agent()')],\n [mainPath, pyMatch('_agent = _agent_ctx.__enter__()')],\n [mainPath, pyMatch(STRANDS_AGUI_AGENT_OLD)],\n [mainPath, pyMatch(STRANDS_AGUI_APP_OLD)],\n [mainPath, pyMatch(STRANDS_AGUI_MIDDLEWARE_OLD)],\n ]));\n\n if (!ready) {\n nextSteps.push(\n `${mainPath}: diverged from the generated shape - left untouched. Manually rebuild this file without \\`create_strands_app\\`: a \\`lifespan\\` handler storing the wrapper on \\`app.state.agui_agent\\`, and inlined \\`/invocations\\`/\\`/ping\\` routes with Content-Type-independent body parsing (see the py#agent generator's strands/ag-ui template).`,\n );\n return;\n }\n\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['asynccontextmanager'],\n 'contextlib',\n );\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['EventType', 'RunAgentInput', 'RunErrorEvent'],\n 'ag_ui.core',\n );\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['EventEncoder'],\n 'ag_ui.encoder',\n );\n await addPythonDestructuredImport(tree, mainPath, ['FastAPI'], 'fastapi');\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['CORSMiddleware'],\n 'fastapi.middleware.cors',\n );\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['StreamingResponse'],\n 'fastapi.responses',\n );\n await addPythonDestructuredImport(\n tree,\n mainPath,\n ['get_current_session_id'],\n mod,\n );\n\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(\n STRANDS_AGUI_IMPORT_OLD,\n 'from ag_ui_strands import StrandsAgent',\n ),\n );\n await applyGritQL(tree, mainPath, pyDelete('_agent_ctx = get_agent()'));\n await applyGritQL(\n tree,\n mainPath,\n pyDelete('_agent = _agent_ctx.__enter__()'),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(STRANDS_AGUI_AGENT_OLD, STRANDS_AGUI_AGENT_NEW),\n );\n for (const comment of STRANDS_AGUI_COMMENTS_OLD) {\n await applyGritQL(tree, mainPath, pyDelete(comment));\n }\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(STRANDS_AGUI_APP_OLD, strandsAguiAppNew(agentClassName)),\n );\n await applyGritQL(\n tree,\n mainPath,\n pyRewrite(STRANDS_AGUI_MIDDLEWARE_OLD, STRANDS_AGUI_ROUTES_NEW),\n );\n};\n\n/** This agent's ComponentMetadata entries. */\nconst findAgentComponents = (\n components: ComponentMetadata[] | undefined,\n): ComponentMetadata[] =>\n (components ?? []).filter(\n (component) => component.generator === PY_AGENT_GENERATOR_INFO.id,\n );\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const components = findAgentComponents(\n (project.metadata as { components?: ComponentMetadata[] })?.components,\n );\n\n for (const component of components) {\n if (!component.path) continue;\n const dir = joinPathFragments(project.root, component.path);\n const framework = component.framework ?? 'strands';\n\n if (component.protocol === 'http') {\n if (framework === 'langchain') {\n await migrateLangchainHttpAgent(tree, dir, nextSteps);\n } else {\n await migrateStrandsHttpAgent(tree, dir, nextSteps);\n }\n } else if (component.protocol === 'ag-ui') {\n if (framework === 'langchain') {\n await migrateLangchainAgUiAgent(tree, dir, nextSteps);\n } else {\n await migrateStrandsAgUiAgent(tree, dir, nextSteps);\n }\n }\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","PY_AGENT_GENERATOR_INFO","addPythonDestructuredImport","applyGritQL","captureGritQLVariable","matchGritQL","formatFilesInSubtree","pyMatch","snippet","pyDelete","pyRewrite","from","to","where","replacement","includes","allMatch","tree","checks","filePath","pattern","AGENT_CONNECTION_MODULE_PATTERN","names","STRANDS_HTTP_MARKER","LANGCHAIN_HTTP_MARKER","STRANDS_AGUI_MARKER","LANGCHAIN_AGUI_MARKER","HTTP_INIT_APP_OLD","STRANDS_HTTP_MAIN_CTX_OLD","STRANDS_HTTP_INIT_APP_NEW","migrateStrandsHttpAgent","dir","nextSteps","initPath","mainPath","exists","mainContents","read","mod","ready","push","LANGCHAIN_HTTP_INIT_APP_NEW","migrateLangchainHttpAgent","LANGCHAIN_AGUI_AGENT_OLD","LANGCHAIN_AGUI_AGENT_NEW","migrateLangchainAgUiAgent","STRANDS_AGUI_IMPORT_OLD","STRANDS_AGUI_AGENT_OLD","STRANDS_AGUI_AGENT_NEW","STRANDS_AGUI_APP_OLD","strandsAguiAppNew","name","STRANDS_AGUI_MIDDLEWARE_OLD","STRANDS_AGUI_ROUTES_NEW","STRANDS_AGUI_COMMENTS_OLD","migrateStrandsAgUiAgent","agentClassName","comment","findAgentComponents","components","filter","component","generator","id","migration","project","values","metadata","path","root","framework","protocol"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.50/0001-modernize-function-props-cast/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n} from '../../../utils/shared-constructs-constants';\n\n/**\n * Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs\n *\n * The legacy `<FunctionProps>{ ... }` cast form is not supported by GritQL's\n * TypeScript parser, which silently breaks parsing (and therefore matching)\n * for the entire containing file. Since several other migrations rely on\n * GritQL to update these API construct files, this cast is replaced here with\n * the equivalent, GritQL-compatible `{ ... } as FunctionProps` form. This is\n * matched as an exact literal (rather than via GritQL) since GritQL cannot\n * parse the file while the legacy cast is still present.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, or consider a\n * hybrid migration, rather than clobbering the user's changes.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files your\n * migration wrote are formatted correctly.\n */\n\nconst APIS_APP_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/app/apis`;\n\nconst OLD_CAST_OPEN = 'defaultIntegrationOptions: <FunctionProps>{';\nconst NEW_CAST_OPEN = 'defaultIntegrationOptions: {';\nconst OLD_CAST_CLOSE = '\\n },\\n buildDefaultIntegration:';\nconst NEW_CAST_CLOSE =\n '\\n } as FunctionProps,\\n buildDefaultIntegration:';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(APIS_APP_DIR)) {\n return { nextSteps };\n }\n\n const apiAppFiles: string[] = [];\n visitNotIgnoredFiles(tree, APIS_APP_DIR, (filePath) => {\n apiAppFiles.push(filePath);\n });\n\n for (const filePath of apiAppFiles) {\n if (!filePath.endsWith('.ts') || filePath.endsWith('/index.ts')) {\n continue;\n }\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes(OLD_CAST_OPEN)) {\n // Already migrated, or doesn't use this shape.\n continue;\n }\n if (!contents.includes(OLD_CAST_CLOSE)) {\n nextSteps.push(\n `${filePath}: the defaultIntegrationOptions cast has diverged from the generated shape - left untouched. Manually replace \\`<FunctionProps>{ ... }\\` with \\`{ ... } as FunctionProps\\`.`,\n );\n continue;\n }\n tree.write(\n filePath,\n contents\n .replace(OLD_CAST_OPEN, NEW_CAST_OPEN)\n .replace(OLD_CAST_CLOSE, NEW_CAST_CLOSE),\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["visitNotIgnoredFiles","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","APIS_APP_DIR","OLD_CAST_OPEN","NEW_CAST_OPEN","OLD_CAST_CLOSE","NEW_CAST_CLOSE","migration","tree","nextSteps","exists","apiAppFiles","filePath","push","endsWith","contents","read","includes","write","replace"],"mappings":"AAAA;;;CAGC,GACD,SAGEA,oBAAoB,QACf,aAAa;AACpB,SAASC,oBAAoB,QAAQ,2BAAwB;AAC7D,SACEC,YAAY,EACZC,qBAAqB,QAChB,gDAA6C;AAEpD;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED,MAAMC,eAAe,GAAGF,aAAa,CAAC,EAAEC,sBAAsB,aAAa,CAAC;AAE5E,MAAME,gBAAgB;AACtB,MAAMC,gBAAgB;AACtB,MAAMC,iBAAiB;AACvB,MAAMC,iBACJ;AAEF,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,IAAI,CAACD,KAAKE,MAAM,CAACR,eAAe;QAC9B,OAAO;YAAEO;QAAU;IACrB;IAEA,MAAME,cAAwB,EAAE;IAChCb,qBAAqBU,MAAMN,cAAc,CAACU;QACxCD,YAAYE,IAAI,CAACD;IACnB;IAEA,KAAK,MAAMA,YAAYD,YAAa;QAClC,IAAI,CAACC,SAASE,QAAQ,CAAC,UAAUF,SAASE,QAAQ,CAAC,cAAc;YAC/D;QACF;QACA,MAAMC,WAAWP,KAAKQ,IAAI,CAACJ,UAAU,YAAY;QACjD,IAAI,CAACG,SAASE,QAAQ,CAACd,gBAAgB;YAErC;QACF;QACA,IAAI,CAACY,SAASE,QAAQ,CAACZ,iBAAiB;YACtCI,UAAUI,IAAI,CACZ,GAAGD,SAAS,2KAA2K,CAAC;YAE1L;QACF;QACAJ,KAAKU,KAAK,CACRN,UACAG,SACGI,OAAO,CAAChB,eAAeC,eACvBe,OAAO,CAACd,gBAAgBC;IAE/B;IAEA,MAAMP,qBAAqBS;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.50/0002-restrict-cors-to-custom-domains/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport {\n addDestructuredImport,\n addStarExport,\n applyGritQL,\n} from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport { isEsmWorkspace } from '../../../utils/module-format';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n} from '../../../utils/shared-constructs-constants';\n\n/**\n * Include CloudFront custom domain aliases in restrictCorsTo and UserIdentity callback URLs\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, rather than\n * clobbering the user's changes.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files your\n * migration wrote are formatted correctly.\n */\n\nconst CORE_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core`;\nconst APIS_APP_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/app/apis`;\nconst CLOUDFRONT_CORE_FILE = `${CORE_DIR}/cloudfront.ts`;\nconst CORE_INDEX_FILE = `${CORE_DIR}/index.ts`;\nconst USER_IDENTITY_FILE = `${CORE_DIR}/user-identity.ts`;\n\nconst CLOUDFRONT_HELPER_CONTENT = `import { CfnDistribution, Distribution } from 'aws-cdk-lib/aws-cloudfront';\n\n/**\n * Finds the domain names associated with a CloudFront distribution.\n *\n * Includes the distribution's default \\`*.cloudfront.net\\` domain name plus any custom\n * domain names (aliases) configured on it.\n */\nexport const findCloudFrontDomainNames = (\n distribution: Distribution,\n): string[] => {\n const cfnDistribution = distribution.node.defaultChild as CfnDistribution;\n const distributionConfig =\n cfnDistribution.distributionConfig as CfnDistribution.DistributionConfigProperty;\n return [distribution.domainName, ...(distributionConfig.aliases ?? [])];\n};\n`;\n\n// Rewrites the `restrictCorsTo` body as produced by generators prior to this fix.\nconst RESTRICT_CORS_TO_GRITQL_PATTERN =\n \"`origins.map(($o) => typeof $o === 'string' ? $o : 'cloudFrontDistribution' in $o ? $branch1 : $branch2)` where { $branch1 <: contains `distributionDomainName`, $branch2 <: contains `distributionDomainName` } => raw`origins.flatMap(($o) => typeof $o === 'string' ? [$o] : findCloudFrontDomainNames('cloudFrontDistribution' in $o ? $o.cloudFrontDistribution : $o).map((domain) => \\\\`https://${domain}\\\\`))`\";\n\n// Rewrites UserIdentity's callback/logout URL logic as produced by generators\n// prior to this fix.\nconst USER_IDENTITY_CALLBACK_URLS_GRITQL_PATTERN =\n '`this.findCloudFrontDomainNames()` => `Stack.of(this).node.findAll().filter((child): child is Distribution => child instanceof Distribution).flatMap(findCloudFrontDomainNames)`';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(CORE_INDEX_FILE)) {\n // No common/constructs shared library in this workspace - nothing to migrate.\n return { nextSteps };\n }\n\n const esm = isEsmWorkspace(tree);\n const apiCloudFrontImportSpecifier = esm\n ? '../../core/cloudfront.js'\n : '../../core/cloudfront';\n const coreCloudFrontImportSpecifier = esm\n ? './cloudfront.js'\n : './cloudfront';\n\n if (!tree.exists(CLOUDFRONT_CORE_FILE)) {\n tree.write(CLOUDFRONT_CORE_FILE, CLOUDFRONT_HELPER_CONTENT);\n }\n await addStarExport(tree, CORE_INDEX_FILE, './cloudfront.js');\n\n const apiAppFiles: string[] = [];\n visitNotIgnoredFiles(tree, APIS_APP_DIR, (filePath) => {\n apiAppFiles.push(filePath);\n });\n\n for (const filePath of apiAppFiles) {\n if (!filePath.endsWith('.ts') || filePath.endsWith('/index.ts')) {\n continue;\n }\n const rewrote = await applyGritQL(\n tree,\n filePath,\n RESTRICT_CORS_TO_GRITQL_PATTERN,\n );\n if (rewrote) {\n await addDestructuredImport(\n tree,\n filePath,\n ['findCloudFrontDomainNames'],\n apiCloudFrontImportSpecifier,\n );\n continue;\n }\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('findCloudFrontDomainNames(')) {\n nextSteps.push(\n `${filePath}: restrictCorsTo has diverged from the generated shape - left untouched. Manually apply the CloudFront custom domain fix (see findCloudFrontDomainNames in common/constructs/src/core/cloudfront.ts).`,\n );\n }\n // Otherwise already migrated - silent skip.\n }\n\n if (tree.exists(USER_IDENTITY_FILE)) {\n const rewrote = await applyGritQL(\n tree,\n USER_IDENTITY_FILE,\n USER_IDENTITY_CALLBACK_URLS_GRITQL_PATTERN,\n );\n if (rewrote) {\n await applyGritQL(\n tree,\n USER_IDENTITY_FILE,\n \"`import { CfnDistribution, Distribution } from 'aws-cdk-lib/aws-cloudfront'` => `import { Distribution } from 'aws-cdk-lib/aws-cloudfront';`\",\n );\n await applyGritQL(\n tree,\n USER_IDENTITY_FILE,\n \"or { `// Includes each distribution's default domain name plus any custom domain names (aliases) configured on it.` => ., `private findCloudFrontDomainNames = (): string[] => $body` => . }\",\n );\n await addDestructuredImport(\n tree,\n USER_IDENTITY_FILE,\n ['findCloudFrontDomainNames'],\n coreCloudFrontImportSpecifier,\n );\n } else {\n const contents = tree.read(USER_IDENTITY_FILE, 'utf-8') ?? '';\n if (!contents.includes('.flatMap(findCloudFrontDomainNames)')) {\n nextSteps.push(\n `${USER_IDENTITY_FILE}: the callback URL logic has diverged from the generated shape - left untouched. Manually apply the shared findCloudFrontDomainNames helper (see common/constructs/src/core/cloudfront.ts).`,\n );\n }\n // Otherwise already migrated - silent skip.\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["visitNotIgnoredFiles","addDestructuredImport","addStarExport","applyGritQL","formatFilesInSubtree","isEsmWorkspace","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","CORE_DIR","APIS_APP_DIR","CLOUDFRONT_CORE_FILE","CORE_INDEX_FILE","USER_IDENTITY_FILE","CLOUDFRONT_HELPER_CONTENT","RESTRICT_CORS_TO_GRITQL_PATTERN","USER_IDENTITY_CALLBACK_URLS_GRITQL_PATTERN","migration","tree","nextSteps","exists","esm","apiCloudFrontImportSpecifier","coreCloudFrontImportSpecifier","write","apiAppFiles","filePath","push","endsWith","rewrote","contents","read","includes"],"mappings":"AAAA;;;CAGC,GACD,SAGEA,oBAAoB,QACf,aAAa;AACpB,SACEC,qBAAqB,EACrBC,aAAa,EACbC,WAAW,QACN,wBAAqB;AAC5B,SAASC,oBAAoB,QAAQ,2BAAwB;AAC7D,SAASC,cAAc,QAAQ,kCAA+B;AAC9D,SACEC,YAAY,EACZC,qBAAqB,QAChB,gDAA6C;AAEpD;;;;;;;;;;;;;;CAcC,GAED,MAAMC,WAAW,GAAGF,aAAa,CAAC,EAAEC,sBAAsB,SAAS,CAAC;AACpE,MAAME,eAAe,GAAGH,aAAa,CAAC,EAAEC,sBAAsB,aAAa,CAAC;AAC5E,MAAMG,uBAAuB,GAAGF,SAAS,cAAc,CAAC;AACxD,MAAMG,kBAAkB,GAAGH,SAAS,SAAS,CAAC;AAC9C,MAAMI,qBAAqB,GAAGJ,SAAS,iBAAiB,CAAC;AAEzD,MAAMK,4BAA4B,CAAC;;;;;;;;;;;;;;;;AAgBnC,CAAC;AAED,kFAAkF;AAClF,MAAMC,kCACJ;AAEF,8EAA8E;AAC9E,qBAAqB;AACrB,MAAMC,6CACJ;AAEF,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,IAAI,CAACD,KAAKE,MAAM,CAACR,kBAAkB;QACjC,8EAA8E;QAC9E,OAAO;YAAEO;QAAU;IACrB;IAEA,MAAME,MAAMf,eAAeY;IAC3B,MAAMI,+BAA+BD,MACjC,6BACA;IACJ,MAAME,gCAAgCF,MAClC,oBACA;IAEJ,IAAI,CAACH,KAAKE,MAAM,CAACT,uBAAuB;QACtCO,KAAKM,KAAK,CAACb,sBAAsBG;IACnC;IACA,MAAMX,cAAce,MAAMN,iBAAiB;IAE3C,MAAMa,cAAwB,EAAE;IAChCxB,qBAAqBiB,MAAMR,cAAc,CAACgB;QACxCD,YAAYE,IAAI,CAACD;IACnB;IAEA,KAAK,MAAMA,YAAYD,YAAa;QAClC,IAAI,CAACC,SAASE,QAAQ,CAAC,UAAUF,SAASE,QAAQ,CAAC,cAAc;YAC/D;QACF;QACA,MAAMC,UAAU,MAAMzB,YACpBc,MACAQ,UACAX;QAEF,IAAIc,SAAS;YACX,MAAM3B,sBACJgB,MACAQ,UACA;gBAAC;aAA4B,EAC7BJ;YAEF;QACF;QACA,MAAMQ,WAAWZ,KAAKa,IAAI,CAACL,UAAU,YAAY;QACjD,IAAI,CAACI,SAASE,QAAQ,CAAC,+BAA+B;YACpDb,UAAUQ,IAAI,CACZ,GAAGD,SAAS,qMAAqM,CAAC;QAEtN;IACA,4CAA4C;IAC9C;IAEA,IAAIR,KAAKE,MAAM,CAACP,qBAAqB;QACnC,MAAMgB,UAAU,MAAMzB,YACpBc,MACAL,oBACAG;QAEF,IAAIa,SAAS;YACX,MAAMzB,YACJc,MACAL,oBACA;YAEF,MAAMT,YACJc,MACAL,oBACA;YAEF,MAAMX,sBACJgB,MACAL,oBACA;gBAAC;aAA4B,EAC7BU;QAEJ,OAAO;YACL,MAAMO,WAAWZ,KAAKa,IAAI,CAAClB,oBAAoB,YAAY;YAC3D,IAAI,CAACiB,SAASE,QAAQ,CAAC,wCAAwC;gBAC7Db,UAAUQ,IAAI,CACZ,GAAGd,mBAAmB,2LAA2L,CAAC;YAEtN;QACA,4CAA4C;QAC9C;IACF;IAEA,MAAMR,qBAAqBa;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|