@aws/nx-plugin 1.0.0-rc.55 → 1.0.0-rc.57

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.
Files changed (81) hide show
  1. package/migrations.json +13 -3
  2. package/package.json +1 -1
  3. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.d.ts +6 -0
  4. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.js +89 -0
  5. package/src/migrations/latest/dynamodb-local-remove-optimize-flag/migration.js.map +1 -0
  6. package/src/migrations/latest/modernize-function-props-cast/migration.js +0 -1
  7. package/src/migrations/latest/modernize-function-props-cast/migration.js.map +1 -1
  8. package/src/migrations/latest/order-access-log-delivery-after-bucket-policy/migration.js +0 -1
  9. package/src/migrations/latest/order-access-log-delivery-after-bucket-policy/migration.js.map +1 -1
  10. package/src/migrations/latest/restrict-cors-to-custom-domains/migration.js +0 -2
  11. package/src/migrations/latest/restrict-cors-to-custom-domains/migration.js.map +1 -1
  12. package/src/migrations/latest/terraform-bootstrap-adopt-existing-bucket/migration.js +0 -1
  13. package/src/migrations/latest/terraform-bootstrap-adopt-existing-bucket/migration.js.map +1 -1
  14. package/src/migrations/latest/ts-agent-session-management-support/migration.d.ts +6 -0
  15. package/src/migrations/latest/ts-agent-session-management-support/migration.js +449 -0
  16. package/src/migrations/latest/ts-agent-session-management-support/migration.js.map +1 -0
  17. package/src/migrations/latest/user-identity-waf-allow-localhost-callback/migration.js +0 -2
  18. package/src/migrations/latest/user-identity-waf-allow-localhost-callback/migration.js.map +1 -1
  19. package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +3 -1
  20. package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +12 -12
  21. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +1 -1
  22. package/src/py/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +2 -1
  23. package/src/py/agent/a2a-connection/files/agent-connection/app/__targetAgentSnakeCase___client_strands.py.template +2 -1
  24. package/src/py/agent/a2a-connection/files/langchain/agent-connection/app/__targetAgentSnakeCase___client_langchain.py.template +2 -1
  25. package/src/py/agent/generator.js +4 -0
  26. package/src/py/agent/generator.js.map +1 -1
  27. package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +2 -1
  28. package/src/py/agent/mcp-connection/files/agent-connection/app/__mcpServerSnakeCase___client_strands.py.template +2 -1
  29. package/src/py/agent/mcp-connection/files/langchain/agent-connection/app/__mcpServerSnakeCase___client_langchain.py.template +2 -1
  30. package/src/py/agent/react-connection/__snapshots__/generator.spec.ts.snap +6 -0
  31. package/src/py/agent/schema.d.js.map +1 -1
  32. package/src/py/agent/schema.d.ts +3 -0
  33. package/src/py/agent/schema.json +8 -0
  34. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +0 -2
  35. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -2
  36. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +663 -19
  37. package/src/ts/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +2 -1
  38. package/src/ts/agent/a2a-connection/files/agent-connection/app/__targetAgentKebabCase__-client-strands.ts.template +1 -1
  39. package/src/ts/agent/files/ag-ui/index.ts.template +10 -1
  40. package/src/ts/agent/files/common/agent.ts.template +11 -3
  41. package/src/ts/agent/files/common/session.ts.template +43 -0
  42. package/src/ts/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +1 -0
  43. package/src/ts/agent/generator.d.ts +4 -0
  44. package/src/ts/agent/generator.js +21 -0
  45. package/src/ts/agent/generator.js.map +1 -1
  46. package/src/ts/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +2 -1
  47. package/src/ts/agent/mcp-connection/files/agent-connection/app/__mcpServerKebabCase__-client-strands.ts.template +2 -1
  48. package/src/ts/agent/react-connection/__snapshots__/generator.spec.ts.snap +8 -4
  49. package/src/ts/agent/react-connection/files/src/components/__agentNameClassName__AgentClientProvider.tsx.template +2 -1
  50. package/src/ts/agent/schema.d.js.map +1 -1
  51. package/src/ts/agent/schema.d.ts +3 -0
  52. package/src/ts/agent/schema.json +8 -0
  53. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +0 -2
  54. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -2
  55. package/src/ts/nx-migration/files/migration.ts.template +4 -0
  56. package/src/utils/agent-chat/files/common/agentcore.ts.template +2 -2
  57. package/src/utils/agent-connection/agent-connection.js +4 -0
  58. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  59. package/src/utils/agent-connection/files/core-runtime-config/runtime-config.ts.template +11 -1
  60. package/src/utils/agent-connection/files/core-strands/base/model-errors-strands.ts.template +26 -15
  61. package/src/utils/agent-connection/files/core-strands/base/tool-errors-strands.ts.template +23 -16
  62. package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +4 -3
  63. package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +4 -0
  64. package/src/utils/agent-core-constructs/agent-core-constructs.js +7 -1
  65. package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
  66. package/src/utils/agent-core-constructs/files/cdk/app/agent-core/__nameKebabCase__/__nameKebabCase__.ts.template +131 -3
  67. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +180 -1
  68. package/src/utils/ast.d.ts +5 -0
  69. package/src/utils/ast.js +25 -0
  70. package/src/utils/ast.js.map +1 -1
  71. package/src/utils/connection/open-api/files/components/__apiNameClassName__Provider.tsx.template +2 -0
  72. package/src/utils/files/common/scripts/src/dynamodb/start-container.ts.template +0 -1
  73. package/src/utils/migration-versions.d.ts +15 -10
  74. package/src/utils/migration-versions.js +48 -36
  75. package/src/utils/migration-versions.js.map +1 -1
  76. package/src/utils/version-upgrade-migration/nx-package-updates.d.ts +18 -8
  77. package/src/utils/version-upgrade-migration/nx-package-updates.js +19 -11
  78. package/src/utils/version-upgrade-migration/nx-package-updates.js.map +1 -1
  79. package/src/utils/version-upgrade-migration/register.d.ts +6 -1
  80. package/src/utils/version-upgrade-migration/register.js +8 -3
  81. package/src/utils/version-upgrade-migration/register.js.map +1 -1
@@ -1,6 +1,17 @@
1
- import { Fn, Lazy, Names, Stack } from 'aws-cdk-lib';
1
+ import { Fn, Lazy, Names<%_ if (session === 's3') { _%>, RemovalPolicy<%_ } _%>, Stack } from 'aws-cdk-lib';
2
2
  import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
3
3
  import { Connections, IConnectable } from 'aws-cdk-lib/aws-ec2';
4
+ <%_ if (session === 's3') { _%>
5
+ import { BlockPublicAccess, Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
6
+ import { Key } from 'aws-cdk-lib/aws-kms';
7
+ import {
8
+ CfnDelivery,
9
+ CfnDeliveryDestination,
10
+ CfnDeliverySource,
11
+ LogGroup,
12
+ RetentionDays,
13
+ } from 'aws-cdk-lib/aws-logs';
14
+ <%_ } _%>
4
15
  import { Construct } from 'constructs';
5
16
  import * as path from 'path';
6
17
  import * as url from 'url';
@@ -13,10 +24,13 @@ import {
13
24
  RuntimeAuthorizerConfiguration,
14
25
  <%_ } _%>
15
26
  } from 'aws-cdk-lib/aws-bedrockagentcore';
16
- import { <%_ if (serverProtocol !== 'mcp') { _%>PolicyStatement, <%_ } _%>IGrantable, IPrincipal<%_ if (auth === 'iam') { _%>, Grant<%_ } _%> } from 'aws-cdk-lib/aws-iam';
27
+ import { <%_ if (serverProtocol !== 'mcp') { _%>PolicyStatement, <%_ } _%><%_ if (session === 's3') { _%>Effect, ServicePrincipal, <%_ } _%>IGrantable, IPrincipal<%_ if (auth === 'iam') { _%>, Grant<%_ } _%> } from 'aws-cdk-lib/aws-iam';
17
28
  <%_ if (auth === 'cognito') { _%>
18
29
  import { IUserPool, IUserPoolClient } from 'aws-cdk-lib/aws-cognito';
19
30
  <%_ } _%>
31
+ <%_ if (session === 's3') { _%>
32
+ import { suppressRules } from '../../../core/checkov<% if (esm) { %>.js<% } %>';
33
+ <%_ } _%>
20
34
  import { RuntimeConfig } from '../../../core/runtime-config<% if (esm) { %>.js<% } %>';
21
35
  import { findWorkspaceRoot } from '../../../core/workspace<% if (esm) { %>.js<% } %>';
22
36
 
@@ -72,6 +86,111 @@ export class <%- nameClassName %> extends Construct implements IGrantable, IConn
72
86
  <%_ if (auth === 'cognito') { _%>
73
87
  const { identity, ...restProps } = props;
74
88
 
89
+ <%_ } _%>
90
+ <%_ if (session === 's3') { _%>
91
+ const sessionKey = new Key(this, 'SessionKey', {
92
+ enableKeyRotation: true,
93
+ });
94
+
95
+ // Allow CloudWatch Logs to use the session key for server access log delivery.
96
+ const stack = Stack.of(this);
97
+ sessionKey.addToResourcePolicy(
98
+ new PolicyStatement({
99
+ effect: Effect.ALLOW,
100
+ principals: [new ServicePrincipal(`logs.${stack.region}.amazonaws.com`)],
101
+ actions: [
102
+ 'kms:Encrypt',
103
+ 'kms:Decrypt',
104
+ 'kms:ReEncrypt*',
105
+ 'kms:GenerateDataKey*',
106
+ 'kms:DescribeKey',
107
+ ],
108
+ resources: ['*'],
109
+ conditions: {
110
+ ArnLike: {
111
+ 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${stack.region}:${stack.account}:log-group:*`,
112
+ },
113
+ },
114
+ }),
115
+ );
116
+
117
+ const sessionAccessLogs = new LogGroup(this, 'SessionAccessLogs', {
118
+ retention: RetentionDays.ONE_YEAR,
119
+ encryptionKey: sessionKey,
120
+ removalPolicy: RemovalPolicy.DESTROY,
121
+ });
122
+
123
+ const sessionBucket = new Bucket(this, 'SessionBucket', {
124
+ enforceSSL: true,
125
+ removalPolicy: RemovalPolicy.RETAIN,
126
+ encryption: BucketEncryption.KMS,
127
+ encryptionKey: sessionKey,
128
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
129
+ });
130
+ suppressRules(
131
+ sessionBucket,
132
+ ['CKV_AWS_21'],
133
+ 'Session data does not need versioning enabled',
134
+ );
135
+ suppressRules(
136
+ sessionBucket,
137
+ ['CKV2_AWS_61'],
138
+ 'Lifecycle configuration not required for session data',
139
+ );
140
+ suppressRules(
141
+ sessionBucket,
142
+ ['CKV_AWS_144'],
143
+ 'Cross-region replication not required for session data',
144
+ );
145
+ suppressRules(
146
+ sessionBucket,
147
+ ['CKV2_AWS_62'],
148
+ 'Event notifications not required for session data',
149
+ );
150
+ suppressRules(
151
+ sessionBucket,
152
+ ['CKV_AWS_18'],
153
+ 'Server access logs are delivered to CloudWatch Logs',
154
+ );
155
+
156
+ const sessionAccessLogsSource: CfnDeliverySource = new CfnDeliverySource(
157
+ this,
158
+ 'SessionAccessLogsSource',
159
+ {
160
+ name: Lazy.string({
161
+ produce: () =>
162
+ Names.uniqueResourceName(sessionAccessLogsSource, {
163
+ maxLength: 60,
164
+ }),
165
+ }),
166
+ logType: 'S3_SERVER_ACCESS_LOGS',
167
+ resourceArn: sessionBucket.bucketArn,
168
+ },
169
+ );
170
+ const sessionBucketPolicy = sessionBucket.policy;
171
+ if (sessionBucketPolicy) {
172
+ sessionAccessLogsSource.node.addDependency(sessionBucketPolicy);
173
+ }
174
+ const sessionAccessLogsDestination: CfnDeliveryDestination =
175
+ new CfnDeliveryDestination(this, 'SessionAccessLogsDestination', {
176
+ name: Lazy.string({
177
+ produce: () =>
178
+ Names.uniqueResourceName(sessionAccessLogsDestination, {
179
+ maxLength: 60,
180
+ }),
181
+ }),
182
+ destinationResourceArn: sessionAccessLogs.logGroupArn,
183
+ });
184
+ const sessionAccessLogsDelivery = new CfnDelivery(
185
+ this,
186
+ 'SessionAccessLogsDelivery',
187
+ {
188
+ deliverySourceName: sessionAccessLogsSource.name,
189
+ deliveryDestinationArn: sessionAccessLogsDestination.attrArn,
190
+ },
191
+ );
192
+ sessionAccessLogsDelivery.addDependency(sessionAccessLogsSource);
193
+
75
194
  <%_ } _%>
76
195
  this.agentCoreRuntime = new Runtime(this, '<%- nameClassName %>', {
77
196
  runtimeName: Lazy.string({
@@ -114,12 +233,21 @@ export class <%- nameClassName %> extends Construct implements IGrantable, IConn
114
233
  }),
115
234
  );
116
235
 
236
+ <%_ } _%>
237
+ <%_ if (session === 's3') { _%>
238
+ sessionBucket.grantReadWrite(this.agentCoreRuntime);
239
+
117
240
  <%_ } _%>
118
241
  rc.grantReadAppConfig(this.agentCoreRuntime);
119
242
 
120
243
  rc.set('agentcore', 'agentRuntimes', {
121
244
  ...rc.get('agentcore').agentRuntimes,
122
- <%- nameClassName %>: this.agentCoreRuntime.agentRuntimeArn,
245
+ <%- nameClassName %>: {
246
+ arn: this.agentCoreRuntime.agentRuntimeArn,
247
+ <%_ if (session === 's3') { _%>
248
+ session: { bucketName: sessionBucket.bucketName },
249
+ <%_ } _%>
250
+ },
123
251
  });
124
252
  }
125
253
 
@@ -1,3 +1,14 @@
1
+ <%_ if (session === 's3') { _%>
2
+ terraform {
3
+ required_providers {
4
+ random = {
5
+ source = "hashicorp/random"
6
+ version = "<%- randomProviderVersion %>"
7
+ }
8
+ }
9
+ }
10
+
11
+ <%_ } _%>
1
12
  variable "env" {
2
13
  description = "Environment variables to pass to the agent core runtime"
3
14
  type = map(string)
@@ -60,11 +71,156 @@ variable "user_pool_client_ids" {
60
71
  type = list(string)
61
72
  }
62
73
 
74
+ <%_ } _%>
75
+ <%_ if (auth === 'cognito' || session === 's3') { _%>
63
76
  data "aws_region" "current" {}
77
+ <%_ } _%>
78
+ <%_ if (session === 's3') { _%>
79
+ data "aws_caller_identity" "current" {}
80
+ <%_ } _%>
81
+ <%_ if (auth === 'cognito') { _%>
64
82
 
65
83
  locals {
66
84
  aws_region = data.aws_region.current.id
67
85
  }
86
+ <%_ } _%>
87
+ <%_ if (session === 's3') { _%>
88
+
89
+ locals {
90
+ # Delivery source/destination names have a 60 character maximum. Truncate
91
+ # the agent name so these stay within the limit.
92
+ session_access_logs_name_prefix = substr("<%= nameKebabCase %>", 0, 30)
93
+ }
94
+
95
+ # Uniquifies resource names below so this construct can be deployed more than
96
+ # once (e.g. across stages) within the same account/region.
97
+ resource "random_id" "session_unique_suffix" {
98
+ byte_length = 4
99
+ }
100
+
101
+ # KMS key encrypting the session bucket and its server access logs.
102
+ resource "aws_kms_key" "session" {
103
+ description = "KMS key for <%= nameKebabCase %> agent session data"
104
+ enable_key_rotation = true
105
+ deletion_window_in_days = 7
106
+
107
+ policy = jsonencode({
108
+ Version = "2012-10-17"
109
+ Statement = [
110
+ {
111
+ Sid = "EnableIAMUserPermissions"
112
+ Effect = "Allow"
113
+ Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
114
+ Action = "kms:*"
115
+ Resource = "*"
116
+ },
117
+ {
118
+ Sid = "AllowCloudWatchLogs"
119
+ Effect = "Allow"
120
+ Principal = { Service = "logs.${data.aws_region.current.region}.amazonaws.com" }
121
+ Action = [
122
+ "kms:Encrypt",
123
+ "kms:Decrypt",
124
+ "kms:ReEncrypt*",
125
+ "kms:GenerateDataKey*",
126
+ "kms:DescribeKey"
127
+ ]
128
+ Resource = "*"
129
+ Condition = {
130
+ ArnLike = {
131
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:*"
132
+ }
133
+ }
134
+ }
135
+ ]
136
+ })
137
+
138
+ tags = var.tags
139
+ }
140
+
141
+ # CloudWatch log group receiving the session bucket's server access logs.
142
+ resource "aws_cloudwatch_log_group" "session_access_logs" {
143
+ name = "/aws/s3/<%= nameKebabCase %>-session-access-logs-${random_id.session_unique_suffix.hex}"
144
+ retention_in_days = 365
145
+ kms_key_id = aws_kms_key.session.arn
146
+
147
+ tags = var.tags
148
+ }
149
+
150
+ # Bucket storing the agent's session data
151
+ resource "aws_s3_bucket" "session" {
152
+ #checkov:skip=CKV2_AWS_61:Lifecycle configuration not required for session data
153
+ #checkov:skip=CKV_AWS_144:Cross-region replication not required for session data
154
+ #checkov:skip=CKV2_AWS_62:Event notifications not required for session data
155
+ #checkov:skip=CKV_AWS_18:Server access logs are delivered to CloudWatch Logs (see aws_cloudwatch_log_delivery.session)
156
+ #checkov:skip=CKV_AWS_21:Session data does not need versioning enabled
157
+ force_destroy = false
158
+ }
159
+
160
+ resource "aws_s3_bucket_server_side_encryption_configuration" "session" {
161
+ bucket = aws_s3_bucket.session.id
162
+
163
+ rule {
164
+ apply_server_side_encryption_by_default {
165
+ kms_master_key_id = aws_kms_key.session.arn
166
+ sse_algorithm = "aws:kms"
167
+ }
168
+ }
169
+ }
170
+
171
+ resource "aws_s3_bucket_public_access_block" "session" {
172
+ bucket = aws_s3_bucket.session.id
173
+
174
+ block_public_acls = true
175
+ block_public_policy = true
176
+ ignore_public_acls = true
177
+ restrict_public_buckets = true
178
+ }
179
+
180
+ resource "aws_s3_bucket_policy" "session" {
181
+ bucket = aws_s3_bucket.session.id
182
+
183
+ policy = jsonencode({
184
+ Version = "2012-10-17"
185
+ Statement = [
186
+ {
187
+ Sid = "DenyInsecureConnections"
188
+ Effect = "Deny"
189
+ Principal = "*"
190
+ Action = "s3:*"
191
+ Resource = [
192
+ aws_s3_bucket.session.arn,
193
+ "${aws_s3_bucket.session.arn}/*"
194
+ ]
195
+ Condition = {
196
+ Bool = {
197
+ "aws:SecureTransport" = "false"
198
+ }
199
+ }
200
+ }
201
+ ]
202
+ })
203
+ }
204
+
205
+ # Deliver the session bucket's server access logs to CloudWatch Logs.
206
+ resource "aws_cloudwatch_log_delivery_source" "session" {
207
+ name = "${local.session_access_logs_name_prefix}-access-logs-source-${random_id.session_unique_suffix.hex}"
208
+ log_type = "S3_SERVER_ACCESS_LOGS"
209
+ resource_arn = aws_s3_bucket.session.arn
210
+ }
211
+
212
+ resource "aws_cloudwatch_log_delivery_destination" "session" {
213
+ name = "${local.session_access_logs_name_prefix}-access-logs-dest-${random_id.session_unique_suffix.hex}"
214
+
215
+ delivery_destination_configuration {
216
+ destination_resource_arn = aws_cloudwatch_log_group.session_access_logs.arn
217
+ }
218
+ }
219
+
220
+ resource "aws_cloudwatch_log_delivery" "session" {
221
+ delivery_source_name = aws_cloudwatch_log_delivery_source.session.name
222
+ delivery_destination_arn = aws_cloudwatch_log_delivery_destination.session.arn
223
+ }
68
224
 
69
225
  <%_ } _%>
70
226
  module "agent_core_runtime" {
@@ -107,6 +263,29 @@ module "agent_core_runtime" {
107
263
  "arn:aws:bedrock:*:*:foundation-model/*",
108
264
  "arn:aws:bedrock:*:*:inference-profile/*"
109
265
  ]
266
+ }<% } %><% if (session === 's3') { %>,
267
+ # Grant access for the agent to read/write its session data
268
+ {
269
+ Effect = "Allow"
270
+ Action = [
271
+ "s3:GetObject",
272
+ "s3:PutObject",
273
+ "s3:DeleteObject",
274
+ "s3:ListBucket"
275
+ ]
276
+ Resource = [
277
+ aws_s3_bucket.session.arn,
278
+ "${aws_s3_bucket.session.arn}/*"
279
+ ]
280
+ },
281
+ # Grant access for the agent to use the session bucket's KMS key
282
+ {
283
+ Effect = "Allow"
284
+ Action = [
285
+ "kms:Decrypt",
286
+ "kms:GenerateDataKey*"
287
+ ]
288
+ Resource = [aws_kms_key.session.arn]
110
289
  }<% } %>
111
290
  ], var.additional_iam_policy_statements)
112
291
  tags = var.tags
@@ -118,7 +297,7 @@ module "add_agent_runtime_to_runtime_config" {
118
297
 
119
298
  namespace = "agentcore"
120
299
  key = "agentRuntimes"
121
- value = { "<%= nameClassName %>" = module.agent_core_runtime.agent_core_runtime_arn }
300
+ value = { "<%= nameClassName %>" = { arn = module.agent_core_runtime.agent_core_runtime_arn<% if (session === 's3') { %>, session = { bucketName = aws_s3_bucket.session.bucket }<% } %> } }
122
301
 
123
302
  depends_on = [module.agent_core_runtime]
124
303
  }
@@ -54,6 +54,11 @@ export declare const matchGritQL: (tree: Tree, filePath: string, pattern: string
54
54
  * matches. The file is not modified.
55
55
  */
56
56
  export declare const captureGritQL: (tree: Tree, filePath: string, pattern: string) => Promise<string | undefined>;
57
+ /**
58
+ * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`
59
+ * for `$mod`) instead of the whole matched node.
60
+ */
61
+ export declare const captureGritQLVariable: (tree: Tree, filePath: string, pattern: string, variableName: string) => Promise<string | undefined>;
57
62
  /**
58
63
  * Return the text of every node matching the GritQL pattern, in document order.
59
64
  */
package/src/utils/ast.js CHANGED
@@ -226,6 +226,31 @@ export const addDestructuredImport = async (tree, filePath, variableNames, from)
226
226
  }
227
227
  return captured;
228
228
  };
229
+ /**
230
+ * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`
231
+ * for `$mod`) instead of the whole matched node.
232
+ */ export const captureGritQLVariable = async (tree, filePath, pattern, variableName)=>{
233
+ if (!tree.exists(filePath)) return undefined;
234
+ ensureGritDir(tree);
235
+ const source = tree.read(filePath).toString();
236
+ // The underlying binding lookup requires the `$` sigil.
237
+ const name = variableName.startsWith('$') ? variableName : `$${variableName}`;
238
+ let captured;
239
+ try {
240
+ const query = new QueryBuilder(pattern);
241
+ query.filter((node)=>{
242
+ captured ??= node.var(name) ?? undefined;
243
+ return true;
244
+ });
245
+ await query.applyToFile({
246
+ path: filePath,
247
+ content: source
248
+ });
249
+ } catch {
250
+ return undefined;
251
+ }
252
+ return captured;
253
+ };
229
254
  /**
230
255
  * Return the text of every node matching the GritQL pattern, in document order.
231
256
  */ export const captureAllGritQL = async (tree, filePath, pattern)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git';\nimport { isEsmWorkspace } from './module-format';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureAllGritQL","push","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git';\nimport { isEsmWorkspace } from './module-format';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`\n * for `$mod`) instead of the whole matched node.\n */\nexport const captureGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.var(name) ?? undefined;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureGritQLVariable","name","var","captureAllGritQL","push","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace"],"mappings":"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"}
@@ -26,6 +26,8 @@ const useCreate<%- apiNameClassName %>Client = (): <%- apiNameClassName %> => {
26
26
  const runtimeConfig = useRuntimeConfig();
27
27
  <%_ if (isAgentRuntime) { _%>
28
28
  const agentRuntimeValue = runtimeConfig.agentRuntimes.<%- apiNameClassName %>;
29
+ // A local-dev override is a plain URL; otherwise it's the agent's runtime
30
+ // ARN, so build the invocation URL from it.
29
31
  const apiUrl = agentRuntimeValue.startsWith('arn:')
30
32
  ? buildAgentCoreHttpUrl(agentRuntimeValue)
31
33
  : agentRuntimeValue;
@@ -64,7 +64,6 @@ for (;;) {
64
64
  '-sharedDb',
65
65
  '-dbPath', './data',
66
66
  '-port', `${port}`,
67
- '-optimizeDbBeforeStartup',
68
67
  ];
69
68
  const create = spawnSync(containerEngine, runArgs, { stdio: 'pipe' });
70
69
  if (create.status === 0) {
@@ -18,7 +18,8 @@ import { compare } from 'semver';
18
18
  * - The weekly `update-versions` PR backfills the version of the release that
19
19
  * shipped each migration, moving and re-keying it accordingly
20
20
  * (`scripts/backfill-migration-versions.ts`), so source converges on the
21
- * versions of everything already released.
21
+ * versions of everything already released. `packageJsonUpdates` entries are
22
+ * dated in place there too, since their keys never change.
22
23
  *
23
24
  * A version already in source always wins, so backfilled entries are stable.
24
25
  *
@@ -32,8 +33,9 @@ export interface MigrationsJson {
32
33
  } & Record<string, unknown>>;
33
34
  /**
34
35
  * Declarative dependency bumps `nx migrate` applies to the root manifest.
35
- * Keyed `<dir>-<name>` like the migrations so entries from different releases
36
- * can't collide; nx itself gates on the entry's `version`.
36
+ * Keyed by what the bump targets rather than the release that ships it, so an
37
+ * entry is unique from the moment it is written and one release's bump can sit
38
+ * beside the next; nx itself gates on the entry's `version`.
37
39
  */
38
40
  packageJsonUpdates?: Record<string, {
39
41
  version: string;
@@ -44,15 +46,19 @@ export declare const compareVersions: typeof compare;
44
46
  /** Whether a string is an exact semver version. */
45
47
  export declare const isValidVersion: (version: string) => boolean;
46
48
  /**
47
- * Map of migration key -> version of the earliest release that registers it,
48
- * resolved only for the entries still missing a `version` (one already recorded
49
- * in source wins, so its history doesn't need reading). A migration that hasn't
50
- * been released is absent.
49
+ * Map of entry key -> version of the earliest release that registers it, across
50
+ * both `generators` and `packageJsonUpdates`. Resolved only for the entries still
51
+ * missing a version (one already recorded in source wins, so its history doesn't
52
+ * need reading). An entry that hasn't been released is absent.
51
53
  *
52
54
  * Walks releases newest first and stops as soon as every entry is resolved: an
53
55
  * entry that has disappeared from a release's `migrations.json` was first
54
56
  * registered by the release after it, which is the one that shipped it.
55
57
  *
58
+ * Both sections resolve the same way and share the walk, so the release doesn't
59
+ * read tag history twice. Keys can't collide: a migration is keyed by its own
60
+ * name, an nx bump by the nx version it moves to.
61
+ *
56
62
  * @param migrations parsed source migrations.json
57
63
  * @param versions released versions in descending semver order
58
64
  * @param readReleasedMigrations reads the `migrations.json` published by a given
@@ -61,9 +67,8 @@ export declare const isValidVersion: (version: string) => boolean;
61
67
  export declare const readShippedMigrationVersions: (migrations: MigrationsJson, versions: string[], readReleasedMigrations: (version: string) => MigrationsJson | undefined) => Record<string, string>;
62
68
  /**
63
69
  * Return a copy of the migrations collection with a `version` stamped onto
64
- * every generator entry, preserving any already present, and any
65
- * `packageJsonUpdates` entry still keyed `latest` re-keyed to the version it
66
- * ships under.
70
+ * every generator entry, preserving any already present, and the pending version
71
+ * recorded on any `packageJsonUpdates` entry still holding `latest`.
67
72
  *
68
73
  * @param migrations parsed migrations.json to stamp
69
74
  * @param shippedVersions migration key -> version of the earliest release