@aws/nx-plugin 1.0.0-rc.28 → 1.0.0-rc.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws/nx-plugin",
3
- "version": "1.0.0-rc.28",
3
+ "version": "1.0.0-rc.29",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/awslabs/nx-plugin-for-aws.git",
@@ -49,6 +49,7 @@ ${PACKAGE_MANAGERS.map((pm)=>buildNxCommand('<options>', pm)).join(' - \n')}
49
49
  - (Omit -D for production dependencies)
50
50
  - When specifying project names as arguments to generators, prefer the _fully qualified_ project name, for example \`@workspace-name/project-name\`. Check the \`project.json\` file for the specific package to find its fully qualified name
51
51
  - When no generator exists for a specific framework required, use the base \`ts#project\` and \`py#project\` generators and build on top.
52
+ - Leave the \`--infra\` option at its default value unless the user has explicitly instructed otherwise. Generators choose a sensible default type of infrastructure to deploy the project with, so only override \`--infra\` when the user has specified a particular requirement.
52
53
 
53
54
  ## Useful Commands
54
55
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/mcp-server/tools/general-guidance.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { NxGeneratorInfo } from '../../utils/generators';\nimport { IAC_PROVIDERS } from '../../utils/iac-providers';\nimport { buildNxCommand, fetchGuidePages } from '../generator-info';\nimport { PACKAGE_MANAGERS } from '../schema';\n\nexport const TOOL_SELECTION_GUIDE = `## Tool Selection Guide\n\n- Use the \\`general-guidance\\` tool for guidance and best practices for working with Nx and the Nx Plugin for AWS.\n- Use the \\`create-workspace-command\\` tool to discover how to create a workspace to start a new project.\n- Use the \\`list-generators\\` tool to discover the available generators and how to run them.\n- Use the \\`generator-guide\\` tool to retrieve detailed information about a specific generator.`;\n\n/**\n * Add a tool which provides general guidance for using Nx and the Nx Plugin for AWS\n */\nexport const addGeneralGuidanceTool = (\n server: McpServer,\n generators: NxGeneratorInfo[],\n) => {\n server.registerTool(\n 'general-guidance',\n {\n title: 'General Guidance',\n description:\n 'Tool for guidance and best practices for working with Nx and the Nx Plugin for AWS',\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: `# Nx Plugin for AWS Guidance\n\n${TOOL_SELECTION_GUIDE}\n\n## Getting Started\n\n- Choose a package manager first. You can choose between ${PACKAGE_MANAGERS.join(', ')}. It's recommended to use \"pnpm\" if the user has no preference\n- Choose an infrastructure as code (IaC) provider next. You can choose between ${IAC_PROVIDERS.join(', ')}. It's recommended to use CDK if the user has no preference\n- Next, you must create an Nx workspace. Use the \\`create-workspace-command\\` tool for more details, and provide it with your chosen package manager\n- After this, you can start scaffolding the main components of your application using generators. Use the \\`list-generators\\` tool to discover available generators, and the \\`generator-guide\\` tool for more detailed information about a specific generator\n\n## Nx Primer\n\n- Prefix nx commands with the appropriate prefix for your package manager, for example:\n${PACKAGE_MANAGERS.map((pm) => buildNxCommand('<options>', pm)).join(' - \\n')}\n- Each project in your workspace has a file named \\`project.json\\` which contains important project information such as its name, and defines the \"targets\" which can be run for that project, for example building or testing the project\n- Use the command \\`nx reset\\` to reset the Nx daemon when unexpected issues arise\n- After adding dependencies between TypeScript projects, use \\`nx sync\\` to ensure project references are set up correctly\n\n## General Instructions\n\n- Workspaces contain a single \\`package.json\\` file at the root which defines the dependencies for all projects. Therefore when installing dependencies, you must add these to the root workspace using the appropriate command for your package manager:\n - pnpm add -w -D <package>\n - yarn add -D <package>\n - npm install --legacy-peer-deps -D <package>\n - bun install -D <package>\n - (Omit -D for production dependencies)\n- When specifying project names as arguments to generators, prefer the _fully qualified_ project name, for example \\`@workspace-name/project-name\\`. Check the \\`project.json\\` file for the specific package to find its fully qualified name\n- When no generator exists for a specific framework required, use the base \\`ts#project\\` and \\`py#project\\` generators and build on top.\n\n## Useful Commands\n\n- Fix lint issues with \\`nx run-many --target lint --configuration=fix --all --output-style=stream\\`\n- Build all projects with \\`nx run-many --target build --all --output-style=stream\\`\n- Prefer importing the CDK constructs vended by generators in \\`packages/common/constructs\\` over writing your own\n\n## Best Practices\n\n- After running a generator, use the \\`nx show projects\\` command to check which projects have been added (if any)\n- Carefully examine the files that have been generated and always refer back to the generator guide when working in a generated project\n- Generate all projects into the \\`packages/\\` directory\n- After making changes to your projects, fix linting issues, then run a full build\n- When it's time to start testing a project, suggest to the user that infrastructure is deployed to AWS. For websites, if a runtime-config.json is needed, use the load:runtime-config target after a deployment to point a local website at a sandbox stack.\n\n## Batching Generators\n\nWhen scaffolding several projects in one go, chain generators to avoid a slow dependency install after every generator:\n\n- **Chain generators** with \\`&&\\` in a single command. Pass \\`--prefer-install-dependencies=false\\` on each generator except the last so dependencies install once at the end, for example:\n\n${PACKAGE_MANAGERS.map(\n (pm) => ` \\`\\`\\`bash\n ${buildNxCommand('g @aws/nx-plugin:ts#trpc-api --no-interactive --name=my-app-api --auth=IAM --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:ts#react-website --no-interactive --name=my-app-website --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:connection --no-interactive --sourceProject=@my-app/my-app-website --targetProject=@my-app/my-app-api --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:ts#infra --no-interactive --name=infra', pm)} && \\\\\n ${buildNxCommand('sync', pm)}\n \\`\\`\\``,\n).join('\\n')}\n\n- **\\`--prefer-install-dependencies=false\\`** asks a generator to defer its dependency install so the batch installs once at the end (the final generator above omits the flag and installs everything).\n- **\\`nx sync\\`** is required before building — generators modify TypeScript project references.\n\n## Detailed Guides\n\nPlease refer to the below documentation for important details regarding workspaces and working with TypeScript or Python projects.\n\n${await fetchGuidePages(['workspace', 'typescript-project', 'python-project'], generators)}\n\n `,\n },\n ],\n }),\n );\n};\n"],"names":["IAC_PROVIDERS","buildNxCommand","fetchGuidePages","PACKAGE_MANAGERS","TOOL_SELECTION_GUIDE","addGeneralGuidanceTool","server","generators","registerTool","title","description","content","type","text","join","map","pm"],"mappings":"AAAA;;;CAGC,GAGD,SAASA,aAAa,QAAQ,+BAA4B;AAC1D,SAASC,cAAc,EAAEC,eAAe,QAAQ,uBAAoB;AACpE,SAASC,gBAAgB,QAAQ,eAAY;AAE7C,OAAO,MAAMC,uBAAuB,CAAC;;;;;+FAK0D,CAAC,CAAC;AAEjG;;CAEC,GACD,OAAO,MAAMC,yBAAyB,CACpCC,QACAC;IAEAD,OAAOE,YAAY,CACjB,oBACA;QACEC,OAAO;QACPC,aACE;IACJ,GACA,UAAa,CAAA;YACXC,SAAS;gBACP;oBACEC,MAAM;oBACNC,MAAM,CAAC;;AAEjB,EAAET,qBAAqB;;;;yDAIkC,EAAED,iBAAiBW,IAAI,CAAC,MAAM;+EACR,EAAEd,cAAcc,IAAI,CAAC,MAAM;;;;;;;AAO1G,EAAEX,iBAAiBY,GAAG,CAAC,CAACC,KAAOf,eAAe,aAAae,KAAKF,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC9E,EAAEX,iBAAiBY,GAAG,CACpB,CAACC,KAAO,CAAC;EACT,EAAEf,eAAe,kHAAkHe,IAAI;IACrI,EAAEf,eAAe,gHAAgHe,IAAI;IACrI,EAAEf,eAAe,8JAA8Je,IAAI;IACnL,EAAEf,eAAe,2DAA2De,IAAI;IAChF,EAAEf,eAAe,QAAQe,IAAI;QACzB,CAAC,EACPF,IAAI,CAAC,MAAM;;;;;;;;;AASb,EAAE,MAAMZ,gBAAgB;wBAAC;wBAAa;wBAAsB;qBAAiB,EAAEK,YAAY;;IAEvF,CAAC;gBACG;aACD;QACH,CAAA;AAEJ,EAAE"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/mcp-server/tools/general-guidance.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { NxGeneratorInfo } from '../../utils/generators';\nimport { IAC_PROVIDERS } from '../../utils/iac-providers';\nimport { buildNxCommand, fetchGuidePages } from '../generator-info';\nimport { PACKAGE_MANAGERS } from '../schema';\n\nexport const TOOL_SELECTION_GUIDE = `## Tool Selection Guide\n\n- Use the \\`general-guidance\\` tool for guidance and best practices for working with Nx and the Nx Plugin for AWS.\n- Use the \\`create-workspace-command\\` tool to discover how to create a workspace to start a new project.\n- Use the \\`list-generators\\` tool to discover the available generators and how to run them.\n- Use the \\`generator-guide\\` tool to retrieve detailed information about a specific generator.`;\n\n/**\n * Add a tool which provides general guidance for using Nx and the Nx Plugin for AWS\n */\nexport const addGeneralGuidanceTool = (\n server: McpServer,\n generators: NxGeneratorInfo[],\n) => {\n server.registerTool(\n 'general-guidance',\n {\n title: 'General Guidance',\n description:\n 'Tool for guidance and best practices for working with Nx and the Nx Plugin for AWS',\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: `# Nx Plugin for AWS Guidance\n\n${TOOL_SELECTION_GUIDE}\n\n## Getting Started\n\n- Choose a package manager first. You can choose between ${PACKAGE_MANAGERS.join(', ')}. It's recommended to use \"pnpm\" if the user has no preference\n- Choose an infrastructure as code (IaC) provider next. You can choose between ${IAC_PROVIDERS.join(', ')}. It's recommended to use CDK if the user has no preference\n- Next, you must create an Nx workspace. Use the \\`create-workspace-command\\` tool for more details, and provide it with your chosen package manager\n- After this, you can start scaffolding the main components of your application using generators. Use the \\`list-generators\\` tool to discover available generators, and the \\`generator-guide\\` tool for more detailed information about a specific generator\n\n## Nx Primer\n\n- Prefix nx commands with the appropriate prefix for your package manager, for example:\n${PACKAGE_MANAGERS.map((pm) => buildNxCommand('<options>', pm)).join(' - \\n')}\n- Each project in your workspace has a file named \\`project.json\\` which contains important project information such as its name, and defines the \"targets\" which can be run for that project, for example building or testing the project\n- Use the command \\`nx reset\\` to reset the Nx daemon when unexpected issues arise\n- After adding dependencies between TypeScript projects, use \\`nx sync\\` to ensure project references are set up correctly\n\n## General Instructions\n\n- Workspaces contain a single \\`package.json\\` file at the root which defines the dependencies for all projects. Therefore when installing dependencies, you must add these to the root workspace using the appropriate command for your package manager:\n - pnpm add -w -D <package>\n - yarn add -D <package>\n - npm install --legacy-peer-deps -D <package>\n - bun install -D <package>\n - (Omit -D for production dependencies)\n- When specifying project names as arguments to generators, prefer the _fully qualified_ project name, for example \\`@workspace-name/project-name\\`. Check the \\`project.json\\` file for the specific package to find its fully qualified name\n- When no generator exists for a specific framework required, use the base \\`ts#project\\` and \\`py#project\\` generators and build on top.\n- Leave the \\`--infra\\` option at its default value unless the user has explicitly instructed otherwise. Generators choose a sensible default type of infrastructure to deploy the project with, so only override \\`--infra\\` when the user has specified a particular requirement.\n\n## Useful Commands\n\n- Fix lint issues with \\`nx run-many --target lint --configuration=fix --all --output-style=stream\\`\n- Build all projects with \\`nx run-many --target build --all --output-style=stream\\`\n- Prefer importing the CDK constructs vended by generators in \\`packages/common/constructs\\` over writing your own\n\n## Best Practices\n\n- After running a generator, use the \\`nx show projects\\` command to check which projects have been added (if any)\n- Carefully examine the files that have been generated and always refer back to the generator guide when working in a generated project\n- Generate all projects into the \\`packages/\\` directory\n- After making changes to your projects, fix linting issues, then run a full build\n- When it's time to start testing a project, suggest to the user that infrastructure is deployed to AWS. For websites, if a runtime-config.json is needed, use the load:runtime-config target after a deployment to point a local website at a sandbox stack.\n\n## Batching Generators\n\nWhen scaffolding several projects in one go, chain generators to avoid a slow dependency install after every generator:\n\n- **Chain generators** with \\`&&\\` in a single command. Pass \\`--prefer-install-dependencies=false\\` on each generator except the last so dependencies install once at the end, for example:\n\n${PACKAGE_MANAGERS.map(\n (pm) => ` \\`\\`\\`bash\n ${buildNxCommand('g @aws/nx-plugin:ts#trpc-api --no-interactive --name=my-app-api --auth=IAM --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:ts#react-website --no-interactive --name=my-app-website --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:connection --no-interactive --sourceProject=@my-app/my-app-website --targetProject=@my-app/my-app-api --prefer-install-dependencies=false', pm)} && \\\\\n ${buildNxCommand('g @aws/nx-plugin:ts#infra --no-interactive --name=infra', pm)} && \\\\\n ${buildNxCommand('sync', pm)}\n \\`\\`\\``,\n).join('\\n')}\n\n- **\\`--prefer-install-dependencies=false\\`** asks a generator to defer its dependency install so the batch installs once at the end (the final generator above omits the flag and installs everything).\n- **\\`nx sync\\`** is required before building — generators modify TypeScript project references.\n\n## Detailed Guides\n\nPlease refer to the below documentation for important details regarding workspaces and working with TypeScript or Python projects.\n\n${await fetchGuidePages(['workspace', 'typescript-project', 'python-project'], generators)}\n\n `,\n },\n ],\n }),\n );\n};\n"],"names":["IAC_PROVIDERS","buildNxCommand","fetchGuidePages","PACKAGE_MANAGERS","TOOL_SELECTION_GUIDE","addGeneralGuidanceTool","server","generators","registerTool","title","description","content","type","text","join","map","pm"],"mappings":"AAAA;;;CAGC,GAGD,SAASA,aAAa,QAAQ,+BAA4B;AAC1D,SAASC,cAAc,EAAEC,eAAe,QAAQ,uBAAoB;AACpE,SAASC,gBAAgB,QAAQ,eAAY;AAE7C,OAAO,MAAMC,uBAAuB,CAAC;;;;;+FAK0D,CAAC,CAAC;AAEjG;;CAEC,GACD,OAAO,MAAMC,yBAAyB,CACpCC,QACAC;IAEAD,OAAOE,YAAY,CACjB,oBACA;QACEC,OAAO;QACPC,aACE;IACJ,GACA,UAAa,CAAA;YACXC,SAAS;gBACP;oBACEC,MAAM;oBACNC,MAAM,CAAC;;AAEjB,EAAET,qBAAqB;;;;yDAIkC,EAAED,iBAAiBW,IAAI,CAAC,MAAM;+EACR,EAAEd,cAAcc,IAAI,CAAC,MAAM;;;;;;;AAO1G,EAAEX,iBAAiBY,GAAG,CAAC,CAACC,KAAOf,eAAe,aAAae,KAAKF,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqC9E,EAAEX,iBAAiBY,GAAG,CACpB,CAACC,KAAO,CAAC;EACT,EAAEf,eAAe,kHAAkHe,IAAI;IACrI,EAAEf,eAAe,gHAAgHe,IAAI;IACrI,EAAEf,eAAe,8JAA8Je,IAAI;IACnL,EAAEf,eAAe,2DAA2De,IAAI;IAChF,EAAEf,eAAe,QAAQe,IAAI;QACzB,CAAC,EACPF,IAAI,CAAC,MAAM;;;;;;;;;AASb,EAAE,MAAMZ,gBAAgB;wBAAC;wBAAa;wBAAsB;qBAAiB,EAAEK,YAAY;;IAEvF,CAAC;gBACG;aACD;QACH,CAAA;AAEJ,EAAE"}
@@ -27,18 +27,12 @@ class AgentCoreA2aClientConfig:
27
27
  """SigV4-authenticated A2A client config for a Bedrock AgentCore runtime."""
28
28
  region = region_from_arn(agent_runtime_arn)
29
29
  credentials = boto3.Session(region_name=region).get_credentials()
30
- return _config(
31
- a2a_url_from_arn(agent_runtime_arn), sigv4_auth(credentials, region)
32
- )
30
+ return _config(a2a_url_from_arn(agent_runtime_arn), sigv4_auth(credentials, region))
33
31
 
34
32
  @staticmethod
35
- def with_jwt_auth(
36
- agent_runtime_arn: str, access_token_provider: Callable[[], str]
37
- ) -> tuple[str, ClientConfig]:
33
+ def with_jwt_auth(agent_runtime_arn: str, access_token_provider: Callable[[], str]) -> tuple[str, ClientConfig]:
38
34
  """Bearer-authenticated A2A client config for a Bedrock AgentCore runtime."""
39
- return _config(
40
- a2a_url_from_arn(agent_runtime_arn), jwt_auth(access_token_provider)
41
- )
35
+ return _config(a2a_url_from_arn(agent_runtime_arn), jwt_auth(access_token_provider))
42
36
 
43
37
  @staticmethod
44
38
  def without_auth(url: str) -> tuple[str, ClientConfig]:
@@ -92,9 +86,7 @@ class AgentCoreA2aClientStrands:
92
86
  ) -> A2AAgent:
93
87
  """Bearer-authenticated client for a Bedrock AgentCore runtime."""
94
88
  return _build(
95
- AgentCoreA2aClientConfig.with_jwt_auth(
96
- agent_runtime_arn, access_token_provider
97
- ),
89
+ AgentCoreA2aClientConfig.with_jwt_auth(agent_runtime_arn, access_token_provider),
98
90
  name=name,
99
91
  description=description,
100
92
  )
@@ -119,6 +111,7 @@ exports[`py#agent#a2a-connection generator > should match snapshot for agent-con
119
111
  "import os
120
112
 
121
113
  from strands.agent.a2a_agent import A2AAgent
114
+
122
115
  from test_agent_connection.core.agentcore_a2a_client_strands import (
123
116
  AgentCoreA2aClientStrands,
124
117
  )
@@ -137,9 +130,7 @@ class RemoteClientStrands:
137
130
  config = get_agentcore_runtime_config()
138
131
  agent_runtime_arn = config.get("agentRuntimes", {}).get("Remote")
139
132
  if not agent_runtime_arn:
140
- raise RuntimeError(
141
- "No connected agent runtime named 'Remote' found in runtime configuration."
142
- )
133
+ raise RuntimeError("No connected agent runtime named 'Remote' found in runtime configuration.")
143
134
  return AgentCoreA2aClientStrands.with_iam_auth(agent_runtime_arn)
144
135
  "
145
136
  `;
@@ -175,9 +166,7 @@ class SessionHeaderAuth(httpx.Auth):
175
166
  yield from self._inner.auth_flow(request)
176
167
 
177
168
 
178
- def sigv4_auth(
179
- credentials, region: str, service: str = "bedrock-agentcore"
180
- ) -> httpx.Auth:
169
+ def sigv4_auth(credentials, region: str, service: str = "bedrock-agentcore") -> httpx.Auth:
181
170
  """Session-forwarding SigV4 auth (per-request, body-aware)."""
182
171
  return SessionHeaderAuth(SigV4HTTPXAuth(credentials, service, region))
183
172
 
@@ -15,9 +15,7 @@ class AgentCoreGatewayMCPClientStrands:
15
15
  region: str | None = None,
16
16
  ) -> MCPClient:
17
17
  """Create a gateway MCP client authenticated with IAM SigV4."""
18
- return MCPClient(
19
- AgentCoreGatewayMCPTransport.with_iam_auth(gateway_url, region)
20
- )
18
+ return MCPClient(AgentCoreGatewayMCPTransport.with_iam_auth(gateway_url, region))
21
19
 
22
20
  @staticmethod
23
21
  def without_auth(gateway_url: str) -> MCPClient:
@@ -47,9 +45,7 @@ class AgentCoreGatewayMCPTransport:
47
45
  region: str | None = None,
48
46
  ) -> TransportFactory:
49
47
  """Create a gateway MCP transport authenticated with IAM SigV4."""
50
- return sigv4_transport(
51
- gateway_url, region or region_from_gateway_url(gateway_url)
52
- )
48
+ return sigv4_transport(gateway_url, region or region_from_gateway_url(gateway_url))
53
49
 
54
50
  @staticmethod
55
51
  def without_auth(gateway_url: str) -> TransportFactory:
@@ -88,9 +84,7 @@ def sigv4_transport(url: str, region: str) -> TransportFactory:
88
84
  return _factory(url, sigv4_auth(credentials, region))
89
85
 
90
86
 
91
- def jwt_transport(
92
- url: str, access_token_provider: Callable[[], str]
93
- ) -> TransportFactory:
87
+ def jwt_transport(url: str, access_token_provider: Callable[[], str]) -> TransportFactory:
94
88
  """Bearer-token transport factory for a resolved AgentCore endpoint."""
95
89
  return _factory(url, jwt_auth(access_token_provider))
96
90
 
@@ -104,13 +98,14 @@ def no_auth_transport(url: str) -> TransportFactory:
104
98
  exports[`py#agent#gateway-connection generator > should match snapshot for agent-connection core files > my_gateway_client_strands.py 1`] = `
105
99
  "import os
106
100
 
101
+ from strands.tools.mcp.mcp_client import MCPClient
102
+
107
103
  from proj_agent_connection.core.agentcore_gateway_mcp_client_strands import (
108
104
  AgentCoreGatewayMCPClientStrands,
109
105
  )
110
106
  from proj_agent_connection.core.runtime_config import (
111
107
  get_agentcore_runtime_config,
112
108
  )
113
- from strands.tools.mcp.mcp_client import MCPClient
114
109
 
115
110
 
116
111
  class MyGatewayClientStrands:
@@ -126,15 +121,11 @@ class MyGatewayClientStrands:
126
121
  @staticmethod
127
122
  def create() -> MCPClient:
128
123
  if os.environ.get("LOCAL_DEV") == "true":
129
- return AgentCoreGatewayMCPClientStrands.without_auth(
130
- gateway_url="http://localhost:8100/mcp"
131
- )
124
+ return AgentCoreGatewayMCPClientStrands.without_auth(gateway_url="http://localhost:8100/mcp")
132
125
  config = get_agentcore_runtime_config()
133
126
  gateway_url = config.get("gateways", {}).get("MyGateway")
134
127
  if not gateway_url:
135
- raise RuntimeError(
136
- "No connected gateway named 'MyGateway' found in runtime configuration."
137
- )
128
+ raise RuntimeError("No connected gateway named 'MyGateway' found in runtime configuration.")
138
129
  return AgentCoreGatewayMCPClientStrands.with_iam_auth(gateway_url=gateway_url)
139
130
  "
140
131
  `;
@@ -170,9 +161,7 @@ class SessionHeaderAuth(httpx.Auth):
170
161
  yield from self._inner.auth_flow(request)
171
162
 
172
163
 
173
- def sigv4_auth(
174
- credentials, region: str, service: str = "bedrock-agentcore"
175
- ) -> httpx.Auth:
164
+ def sigv4_auth(credentials, region: str, service: str = "bedrock-agentcore") -> httpx.Auth:
176
165
  """Session-forwarding SigV4 auth (per-request, body-aware)."""
177
166
  return SessionHeaderAuth(SigV4HTTPXAuth(credentials, service, region))
178
167
 
@@ -35,15 +35,9 @@ class AgentCoreMCPClientStrands:
35
35
  return MCPClient(AgentCoreMCPTransport.with_iam_auth(agent_runtime_arn))
36
36
 
37
37
  @staticmethod
38
- def with_jwt_auth(
39
- agent_runtime_arn: str, access_token_provider: Callable[[], str]
40
- ) -> MCPClient:
38
+ def with_jwt_auth(agent_runtime_arn: str, access_token_provider: Callable[[], str]) -> MCPClient:
41
39
  """Bearer-authenticated client for a Bedrock AgentCore runtime."""
42
- return MCPClient(
43
- AgentCoreMCPTransport.with_jwt_auth(
44
- agent_runtime_arn, access_token_provider
45
- )
46
- )
40
+ return MCPClient(AgentCoreMCPTransport.with_jwt_auth(agent_runtime_arn, access_token_provider))
47
41
 
48
42
  @staticmethod
49
43
  def without_auth(url: str) -> MCPClient:
@@ -70,14 +64,10 @@ class AgentCoreMCPTransport:
70
64
  @staticmethod
71
65
  def with_iam_auth(agent_runtime_arn: str) -> TransportFactory:
72
66
  """SigV4-authenticated transport for a Bedrock AgentCore runtime."""
73
- return sigv4_transport(
74
- mcp_url_from_arn(agent_runtime_arn), region_from_arn(agent_runtime_arn)
75
- )
67
+ return sigv4_transport(mcp_url_from_arn(agent_runtime_arn), region_from_arn(agent_runtime_arn))
76
68
 
77
69
  @staticmethod
78
- def with_jwt_auth(
79
- agent_runtime_arn: str, access_token_provider: Callable[[], str]
80
- ) -> TransportFactory:
70
+ def with_jwt_auth(agent_runtime_arn: str, access_token_provider: Callable[[], str]) -> TransportFactory:
81
71
  """Bearer-authenticated transport for a Bedrock AgentCore runtime."""
82
72
  return jwt_transport(mcp_url_from_arn(agent_runtime_arn), access_token_provider)
83
73
 
@@ -118,9 +108,7 @@ def sigv4_transport(url: str, region: str) -> TransportFactory:
118
108
  return _factory(url, sigv4_auth(credentials, region))
119
109
 
120
110
 
121
- def jwt_transport(
122
- url: str, access_token_provider: Callable[[], str]
123
- ) -> TransportFactory:
111
+ def jwt_transport(url: str, access_token_provider: Callable[[], str]) -> TransportFactory:
124
112
  """Bearer-token transport factory for a resolved AgentCore endpoint."""
125
113
  return _factory(url, jwt_auth(access_token_provider))
126
114
 
@@ -134,13 +122,14 @@ def no_auth_transport(url: str) -> TransportFactory:
134
122
  exports[`py#agent#mcp-connection generator > should match snapshot for generated files > inventory_mcp_client_strands.py 1`] = `
135
123
  "import os
136
124
 
125
+ from strands.tools.mcp.mcp_client import MCPClient
126
+
137
127
  from proj_agent_connection.core.agentcore_mcp_client_strands import (
138
128
  AgentCoreMCPClientStrands,
139
129
  )
140
130
  from proj_agent_connection.core.runtime_config import (
141
131
  get_agentcore_runtime_config,
142
132
  )
143
- from strands.tools.mcp.mcp_client import MCPClient
144
133
 
145
134
 
146
135
  class InventoryMcpClientStrands:
@@ -153,9 +142,7 @@ class InventoryMcpClientStrands:
153
142
  config = get_agentcore_runtime_config()
154
143
  agent_runtime_arn = config.get("agentRuntimes", {}).get("InventoryMcp")
155
144
  if not agent_runtime_arn:
156
- raise RuntimeError(
157
- "No connected MCP server runtime named 'InventoryMcp' found in runtime configuration."
158
- )
145
+ raise RuntimeError("No connected MCP server runtime named 'InventoryMcp' found in runtime configuration.")
159
146
  return AgentCoreMCPClientStrands.with_iam_auth(agent_runtime_arn)
160
147
  "
161
148
  `;
@@ -191,9 +178,7 @@ class SessionHeaderAuth(httpx.Auth):
191
178
  yield from self._inner.auth_flow(request)
192
179
 
193
180
 
194
- def sigv4_auth(
195
- credentials, region: str, service: str = "bedrock-agentcore"
196
- ) -> httpx.Auth:
181
+ def sigv4_auth(credentials, region: str, service: str = "bedrock-agentcore") -> httpx.Auth:
197
182
  """Session-forwarding SigV4 auth (per-request, body-aware)."""
198
183
  return SessionHeaderAuth(SigV4HTTPXAuth(credentials, service, region))
199
184
 
@@ -70,9 +70,7 @@ class JsonStreamingResponse(StreamingResponse):
70
70
  "description": description,
71
71
  "content": {
72
72
  "application/jsonl": {
73
- "itemSchema": {
74
- "$ref": f"#/components/schemas/{item_model.__name__}"
75
- },
73
+ "itemSchema": {"$ref": f"#/components/schemas/{item_model.__name__}"},
76
74
  }
77
75
  },
78
76
  # Include the model so FastAPI registers the schema in components/schemas
@@ -93,11 +91,7 @@ async def cors_middleware(request: Request, call_next):
93
91
  response = await call_next(request)
94
92
 
95
93
  origin = request.headers.get("origin")
96
- allowed_origins = (
97
- os.environ.get("ALLOWED_ORIGINS", "").split(",")
98
- if os.environ.get("ALLOWED_ORIGINS")
99
- else []
100
- )
94
+ allowed_origins = os.environ.get("ALLOWED_ORIGINS", "").split(",") if os.environ.get("ALLOWED_ORIGINS") else []
101
95
 
102
96
  is_localhost = origin and urlparse(origin).hostname in ["localhost", "127.0.0.1"]
103
97
  is_allowed_origin = origin and origin in allowed_origins
@@ -124,8 +118,7 @@ async def unhandled_exception_handler(request, err):
124
118
  metrics.add_metric(name="Failure", unit=MetricUnit.Count, value=1)
125
119
 
126
120
  return JSONResponse(
127
- status_code=500,
128
- content=InternalServerErrorDetails(detail="Internal Server Error").model_dump(),
121
+ status_code=500, content=InternalServerErrorDetails(detail="Internal Server Error").model_dump()
129
122
  )
130
123
 
131
124
 
@@ -2,7 +2,7 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import type { Tree } from '@nx/devkit';
5
+ import { type Tree } from '@nx/devkit';
6
6
  export declare const DEFAULT_BIOME_CONFIG: {
7
7
  $schema: string;
8
8
  root: boolean;
@@ -2,10 +2,12 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */ import { Biome } from "@biomejs/js-api/nodejs";
5
+ import { getProjects } from "@nx/devkit";
5
6
  import { execFileSync, execSync } from "child_process";
6
7
  import { existsSync, readFileSync } from "fs";
7
8
  import { createRequire } from "module";
8
9
  import path from "path";
10
+ import { readToml } from "./toml.js";
9
11
  const require = createRequire(import.meta.url);
10
12
  export const DEFAULT_BIOME_CONFIG = {
11
13
  $schema: 'https://biomejs.dev/schemas/2.4.16/schema.json',
@@ -79,10 +81,13 @@ const BIOME_FORMATTABLE_EXTENSIONS = new Set([
79
81
  const changedFiles = tree.listChanges().filter((file)=>file.type !== 'DELETE').filter((file)=>dir ? file.path.startsWith(dir) : true);
80
82
  const pyFiles = changedFiles.filter((file)=>file.path.endsWith('.py'));
81
83
  const otherFiles = changedFiles.filter((file)=>BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)));
84
+ // Resolve each project's ruff settings (module names, line-length) so files
85
+ // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).
86
+ const pythonProjectConfigs = pyFiles.length ? getPythonProjectRuffConfigs(tree) : [];
82
87
  // Format Python files with ruff (lint fixes + formatting)
83
88
  for (const file of pyFiles){
84
89
  try {
85
- const content = ruffFixAndFormat(file.content.toString('utf-8'), file.path, hasRuffConfigOnDisk(tree, file.path));
90
+ const content = ruffFixAndFormat(file.content.toString('utf-8'), file.path, hasRuffConfigOnDisk(tree, file.path), getOwningProjectRuffConfig(file.path, pythonProjectConfigs));
86
91
  tree.write(file.path, content);
87
92
  } catch {
88
93
  // Silently skip ruff formatting failures
@@ -263,6 +268,52 @@ function getRuffCommand() {
263
268
  dir = parent;
264
269
  }
265
270
  }
271
+ /**
272
+ * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk
273
+ * build enforces for it: its top-level module names (from
274
+ * `[tool.hatch.build.targets.wheel].packages`) and its `[tool.ruff].line-length`.
275
+ */ function getPythonProjectRuffConfigs(tree) {
276
+ const configs = [];
277
+ for (const project of getProjects(tree).values()){
278
+ const pyprojectPath = path.join(project.root, 'pyproject.toml');
279
+ if (tree.exists(pyprojectPath)) {
280
+ try {
281
+ const pyproject = readToml(tree, pyprojectPath);
282
+ const wheelPackages = pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;
283
+ // Record the top-level module segment (`pkg/sub` -> `pkg`), which is
284
+ // all `known-first-party` keys off.
285
+ const modules = Array.isArray(wheelPackages) ? wheelPackages.filter((pkg)=>typeof pkg === 'string' && !!pkg).map((pkg)=>pkg.split('/')[0]) : [];
286
+ const lineLength = pyproject?.tool?.ruff?.['line-length'];
287
+ if (modules.length || typeof lineLength === 'number') {
288
+ configs.push({
289
+ root: project.root.split(path.sep).join('/'),
290
+ modules,
291
+ lineLength: typeof lineLength === 'number' ? lineLength : undefined
292
+ });
293
+ }
294
+ } catch {
295
+ // Skip projects whose pyproject.toml cannot be parsed
296
+ }
297
+ }
298
+ }
299
+ return configs;
300
+ }
301
+ /**
302
+ * Resolve the ruff config for the project that owns a file (the project with
303
+ * the longest root that is a prefix of the file path). Ruff runs per-project on
304
+ * disk, so a file's settings come from its own project — only its own module is
305
+ * first-party (sibling workspace packages are third-party) and its own
306
+ * line-length applies — and scoping this way keeps in-tree formatting
307
+ * consistent with the on-disk build.
308
+ */ function getOwningProjectRuffConfig(filePath, configs) {
309
+ let owner;
310
+ for (const config of configs){
311
+ if ((filePath === config.root || filePath.startsWith(`${config.root}/`)) && (!owner || config.root.length > owner.root.length)) {
312
+ owner = config;
313
+ }
314
+ }
315
+ return owner;
316
+ }
266
317
  /**
267
318
  * Run ruff check --fix and ruff format on Python file content via stdin.
268
319
  * Applies all configured lint fixes (including import sorting) and formatting.
@@ -272,13 +323,29 @@ function getRuffCommand() {
272
323
  * build fails on unsorted imports (I001). In that case we add `--extend-select
273
324
  * I` so import sorting matches what the build enforces. When a config does
274
325
  * exist we defer to it entirely, honouring the user's rule selection.
275
- */ function ruffFixAndFormat(content, filePath, hasConfig) {
326
+ *
327
+ * `projectConfig` carries the owning project's ruff settings, which ruff cannot
328
+ * detect from the filesystem during generation because the project lives only
329
+ * in the tree. We pass them via `--config` so in-tree formatting matches the
330
+ * on-disk build: `known-first-party` (the project's own modules) keeps its
331
+ * imports in their own group, and `line-length` keeps wrapping consistent (the
332
+ * generated config raises it above ruff's default of 88). These are additive to
333
+ * any on-disk config, so they are safe to pass regardless of `hasConfig`.
334
+ */ function ruffFixAndFormat(content, filePath, hasConfig, projectConfig) {
276
335
  const ruff = getRuffCommand();
277
336
  if (!ruff) return content;
278
337
  const extendSelect = hasConfig ? '' : ' --extend-select I';
338
+ const configArgs = [];
339
+ if (projectConfig?.modules.length) {
340
+ configArgs.push(`lint.isort.known-first-party = ${JSON.stringify(projectConfig.modules)}`);
341
+ }
342
+ if (typeof projectConfig?.lineLength === 'number') {
343
+ configArgs.push(`line-length = ${projectConfig.lineLength}`);
344
+ }
345
+ const config = configArgs.map((arg)=>` --config ${JSON.stringify(arg)}`).join('');
279
346
  // First apply lint fixes (import sorting, unused imports, etc.)
280
347
  try {
281
- const result = execSync(`${ruff} check --fix${extendSelect} --stdin-filename ${filePath} -`, {
348
+ const result = execSync(`${ruff} check --fix${extendSelect}${config} --stdin-filename ${filePath} -`, {
282
349
  input: content,
283
350
  encoding: 'utf-8',
284
351
  stdio: [
@@ -297,7 +364,7 @@ function getRuffCommand() {
297
364
  }
298
365
  // Then apply formatting
299
366
  try {
300
- content = execSync(`${ruff} format --stdin-filename ${filePath} -`, {
367
+ content = execSync(`${ruff} format${config} --stdin-filename ${filePath} -`, {
301
368
  input: content,
302
369
  encoding: 'utf-8',
303
370
  stdio: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport type { Tree } from '@nx/devkit';\nimport { execFileSync, execSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\n\nconst require = createRequire(import.meta.url);\n\nexport const DEFAULT_BIOME_CONFIG = {\n $schema: 'https://biomejs.dev/schemas/2.4.16/schema.json',\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n linter: {\n enabled: true,\n rules: {\n recommended: false,\n correctness: {\n noUndeclaredDependencies: 'warn',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n '!**/*.css',\n ],\n },\n};\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n): Promise<void> {\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter((file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)),\n );\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n hasRuffConfigOnDisk(tree, file.path),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n // Use the workspace's own Biome CLI (its version and config) when biome.json\n // exists on disk; otherwise format via the bundled library API with the\n // in-memory tree config. The CLI path does not see in-tree config changes.\n if (existsSync(path.join(tree.root, 'biome.json'))) {\n formatWithBiomeCli(tree, otherFiles);\n } else {\n formatWithBiomeApi(tree, otherFiles);\n }\n}\n\n/**\n * Format files via the workspace's Biome CLI, run from the workspace root so it\n * discovers the on-disk biome.json.\n */\nfunction formatWithBiomeCli(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n const biome = getBiomeCommand(tree.root);\n if (!biome) {\n // Fall back to the library API if the CLI cannot be resolved\n formatWithBiomeApi(tree, files);\n return;\n }\n\n for (const file of files) {\n try {\n const content = execFileSync(\n biome.command,\n [...biome.args, 'format', `--stdin-file-path=${file.path}`],\n {\n input: file.content?.toString('utf-8') ?? '',\n encoding: 'utf-8',\n cwd: tree.root,\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n}\n\n/**\n * Format files via the bundled Biome library API, applying the in-memory tree\n * config.\n */\nfunction formatWithBiomeApi(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n // Apply the workspace biome.json if it exists in the tree, otherwise the defaults.\n const treeConfig = tree.read('biome.json', 'utf-8');\n biome.applyConfiguration(\n projectKey,\n treeConfig ? JSON.parse(treeConfig) : DEFAULT_BIOME_CONFIG,\n );\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\ninterface BiomeCommand {\n command: string;\n args: string[];\n}\n\n/**\n * Resolve the `@biomejs/biome` CLI from the user's workspace, falling back to a\n * `biome` binary on the PATH.\n */\nconst _biomeCommands = new Map<string, BiomeCommand | null>();\nfunction getBiomeCommand(root: string): BiomeCommand | undefined {\n if (_biomeCommands.has(root)) {\n return _biomeCommands.get(root) ?? undefined;\n }\n\n // Run via node for cross-platform execution of the bin shim.\n try {\n const pkgJsonPath = require.resolve('@biomejs/biome/package.json', {\n paths: [root, import.meta.dirname],\n });\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n const binRelative =\n typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.biome;\n if (binRelative) {\n const binPath = path.join(path.dirname(pkgJsonPath), binRelative);\n const command = { command: process.execPath, args: [binPath] };\n _biomeCommands.set(root, command);\n return command;\n }\n } catch {\n // Fall back to a biome binary on the PATH\n }\n\n try {\n execSync('biome --version', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const command = { command: 'biome', args: [] };\n _biomeCommands.set(root, command);\n return command;\n } catch {\n _biomeCommands.set(root, null);\n return undefined;\n }\n}\n\n/**\n * Find the ruff command. Tries 'uv run ruff', then 'uvx ruff'.\n * Matches how @nxlv/python runs ruff via the UV provider.\n */\nlet _ruffCommand: string | undefined;\nfunction getRuffCommand(): string | undefined {\n if (_ruffCommand !== undefined) {\n return _ruffCommand || undefined;\n }\n for (const cmd of ['uv run ruff', 'uvx ruff']) {\n try {\n execSync(`${cmd} --version`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n _ruffCommand = cmd;\n return cmd;\n } catch {\n // Try next command\n }\n }\n _ruffCommand = '';\n return undefined;\n}\n\n/**\n * Whether ruff would discover a config on disk for a file, by walking from its\n * directory up to the workspace root looking for `.ruff.toml`, `ruff.toml`, or a\n * `pyproject.toml` with a `[tool.ruff]` section — the same files ruff itself\n * resolves. The walk stops at `tree.root` so a stray config in a parent of the\n * workspace (or the home directory) is never treated as the project's. Used to\n * decide whether to nudge ruff towards import sorting (see\n * {@link ruffFixAndFormat}).\n */\nfunction hasRuffConfigOnDisk(tree: Tree, filePath: string): boolean {\n const root = path.resolve(tree.root);\n let dir = path.resolve(root, path.dirname(filePath));\n while (true) {\n if (\n existsSync(path.join(dir, '.ruff.toml')) ||\n existsSync(path.join(dir, 'ruff.toml'))\n ) {\n return true;\n }\n const pyproject = path.join(dir, 'pyproject.toml');\n if (\n existsSync(pyproject) &&\n readFileSync(pyproject, 'utf-8').includes('[tool.ruff')\n ) {\n return true;\n }\n const parent = path.dirname(dir);\n // Stop once the workspace root has been checked (or we hit the FS root).\n if (dir === root || parent === dir) {\n return false;\n }\n dir = parent;\n }\n}\n\n/**\n * Run ruff check --fix and ruff format on Python file content via stdin.\n * Applies all configured lint fixes (including import sorting) and formatting.\n *\n * When no ruff config exists on disk (`hasConfig` false) ruff falls back to its\n * defaults, which omit isort — but generated projects enable rule `I` and their\n * build fails on unsorted imports (I001). In that case we add `--extend-select\n * I` so import sorting matches what the build enforces. When a config does\n * exist we defer to it entirely, honouring the user's rule selection.\n */\nfunction ruffFixAndFormat(\n content: string,\n filePath: string,\n hasConfig: boolean,\n): string {\n const ruff = getRuffCommand();\n if (!ruff) return content;\n\n const extendSelect = hasConfig ? '' : ' --extend-select I';\n\n // First apply lint fixes (import sorting, unused imports, etc.)\n try {\n const result = execSync(\n `${ruff} check --fix${extendSelect} --stdin-filename ${filePath} -`,\n { input: content, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n content = result;\n } catch (e: any) {\n // ruff check exits non-zero when it finds unfixable issues,\n // but stdout still contains the fixed content\n if (e.stdout) {\n content = e.stdout;\n }\n }\n\n // Then apply formatting\n try {\n content = execSync(`${ruff} format --stdin-filename ${filePath} -`, {\n input: content,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch {\n // Fall through with whatever content we have\n }\n\n return content;\n}\n"],"names":["Biome","execFileSync","execSync","existsSync","readFileSync","createRequire","path","require","url","DEFAULT_BIOME_CONFIG","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","css","linter","rules","recommended","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","BIOME_FORMATTABLE_EXTENSIONS","Set","formatFilesInSubtree","tree","dir","changedFiles","listChanges","filter","file","type","startsWith","pyFiles","endsWith","otherFiles","has","extname","content","ruffFixAndFormat","toString","hasRuffConfigOnDisk","write","length","join","formatWithBiomeCli","formatWithBiomeApi","biome","getBiomeCommand","command","args","input","encoding","cwd","stdio","projectKey","openProject","treeConfig","read","applyConfiguration","JSON","parse","formatContent","filePath","_biomeCommands","Map","get","undefined","pkgJsonPath","resolve","paths","dirname","pkgJson","binRelative","bin","binPath","process","execPath","set","_ruffCommand","getRuffCommand","cmd","pyproject","parent","hasConfig","ruff","extendSelect","result","e","stdout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport { getProjects, type Tree } from '@nx/devkit';\nimport { execFileSync, execSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { readToml } from './toml';\n\nconst require = createRequire(import.meta.url);\n\nexport const DEFAULT_BIOME_CONFIG = {\n $schema: 'https://biomejs.dev/schemas/2.4.16/schema.json',\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n linter: {\n enabled: true,\n rules: {\n recommended: false,\n correctness: {\n noUndeclaredDependencies: 'warn',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n '!**/*.css',\n ],\n },\n};\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n): Promise<void> {\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter((file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)),\n );\n\n // Resolve each project's ruff settings (module names, line-length) so files\n // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).\n const pythonProjectConfigs = pyFiles.length\n ? getPythonProjectRuffConfigs(tree)\n : [];\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n hasRuffConfigOnDisk(tree, file.path),\n getOwningProjectRuffConfig(file.path, pythonProjectConfigs),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n // Use the workspace's own Biome CLI (its version and config) when biome.json\n // exists on disk; otherwise format via the bundled library API with the\n // in-memory tree config. The CLI path does not see in-tree config changes.\n if (existsSync(path.join(tree.root, 'biome.json'))) {\n formatWithBiomeCli(tree, otherFiles);\n } else {\n formatWithBiomeApi(tree, otherFiles);\n }\n}\n\n/**\n * Format files via the workspace's Biome CLI, run from the workspace root so it\n * discovers the on-disk biome.json.\n */\nfunction formatWithBiomeCli(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n const biome = getBiomeCommand(tree.root);\n if (!biome) {\n // Fall back to the library API if the CLI cannot be resolved\n formatWithBiomeApi(tree, files);\n return;\n }\n\n for (const file of files) {\n try {\n const content = execFileSync(\n biome.command,\n [...biome.args, 'format', `--stdin-file-path=${file.path}`],\n {\n input: file.content?.toString('utf-8') ?? '',\n encoding: 'utf-8',\n cwd: tree.root,\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n}\n\n/**\n * Format files via the bundled Biome library API, applying the in-memory tree\n * config.\n */\nfunction formatWithBiomeApi(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n // Apply the workspace biome.json if it exists in the tree, otherwise the defaults.\n const treeConfig = tree.read('biome.json', 'utf-8');\n biome.applyConfiguration(\n projectKey,\n treeConfig ? JSON.parse(treeConfig) : DEFAULT_BIOME_CONFIG,\n );\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\ninterface BiomeCommand {\n command: string;\n args: string[];\n}\n\n/**\n * Resolve the `@biomejs/biome` CLI from the user's workspace, falling back to a\n * `biome` binary on the PATH.\n */\nconst _biomeCommands = new Map<string, BiomeCommand | null>();\nfunction getBiomeCommand(root: string): BiomeCommand | undefined {\n if (_biomeCommands.has(root)) {\n return _biomeCommands.get(root) ?? undefined;\n }\n\n // Run via node for cross-platform execution of the bin shim.\n try {\n const pkgJsonPath = require.resolve('@biomejs/biome/package.json', {\n paths: [root, import.meta.dirname],\n });\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n const binRelative =\n typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.biome;\n if (binRelative) {\n const binPath = path.join(path.dirname(pkgJsonPath), binRelative);\n const command = { command: process.execPath, args: [binPath] };\n _biomeCommands.set(root, command);\n return command;\n }\n } catch {\n // Fall back to a biome binary on the PATH\n }\n\n try {\n execSync('biome --version', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const command = { command: 'biome', args: [] };\n _biomeCommands.set(root, command);\n return command;\n } catch {\n _biomeCommands.set(root, null);\n return undefined;\n }\n}\n\n/**\n * Find the ruff command. Tries 'uv run ruff', then 'uvx ruff'.\n * Matches how @nxlv/python runs ruff via the UV provider.\n */\nlet _ruffCommand: string | undefined;\nfunction getRuffCommand(): string | undefined {\n if (_ruffCommand !== undefined) {\n return _ruffCommand || undefined;\n }\n for (const cmd of ['uv run ruff', 'uvx ruff']) {\n try {\n execSync(`${cmd} --version`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n _ruffCommand = cmd;\n return cmd;\n } catch {\n // Try next command\n }\n }\n _ruffCommand = '';\n return undefined;\n}\n\n/**\n * Whether ruff would discover a config on disk for a file, by walking from its\n * directory up to the workspace root looking for `.ruff.toml`, `ruff.toml`, or a\n * `pyproject.toml` with a `[tool.ruff]` section — the same files ruff itself\n * resolves. The walk stops at `tree.root` so a stray config in a parent of the\n * workspace (or the home directory) is never treated as the project's. Used to\n * decide whether to nudge ruff towards import sorting (see\n * {@link ruffFixAndFormat}).\n */\nfunction hasRuffConfigOnDisk(tree: Tree, filePath: string): boolean {\n const root = path.resolve(tree.root);\n let dir = path.resolve(root, path.dirname(filePath));\n while (true) {\n if (\n existsSync(path.join(dir, '.ruff.toml')) ||\n existsSync(path.join(dir, 'ruff.toml'))\n ) {\n return true;\n }\n const pyproject = path.join(dir, 'pyproject.toml');\n if (\n existsSync(pyproject) &&\n readFileSync(pyproject, 'utf-8').includes('[tool.ruff')\n ) {\n return true;\n }\n const parent = path.dirname(dir);\n // Stop once the workspace root has been checked (or we hit the FS root).\n if (dir === root || parent === dir) {\n return false;\n }\n dir = parent;\n }\n}\n\ninterface PythonProjectRuffConfig {\n /** Project root, normalised to use forward slashes. */\n readonly root: string;\n /** Top-level importable module names declared by the project. */\n readonly modules: string[];\n /** The project's `[tool.ruff].line-length`, if set. */\n readonly lineLength?: number;\n}\n\n/**\n * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk\n * build enforces for it: its top-level module names (from\n * `[tool.hatch.build.targets.wheel].packages`) and its `[tool.ruff].line-length`.\n */\nfunction getPythonProjectRuffConfigs(tree: Tree): PythonProjectRuffConfig[] {\n const configs: PythonProjectRuffConfig[] = [];\n\n for (const project of getProjects(tree).values()) {\n const pyprojectPath = path.join(project.root, 'pyproject.toml');\n if (tree.exists(pyprojectPath)) {\n try {\n const pyproject = readToml(tree, pyprojectPath) as any;\n const wheelPackages: unknown =\n pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;\n // Record the top-level module segment (`pkg/sub` -> `pkg`), which is\n // all `known-first-party` keys off.\n const modules = Array.isArray(wheelPackages)\n ? wheelPackages\n .filter((pkg): pkg is string => typeof pkg === 'string' && !!pkg)\n .map((pkg) => pkg.split('/')[0])\n : [];\n const lineLength: unknown = pyproject?.tool?.ruff?.['line-length'];\n if (modules.length || typeof lineLength === 'number') {\n configs.push({\n root: project.root.split(path.sep).join('/'),\n modules,\n lineLength: typeof lineLength === 'number' ? lineLength : undefined,\n });\n }\n } catch {\n // Skip projects whose pyproject.toml cannot be parsed\n }\n }\n }\n\n return configs;\n}\n\n/**\n * Resolve the ruff config for the project that owns a file (the project with\n * the longest root that is a prefix of the file path). Ruff runs per-project on\n * disk, so a file's settings come from its own project — only its own module is\n * first-party (sibling workspace packages are third-party) and its own\n * line-length applies — and scoping this way keeps in-tree formatting\n * consistent with the on-disk build.\n */\nfunction getOwningProjectRuffConfig(\n filePath: string,\n configs: PythonProjectRuffConfig[],\n): PythonProjectRuffConfig | undefined {\n let owner: PythonProjectRuffConfig | undefined;\n for (const config of configs) {\n if (\n (filePath === config.root || filePath.startsWith(`${config.root}/`)) &&\n (!owner || config.root.length > owner.root.length)\n ) {\n owner = config;\n }\n }\n return owner;\n}\n\n/**\n * Run ruff check --fix and ruff format on Python file content via stdin.\n * Applies all configured lint fixes (including import sorting) and formatting.\n *\n * When no ruff config exists on disk (`hasConfig` false) ruff falls back to its\n * defaults, which omit isort — but generated projects enable rule `I` and their\n * build fails on unsorted imports (I001). In that case we add `--extend-select\n * I` so import sorting matches what the build enforces. When a config does\n * exist we defer to it entirely, honouring the user's rule selection.\n *\n * `projectConfig` carries the owning project's ruff settings, which ruff cannot\n * detect from the filesystem during generation because the project lives only\n * in the tree. We pass them via `--config` so in-tree formatting matches the\n * on-disk build: `known-first-party` (the project's own modules) keeps its\n * imports in their own group, and `line-length` keeps wrapping consistent (the\n * generated config raises it above ruff's default of 88). These are additive to\n * any on-disk config, so they are safe to pass regardless of `hasConfig`.\n */\nfunction ruffFixAndFormat(\n content: string,\n filePath: string,\n hasConfig: boolean,\n projectConfig?: PythonProjectRuffConfig,\n): string {\n const ruff = getRuffCommand();\n if (!ruff) return content;\n\n const extendSelect = hasConfig ? '' : ' --extend-select I';\n const configArgs: string[] = [];\n if (projectConfig?.modules.length) {\n configArgs.push(\n `lint.isort.known-first-party = ${JSON.stringify(projectConfig.modules)}`,\n );\n }\n if (typeof projectConfig?.lineLength === 'number') {\n configArgs.push(`line-length = ${projectConfig.lineLength}`);\n }\n const config = configArgs\n .map((arg) => ` --config ${JSON.stringify(arg)}`)\n .join('');\n\n // First apply lint fixes (import sorting, unused imports, etc.)\n try {\n const result = execSync(\n `${ruff} check --fix${extendSelect}${config} --stdin-filename ${filePath} -`,\n { input: content, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n content = result;\n } catch (e: any) {\n // ruff check exits non-zero when it finds unfixable issues,\n // but stdout still contains the fixed content\n if (e.stdout) {\n content = e.stdout;\n }\n }\n\n // Then apply formatting\n try {\n content = execSync(\n `${ruff} format${config} --stdin-filename ${filePath} -`,\n {\n input: content,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n } catch {\n // Fall through with whatever content we have\n }\n\n return content;\n}\n"],"names":["Biome","getProjects","execFileSync","execSync","existsSync","readFileSync","createRequire","path","readToml","require","url","DEFAULT_BIOME_CONFIG","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","css","linter","rules","recommended","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","BIOME_FORMATTABLE_EXTENSIONS","Set","formatFilesInSubtree","tree","dir","changedFiles","listChanges","filter","file","type","startsWith","pyFiles","endsWith","otherFiles","has","extname","pythonProjectConfigs","length","getPythonProjectRuffConfigs","content","ruffFixAndFormat","toString","hasRuffConfigOnDisk","getOwningProjectRuffConfig","write","join","formatWithBiomeCli","formatWithBiomeApi","biome","getBiomeCommand","command","args","input","encoding","cwd","stdio","projectKey","openProject","treeConfig","read","applyConfiguration","JSON","parse","formatContent","filePath","_biomeCommands","Map","get","undefined","pkgJsonPath","resolve","paths","dirname","pkgJson","binRelative","bin","binPath","process","execPath","set","_ruffCommand","getRuffCommand","cmd","pyproject","parent","configs","project","values","pyprojectPath","exists","wheelPackages","tool","hatch","build","targets","wheel","packages","modules","Array","isArray","pkg","map","split","lineLength","ruff","push","sep","owner","config","hasConfig","projectConfig","extendSelect","configArgs","stringify","arg","result","e","stdout"],"mappings":"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"}