@aws/nx-plugin 1.0.0-rc.87 → 1.0.0-rc.89

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 (133) hide show
  1. package/LICENSE-THIRD-PARTY +9977 -16000
  2. package/migrations.json +124 -76
  3. package/package.json +7 -7
  4. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
  5. package/src/infra/app/__snapshots__/generator.spec.ts.snap +7 -7
  6. package/src/migrations/latest/nx-parallel-default/metadata.json +3 -0
  7. package/src/migrations/latest/nx-parallel-default/migration.d.ts +14 -0
  8. package/src/migrations/latest/nx-parallel-default/migration.js +29 -0
  9. package/src/migrations/latest/nx-parallel-default/migration.js.map +1 -0
  10. package/src/migrations/latest/python-version-drop-exact-patch/metadata.json +3 -0
  11. package/src/migrations/latest/python-version-drop-exact-patch/migration.js +43 -0
  12. package/src/migrations/latest/python-version-drop-exact-patch/migration.js.map +1 -0
  13. package/src/migrations/latest/react-website-nx-react-express-override/metadata.json +3 -0
  14. package/src/migrations/latest/react-website-nx-react-express-override/migration.d.ts +31 -0
  15. package/src/migrations/latest/react-website-nx-react-express-override/migration.js +74 -0
  16. package/src/migrations/latest/react-website-nx-react-express-override/migration.js.map +1 -0
  17. package/src/migrations/v1.0.0-rc.77/0001-dynamodb-configurable-encryption/migration.js.map +1 -0
  18. package/src/migrations/v1.0.0-rc.77/0002-user-identity-configurable-mfa/migration.js.map +1 -0
  19. package/src/migrations/v1.0.0-rc.78/0001-static-website-configurable-waf-encryption/migration.js.map +1 -0
  20. package/src/migrations/v1.0.0-rc.79/0001-py-agent-ag-ui-hooks/migration.js.map +1 -0
  21. package/src/migrations/v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path/migration.js.map +1 -0
  22. package/src/migrations/v1.0.0-rc.82/0002-runtime-config-namespace-defaults/migration.js.map +1 -0
  23. package/src/migrations/v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs/migration.js.map +1 -0
  24. package/src/migrations/v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection/migration.js.map +1 -0
  25. package/src/migrations/v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs/migration.js.map +1 -0
  26. package/src/migrations/v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  27. package/src/migrations/v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  28. package/src/migrations/v1.0.0-rc.84/0003-terraform-declare-used-providers/migration.js.map +1 -0
  29. package/src/migrations/v1.0.0-rc.84/0004-waf-log-group-cmk-encryption/migration.js.map +1 -0
  30. package/src/migrations/v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  31. package/src/migrations/v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
  32. package/src/migrations/v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
  33. package/src/preset/__snapshots__/generator.spec.ts.snap +5 -4
  34. package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +2 -2
  35. package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +5 -5
  36. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +4 -4
  37. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
  38. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +12 -12
  39. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
  40. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +3 -3
  41. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +2 -2
  42. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +7 -7
  43. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -8
  44. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +12 -12
  45. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +3 -3
  46. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +1 -1
  47. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
  48. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
  49. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +2 -2
  50. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +5 -5
  51. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +59 -49
  52. package/src/ts/react-website/app/generator.d.ts +3 -0
  53. package/src/ts/react-website/app/generator.js +25 -1
  54. package/src/ts/react-website/app/generator.js.map +1 -1
  55. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +2 -2
  56. package/src/utils/init.d.ts +1 -0
  57. package/src/utils/init.js +2 -0
  58. package/src/utils/init.js.map +1 -1
  59. package/src/utils/lambda-runtime-resolution.d.ts +0 -21
  60. package/src/utils/lambda-runtime-resolution.js +0 -14
  61. package/src/utils/lambda-runtime-resolution.js.map +1 -1
  62. package/src/utils/version-upgrade-migration/sync-python-language-version.d.ts +2 -2
  63. package/src/utils/version-upgrade-migration/sync-python-language-version.js +2 -2
  64. package/src/utils/version-upgrade-migration/sync-python-language-version.js.map +1 -1
  65. package/src/utils/versions.d.ts +58 -62
  66. package/src/utils/versions.js +57 -60
  67. package/src/utils/versions.js.map +1 -1
  68. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +0 -1
  69. package/src/migrations/latest/dynamodb-configurable-encryption/migration.js.map +0 -1
  70. package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js.map +0 -1
  71. package/src/migrations/latest/py-agent-ag-ui-hooks/migration.js.map +0 -1
  72. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +0 -1
  73. package/src/migrations/latest/runtime-config-namespace-defaults/migration.js.map +0 -1
  74. package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js.map +0 -1
  75. package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js.map +0 -1
  76. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +0 -1
  77. package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js.map +0 -1
  78. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +0 -1
  79. package/src/migrations/latest/terraform-load-runtime-config-src-path/migration.js.map +0 -1
  80. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +0 -1
  81. package/src/migrations/latest/user-identity-configurable-mfa/migration.js.map +0 -1
  82. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +0 -1
  83. /package/src/migrations/latest/{agentcore-harness-no-tools-by-default → python-version-drop-exact-patch}/migration.d.ts +0 -0
  84. /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/__snapshots__/migration.spec.ts.snap +0 -0
  85. /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/metadata.json +0 -0
  86. /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/migration.d.ts +0 -0
  87. /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/migration.js +0 -0
  88. /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/metadata.json +0 -0
  89. /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/migration.d.ts +0 -0
  90. /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/migration.js +0 -0
  91. /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/metadata.json +0 -0
  92. /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/migration.d.ts +0 -0
  93. /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/migration.js +0 -0
  94. /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/metadata.json +0 -0
  95. /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/migration.d.ts +0 -0
  96. /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/migration.js +0 -0
  97. /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/metadata.json +0 -0
  98. /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/migration.d.ts +0 -0
  99. /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/migration.js +0 -0
  100. /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/metadata.json +0 -0
  101. /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/migration.d.ts +0 -0
  102. /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/migration.js +0 -0
  103. /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/metadata.json +0 -0
  104. /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/migration.d.ts +0 -0
  105. /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/migration.js +0 -0
  106. /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/__snapshots__/migration.spec.ts.snap +0 -0
  107. /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/metadata.json +0 -0
  108. /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/migration.d.ts +0 -0
  109. /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/migration.js +0 -0
  110. /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/metadata.json +0 -0
  111. /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/migration.d.ts +0 -0
  112. /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/migration.js +0 -0
  113. /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/__snapshots__/migration.spec.ts.snap +0 -0
  114. /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/metadata.json +0 -0
  115. /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/migration.d.ts +0 -0
  116. /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/migration.js +0 -0
  117. /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/__snapshots__/migration.spec.ts.snap +0 -0
  118. /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/metadata.json +0 -0
  119. /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/migration.d.ts +0 -0
  120. /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/migration.js +0 -0
  121. /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.84/0003-terraform-declare-used-providers}/metadata.json +0 -0
  122. /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.84/0003-terraform-declare-used-providers}/migration.d.ts +0 -0
  123. /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.84/0003-terraform-declare-used-providers}/migration.js +0 -0
  124. /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/__snapshots__/migration.spec.ts.snap +0 -0
  125. /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/metadata.json +0 -0
  126. /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/migration.d.ts +0 -0
  127. /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/migration.js +0 -0
  128. /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/__snapshots__/migration.spec.ts.snap +0 -0
  129. /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/metadata.json +0 -0
  130. /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/migration.d.ts +0 -0
  131. /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/migration.js +0 -0
  132. /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy}/metadata.json +0 -0
  133. /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy}/migration.js +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/static-website-configurable-waf-encryption/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport {\n addDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring existing workspaces up to the generators' current StaticWebsite\n * configurability:\n *\n * - The vended `StaticWebsite` CDK construct gains `enableWaf`, `encryption`,\n * `encryptionKey` and `enableKeyRotation` props. The WAF Web ACL is now\n * optional, the KMS key used to encrypt the website and distribution log\n * buckets is now overridable (or skippable in favour of another\n * `BucketEncryption`), and the auto-created key's rotation is configurable.\n * - Each vended per-website app construct (`app/static-websites/*.ts`) gains\n * an optional `props` constructor parameter so these can be configured per\n * app, rather than only by hand-editing the vended construct.\n * - The vended Terraform static-website core module gains the equivalent\n * `enable_waf`, `encryption`, `kms_key_arn`, `create_kms_key` and\n * `enable_key_rotation` variables. The CloudFront distribution's\n * `lifecycle.replace_triggered_by` on the WAF ACL is also dropped - it\n * can't resolve once the ACL has a count of 0 and no prior state to\n * reference, which broke `terraform plan` on any greenfield deployment\n * with `enable_waf = false`.\n * - Each vended per-website Terraform app module (`app/static-websites/<name>/<name>.tf`)\n * gains pass-through `custom_domain_names`, `acm_certificate_arn`,\n * `enable_waf`, `encryption`, `kms_key_arn`, `create_kms_key` and\n * `enable_key_rotation` variables forwarded to the core module call,\n * matching the pass-through convention every other app-wraps-core\n * Terraform module in this plugin already follows (`rdb`, `agent-core`,\n * `dcr-proxies`, the REST/HTTP API).\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace has generators that support this configuration but vended files\n * that don't. Diverged files are left untouched and reported via `nextSteps`.\n */\n\nconst CDK_STATIC_WEBSITE_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/static-website.ts`;\nconst CDK_STATIC_WEBSITES_APP_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/app/static-websites`;\nconst TERRAFORM_STATIC_WEBSITE_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/static-website/static-website.tf`;\nconst TERRAFORM_STATIC_WEBSITES_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/static-websites`;\n\nconst CDK_DIVERGED_MESSAGE = `${CDK_STATIC_WEBSITE_FILE}: has diverged from the generated shape - left untouched. To pick up the enableWaf, encryption, encryptionKey and enableKeyRotation props, manually port them from the vended core/static-website.ts template (see the ts#react-website generator's static-website construct).`;\n\nconst TERRAFORM_DIVERGED_MESSAGE = `${TERRAFORM_STATIC_WEBSITE_FILE}: has diverged from the generated shape - left untouched. To pick up the enable_waf, encryption, kms_key_arn, create_kms_key and enable_key_rotation variables, manually port them from the vended static-website.tf template (see the ts#react-website generator's static-website module).`;\n\nconst terraformAppDivergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To make custom_domain_names, acm_certificate_arn, enable_waf, encryption, kms_key_arn, create_kms_key and enable_key_rotation configurable from your root Terraform configuration, add pass-through variables here and forward them to the static_website module call (see the ts#react-website generator's static-websites app template).`;\n\n// The doc comments below carry backticked markdown, which must stay out of\n// the GritQL pattern that inserts this text (see insertViaGritQL). It's routed\n// in as plain text via the placeholder instead.\nconst STATIC_WEBSITE_PROPS_TEXT = `/**\n * Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\n *\n * @default true\n */\n readonly enableWaf?: boolean;\n /**\n * Server-side encryption for the website and distribution log buckets.\n *\n * @default BucketEncryption.KMS\n */\n readonly encryption?: BucketEncryption;\n /**\n * KMS key used to encrypt the website and distribution log buckets. Only used when \\`encryption\\` is\n * \\`BucketEncryption.KMS\\`. When not provided, a new key is created. Note that a key imported via\n * \\`Key.fromKeyArn\\` must already grant the CloudWatch Logs, S3 and CloudFront service principals the\n * necessary permissions in its own key policy - \\`addToResourcePolicy\\` is a no-op on an imported key,\n * so this construct cannot grant them on your behalf.\n */\n readonly encryptionKey?: IKey;\n /**\n * Whether the automatically created KMS key has rotation enabled. Only applies when \\`encryption\\` is\n * \\`BucketEncryption.KMS\\` and no \\`encryptionKey\\` is supplied.\n *\n * @default true\n */\n readonly enableKeyRotation?: boolean`;\n\n/**\n * Surface enableWaf/encryption/encryptionKey/enableKeyRotation on the vended\n * StaticWebsite CDK construct.\n */\nconst migrateCdkConstruct = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_STATIC_WEBSITE_FILE)) {\n return; // This workspace has no CDK static website construct.\n }\n\n if (\n await matchGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n '`readonly enableWaf?: boolean`',\n )\n ) {\n return; // Already migrated.\n }\n\n const CERTIFICATE_FIELD = '`readonly certificate?: ICertificate`';\n const DESTRUCTURE_OLD =\n '`{ websiteFilePath, websiteName, domainNames, certificate }: StaticWebsiteProps`';\n const KEY_CREATION_OLD =\n \"`const websiteKey = new Key(this, 'WebsiteKey', { enableKeyRotation: true })`\";\n const ADD_TO_RESOURCE_POLICY_OLD = '`websiteKey.addToResourcePolicy($args)`';\n const WEBSITE_BUCKET_ENCRYPTION_OLD =\n \"`encryption: BucketEncryption.KMS` as $prop where { $prop <: within `new Bucket($_, 'WebsiteBucket', $_)` }\";\n const DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD =\n \"`encryption: BucketEncryption.KMS` as $prop where { $prop <: within `new Bucket($_, 'DistributionLogBucket', $_)` }\";\n const WAF_STACK_OLD = \"`const wafStack = new CloudfrontWebAcl(this, 'waf')`\";\n const WEB_ACL_ID_OLD = '`webAclId: wafStack.wafArn`';\n\n const anchors = [\n CERTIFICATE_FIELD,\n DESTRUCTURE_OLD,\n KEY_CREATION_OLD,\n ADD_TO_RESOURCE_POLICY_OLD,\n WEBSITE_BUCKET_ENCRYPTION_OLD,\n DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD,\n WAF_STACK_OLD,\n WEB_ACL_ID_OLD,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, CDK_STATIC_WEBSITE_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(CDK_DIVERGED_MESSAGE);\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n ['IKey'],\n 'aws-cdk-lib/aws-kms',\n );\n\n await insertViaGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${CERTIFICATE_FIELD} as $field => \\`$field;\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n STATIC_WEBSITE_PROPS_TEXT,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${DESTRUCTURE_OLD} => \\`{\n websiteFilePath,\n websiteName,\n domainNames,\n certificate,\n enableWaf = true,\n encryption = BucketEncryption.KMS,\n encryptionKey,\n enableKeyRotation = true,\n }: StaticWebsiteProps\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${KEY_CREATION_OLD} => \\`const websiteKey: IKey | undefined =\n encryption === BucketEncryption.KMS\n ? (encryptionKey ?? new Key(this, 'WebsiteKey', { enableKeyRotation }))\n : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${ADD_TO_RESOURCE_POLICY_OLD} => \\`websiteKey?.addToResourcePolicy($args)\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WEBSITE_BUCKET_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WAF_STACK_OLD} => \\`const wafStack = enableWaf ? new CloudfrontWebAcl(this, 'waf') : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WEB_ACL_ID_OLD} => \\`webAclId: wafStack?.wafArn\\``,\n );\n};\n\n/**\n * Surface the same props on each vended per-website app construct, so they\n * can be configured per app rather than only by editing the shared construct.\n */\nconst migrateCdkAppConstructs = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_STATIC_WEBSITES_APP_DIR)) {\n return;\n }\n\n for (const fileName of tree.children(CDK_STATIC_WEBSITES_APP_DIR)) {\n if (fileName === 'index.ts' || !fileName.endsWith('.ts')) {\n continue;\n }\n const filePath = joinPathFragments(CDK_STATIC_WEBSITES_APP_DIR, fileName);\n\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (contents.includes('StaticWebsiteProps')) {\n continue; // Already migrated.\n }\n\n const name = await captureGritQLVariable(\n tree,\n filePath,\n '`export class $name extends StaticWebsite { $_ }`',\n 'name',\n );\n if (!name) {\n continue; // Not a StaticWebsite subclass, not ours to touch.\n }\n\n const importPattern = '`import { StaticWebsite } from $path`';\n const ctorPattern = '`constructor(scope: Construct, id: string) { $body }`';\n // Captures the whole property list rather than anchoring on websiteName\n // being followed by exactly one more property ($rest binds a single\n // trailing node, not a variadic list). This must still match when a\n // workspace already customised the super() call with e.g. domainNames\n // and certificate (the pre-existing documented customization point).\n const superPattern = `\\`super(scope, id, { $props })\\` as $call where {\n $props <: contains \\`websiteName: '${name}'\\`\n}`;\n\n const ready = (\n await Promise.all(\n [importPattern, ctorPattern, superPattern].map((pattern) =>\n matchGritQL(tree, filePath, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: has diverged from the generated shape - left untouched. To make enableWaf, encryption, encryptionKey and enableKeyRotation configurable here, add an optional \\`props?: ${name}Props\\` constructor parameter (\\`Omit<StaticWebsiteProps, 'websiteName' | 'websiteFilePath'>\\`) and spread it into the super() call (see the ts#react-website generator's static-websites app template).`,\n );\n continue;\n }\n\n await applyGritQL(\n tree,\n filePath,\n `${importPattern} => \\`import { StaticWebsite, StaticWebsiteProps } from $path\\``,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n `\\`export class ${name} extends StaticWebsite { $body }\\` as $cls => \\`${GRIT_INSERT_PLACEHOLDER}\n\n$cls\\``,\n `export type ${name}Props = Omit<\\n StaticWebsiteProps,\\n 'websiteName' | 'websiteFilePath'\\n>;`,\n );\n\n await applyGritQL(\n tree,\n filePath,\n `${ctorPattern} => \\`constructor(scope: Construct, id: string, props?: ${name}Props) { $body }\\``,\n );\n\n await applyGritQL(\n tree,\n filePath,\n `${superPattern} => \\`super(scope, id, { ...props, $props })\\``,\n );\n }\n};\n\n// Terraform (HCL) patterns are built from plain strings rather than JS\n// template literals, since the HCL text itself is full of `${...}`\n// interpolations that would otherwise be parsed as JS interpolation.\nconst hcl = (pattern: string) => 'language hcl\\n' + pattern;\n\nconst withinResource = (\n linePattern: string,\n resourceType: string,\n resourceLabel: string,\n) =>\n hcl(\n '`' +\n linePattern +\n '` as $line where {\\n' +\n ' $line <: within `resource \"' +\n resourceType +\n '\" \"' +\n resourceLabel +\n '\" { $_ }`\\n' +\n '}',\n );\n\nconst NEW_VARIABLES_TEXT = [\n 'variable \"enable_waf\" {',\n ' description = \"Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED.\"',\n ' type = string',\n ' default = \"KMS\"',\n '',\n ' validation {',\n ' condition = contains([\"KMS\", \"S3_MANAGED\"], var.encryption)',\n ' error_message = \"encryption must be one of KMS or S3_MANAGED.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\nconst NEW_LOCALS_TEXT = [\n '',\n ' create_website_key = var.encryption == \"KMS\" && var.create_kms_key',\n ' website_kms_key_arn = (',\n ' var.encryption != \"KMS\" ? null :',\n ' local.create_website_key ? aws_kms_key.website_key[0].arn :',\n ' var.kms_key_arn',\n ' )',\n].join('\\n');\n\n/**\n * Surface the same configuration on the vended Terraform static-website\n * module.\n */\nconst migrateTerraformModule = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_STATIC_WEBSITE_FILE)) {\n return; // This workspace has no Terraform static website module.\n }\n\n if (\n await matchGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl('`variable \"enable_waf\" { $_ }`'),\n )\n ) {\n return; // Already migrated.\n }\n\n const ACM_CERT_VARIABLE = hcl('`variable \"acm_certificate_arn\" { $_ }`');\n const ACCESS_LOGS_LOCAL = hcl(\n '`access_logs_name_prefix = substr(lower(var.website_name), 0, 16)`',\n );\n const KMS_KEY_BLOCK = hcl('`resource \"aws_kms_key\" \"website_key\" { $_ }`');\n const KMS_KEY_ROTATION_LINE = withinResource(\n 'enable_key_rotation = true',\n 'aws_kms_key',\n 'website_key',\n );\n const KMS_ALIAS_BLOCK = hcl(\n '`resource \"aws_kms_alias\" \"website_key_alias\" { $_ }`',\n );\n const KMS_ALIAS_TARGET_LINE = hcl(\n '`target_key_id = aws_kms_key.website_key.key_id`',\n );\n const LOG_GROUP_KMS_LINE = hcl('`kms_key_id = aws_kms_key.website_key.arn`');\n const WEBSITE_ENCRYPTION_KEY_LINE = withinResource(\n 'kms_master_key_id = aws_kms_key.website_key.arn',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'website_encryption',\n );\n const WEBSITE_ENCRYPTION_ALGO_LINE = withinResource(\n 'sse_algorithm = \"aws:kms\"',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'website_encryption',\n );\n const DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE = withinResource(\n 'kms_master_key_id = aws_kms_key.website_key.arn',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'distribution_logs_encryption',\n );\n const DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE = withinResource(\n 'sse_algorithm = \"aws:kms\"',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'distribution_logs_encryption',\n );\n const WAF_BLOCK = hcl(\n '`resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" { $_ }`',\n );\n const WEB_ACL_ID_LINE = hcl(\n '`web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn`',\n );\n const WAF_OUTPUT_VALUE_LINE = hcl(\n '`value = aws_wafv2_web_acl.cloudfront_waf.arn` as $line where {\\n' +\n ' $line <: within `output \"waf_web_acl_arn\" { $_ }`\\n' +\n '}',\n );\n // Forces distribution replacement whenever the WAF ACL changes. Once the\n // ACL gains `count`, this can't be resolved on a greenfield apply where\n // count is 0 and there's no prior state to reference - see\n // https://github.com/awslabs/nx-plugin-for-aws/pull/1107. It's also\n // redundant: web_acl_id already references the ACL ARN directly, so a\n // replaced ACL still drives a plain distribution update.\n const LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK = hcl(\n '`lifecycle {\\n replace_triggered_by = [\\n aws_wafv2_web_acl.cloudfront_waf\\n ]\\n }`',\n );\n\n const anchors = [\n ACM_CERT_VARIABLE,\n ACCESS_LOGS_LOCAL,\n KMS_KEY_BLOCK,\n KMS_KEY_ROTATION_LINE,\n KMS_ALIAS_BLOCK,\n KMS_ALIAS_TARGET_LINE,\n LOG_GROUP_KMS_LINE,\n WEBSITE_ENCRYPTION_KEY_LINE,\n WEBSITE_ENCRYPTION_ALGO_LINE,\n DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE,\n DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE,\n WAF_BLOCK,\n WEB_ACL_ID_LINE,\n WAF_OUTPUT_VALUE_LINE,\n LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, TERRAFORM_STATIC_WEBSITE_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(TERRAFORM_DIVERGED_MESSAGE);\n return;\n }\n\n // 1. New variables, after acm_certificate_arn. Routed through the\n // placeholder since the variable descriptions contain double-quoted\n // HCL string values.\n await insertViaGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`variable \"acm_certificate_arn\" { $body }` as $var => `$var\\n\\n' +\n GRIT_INSERT_PLACEHOLDER +\n '`',\n ),\n NEW_VARIABLES_TEXT,\n );\n\n // 2. New locals, after access_logs_name_prefix.\n await insertViaGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`access_logs_name_prefix = substr(lower(var.website_name), 0, 16)` as $line => `$line\\n' +\n GRIT_INSERT_PLACEHOLDER +\n '`',\n ),\n NEW_LOCALS_TEXT,\n );\n\n // 3. Make the KMS key and alias conditional on encryption/kms_key_arn.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_kms_key\" \"website_key\" { $body }` => `resource \"aws_kms_key\" \"website_key\" {\\n' +\n ' count = local.create_website_key ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n KMS_KEY_ROTATION_LINE +\n ' => `enable_key_rotation = var.enable_key_rotation`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_kms_alias\" \"website_key_alias\" { $body }` => `resource \"aws_kms_alias\" \"website_key_alias\" {\\n' +\n ' count = local.create_website_key ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n KMS_ALIAS_TARGET_LINE +\n ' => `target_key_id = aws_kms_key.website_key[0].key_id`',\n );\n\n // 4. Resolve the key ARN through the new local everywhere it's consumed.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n LOG_GROUP_KMS_LINE + ' => `kms_key_id = local.website_kms_key_arn`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEBSITE_ENCRYPTION_KEY_LINE +\n ' => `kms_master_key_id = var.encryption == \"KMS\" ? local.website_kms_key_arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEBSITE_ENCRYPTION_ALGO_LINE +\n ' => `sse_algorithm = var.encryption == \"KMS\" ? \"aws:kms\" : \"AES256\"`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE +\n ' => `kms_master_key_id = var.encryption == \"KMS\" ? local.website_kms_key_arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE +\n ' => `sse_algorithm = var.encryption == \"KMS\" ? \"aws:kms\" : \"AES256\"`',\n );\n\n // 5. Make the WAF Web ACL conditional on enable_waf.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" { $body }` => `resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" {\\n' +\n ' count = var.enable_waf ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEB_ACL_ID_LINE +\n ' => `web_acl_id = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WAF_OUTPUT_VALUE_LINE +\n ' => `value = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null`',\n );\n\n // 6. Drop the lifecycle block that can no longer resolve once the WAF ACL\n // has a count of 0 and no prior state to reference.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK + ' => ``',\n );\n};\n\nconst NEW_APP_MODULE_VARIABLES_TEXT = [\n 'variable \"custom_domain_names\" {',\n ' description = \"Custom domain names (aliases) for the CloudFront distribution. Requires acm_certificate_arn.\"',\n ' type = list(string)',\n ' default = []',\n '}',\n '',\n 'variable \"acm_certificate_arn\" {',\n ' description = \"ARN of an ACM certificate (in us-east-1) for the custom domain names. When set, viewers are required to use TLS 1.2 or later.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"enable_waf\" {',\n ' description = \"Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED.\"',\n ' type = string',\n ' default = \"KMS\"',\n '',\n ' validation {',\n ' condition = contains([\"KMS\", \"S3_MANAGED\"], var.encryption)',\n ' error_message = \"encryption must be one of KMS or S3_MANAGED.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\nconst NEW_APP_MODULE_FORWARD_TEXT = [\n 'custom_domain_names = var.custom_domain_names',\n 'acm_certificate_arn = var.acm_certificate_arn',\n 'enable_waf = var.enable_waf',\n 'encryption = var.encryption',\n 'kms_key_arn = var.kms_key_arn',\n 'create_kms_key = var.create_kms_key',\n 'enable_key_rotation = var.enable_key_rotation',\n].join('\\n ');\n\n/**\n * Surface the same configuration as pass-through variables on each vended\n * per-website Terraform app module, matching the pass-through convention\n * every other app-wraps-core Terraform module already follows.\n */\nconst migrateTerraformAppModules = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_STATIC_WEBSITES_APP_DIR)) {\n return;\n }\n\n for (const dirName of tree.children(TERRAFORM_STATIC_WEBSITES_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_STATIC_WEBSITES_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a website app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"enable_waf\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const TERRAFORM_BLOCK = hcl('`terraform { $_ }`');\n const MODULE_BLOCK = hcl('`module \"static_website\" { $_ }`');\n const FILE_PATH_LINE = hcl(\n '`website_file_path = $val` as $line where {\\n' +\n ' $line <: within `module \"static_website\" { $_ }`\\n' +\n '}',\n );\n // custom_domain_names/acm_certificate_arn predate this migration as a\n // documented customization point (hand-editing the module block). If\n // either is already present as a literal argument, blindly forwarding\n // our own `= var.x` line would produce a duplicate argument, which is\n // invalid HCL. Treat that as diverged instead of silently corrupting the file.\n const hasExistingCustomDomainArg = async (name: string) =>\n matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`${name} = $_\\` as $line where {\\n` +\n ' $line <: within `module \"static_website\" { $_ }`\\n' +\n '}',\n ),\n );\n\n const ready =\n (\n await Promise.all(\n [TERRAFORM_BLOCK, MODULE_BLOCK, FILE_PATH_LINE].map((pattern) =>\n matchGritQL(tree, filePath, pattern),\n ),\n )\n ).every(Boolean) &&\n !(await hasExistingCustomDomainArg('custom_domain_names')) &&\n !(await hasExistingCustomDomainArg('acm_certificate_arn'));\n\n if (!ready) {\n nextSteps.push(terraformAppDivergedMessage(filePath));\n continue;\n }\n\n // Inserted right after the terraform/required_providers block (and\n // before the \"Static website module\" comment), matching where the\n // vended template puts these variables.\n await insertViaGritQL(\n tree,\n filePath,\n hcl('`terraform { $body }` as $tf') +\n ` => \\`$tf\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n NEW_APP_MODULE_VARIABLES_TEXT,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n FILE_PATH_LINE + ` => \\`$line\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n NEW_APP_MODULE_FORWARD_TEXT,\n );\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await migrateCdkConstruct(tree, nextSteps);\n await migrateCdkAppConstructs(tree, nextSteps);\n await migrateTerraformModule(tree, nextSteps);\n await migrateTerraformAppModules(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","captureGritQLVariable","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_STATIC_WEBSITE_FILE","CDK_STATIC_WEBSITES_APP_DIR","TERRAFORM_STATIC_WEBSITE_FILE","TERRAFORM_STATIC_WEBSITES_APP_DIR","CDK_DIVERGED_MESSAGE","TERRAFORM_DIVERGED_MESSAGE","terraformAppDivergedMessage","filePath","STATIC_WEBSITE_PROPS_TEXT","migrateCdkConstruct","tree","nextSteps","exists","CERTIFICATE_FIELD","DESTRUCTURE_OLD","KEY_CREATION_OLD","ADD_TO_RESOURCE_POLICY_OLD","WEBSITE_BUCKET_ENCRYPTION_OLD","DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD","WAF_STACK_OLD","WEB_ACL_ID_OLD","anchors","allPresent","Promise","all","map","pattern","every","Boolean","push","migrateCdkAppConstructs","fileName","children","endsWith","contents","read","includes","name","importPattern","ctorPattern","superPattern","ready","hcl","withinResource","linePattern","resourceType","resourceLabel","NEW_VARIABLES_TEXT","join","NEW_LOCALS_TEXT","migrateTerraformModule","ACM_CERT_VARIABLE","ACCESS_LOGS_LOCAL","KMS_KEY_BLOCK","KMS_KEY_ROTATION_LINE","KMS_ALIAS_BLOCK","KMS_ALIAS_TARGET_LINE","LOG_GROUP_KMS_LINE","WEBSITE_ENCRYPTION_KEY_LINE","WEBSITE_ENCRYPTION_ALGO_LINE","DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE","DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE","WAF_BLOCK","WEB_ACL_ID_LINE","WAF_OUTPUT_VALUE_LINE","LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK","NEW_APP_MODULE_VARIABLES_TEXT","NEW_APP_MODULE_FORWARD_TEXT","migrateTerraformAppModules","dirName","TERRAFORM_BLOCK","MODULE_BLOCK","FILE_PATH_LINE","hasExistingCustomDomainArg","migration"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-data-resource-destroy-protection/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { MigrationReturnObject, Tree } from '@nx/devkit';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring existing workspaces up to the destroy protection the generators now\n * vend on the data-bearing Terraform resources:\n *\n * - The DynamoDB table and the Aurora cluster gain\n * `lifecycle { prevent_destroy = true }`. This is a second, independent layer\n * on top of the service-side `deletion_protection_enabled` /\n * `deletion_protection` flags, matching the two guards the CDK constructs\n * already have (`deletionProtection` plus `RemovalPolicy.RETAIN`). Without it,\n * clearing the service-side flag destroys the data in the same apply.\n * `prevent_destroy` cannot reference a variable, so it is vended as a literal\n * and removed to tear down.\n * - Their KMS keys pin `deletion_window_in_days = 30`, the provider maximum and\n * the CDK default. Restoring either resource from a point-in-time backup or\n * snapshot requires its key, so the window in which a scheduled key deletion\n * can still be cancelled matters as much as the backup itself.\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace keeps the weaker single-layer protection. Diverged files are left\n * untouched and reported via `nextSteps`.\n */\n\nconst TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;\nconst TERRAFORM_AURORA_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/rdb/aurora/aurora.tf`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst divergedMessage = (filePath: string, resource: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To protect the ${resource} from being destroyed when the service-side deletion protection flag is cleared, add \\`lifecycle { prevent_destroy = true }\\` to it, and set \\`deletion_window_in_days = 30\\` on its KMS key.`;\n\n/**\n * Add the maximum KMS pending window to a key that omits it, so a scheduled\n * deletion stays cancellable for as long as the provider allows.\n */\nconst migrateKmsKey = async (\n tree: Tree,\n filePath: string,\n keyLabel: string,\n): Promise<void> => {\n const keyBlock = hcl(`\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\``);\n\n // Already pinned - nothing to do.\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\` where { $props <: contains \\`deletion_window_in_days = $_\\` }`,\n ),\n )\n ) {\n return;\n }\n\n if (!(await matchGritQL(tree, filePath, keyBlock))) {\n return; // Reported by the caller, which matches the data resource too.\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\` where { $props <: contains \\`enable_key_rotation = $rotation\\` => \\`enable_key_rotation = $rotation\\n\\n deletion_window_in_days = 30\\` }`,\n ),\n );\n};\n\n/**\n * Add `lifecycle { prevent_destroy = true }` to a data-bearing resource.\n */\nconst migrateDataResource = async (\n tree: Tree,\n filePath: string,\n resourceType: string,\n resourceLabel: string,\n nextSteps: string[],\n description: string,\n): Promise<boolean> => {\n const resourceBlock = hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\``,\n );\n\n if (!(await matchGritQL(tree, filePath, resourceBlock))) {\n nextSteps.push(divergedMessage(filePath, description));\n return false;\n }\n\n // Already guarded - keep the user's block, whatever it is set to.\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\` where { $props <: contains \\`prevent_destroy = $_\\` }`,\n ),\n )\n ) {\n return true;\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\` => \\`resource \"${resourceType}\" \"${resourceLabel}\" {\\n $props\\n\\n lifecycle {\\n prevent_destroy = true\\n }\\n}\\``,\n ),\n );\n\n return true;\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (tree.exists(TERRAFORM_DYNAMODB_FILE)) {\n if (\n await migrateDataResource(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n 'aws_dynamodb_table',\n 'table',\n nextSteps,\n 'DynamoDB table',\n )\n ) {\n await migrateKmsKey(tree, TERRAFORM_DYNAMODB_FILE, 'table');\n }\n }\n\n if (tree.exists(TERRAFORM_AURORA_FILE)) {\n if (\n await migrateDataResource(\n tree,\n TERRAFORM_AURORA_FILE,\n 'aws_rds_cluster',\n 'database',\n nextSteps,\n 'Aurora cluster',\n )\n ) {\n await migrateKmsKey(tree, TERRAFORM_AURORA_FILE, 'database');\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["applyGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_DYNAMODB_FILE","TERRAFORM_AURORA_FILE","hcl","pattern","divergedMessage","filePath","resource","migrateKmsKey","tree","keyLabel","keyBlock","migrateDataResource","resourceType","resourceLabel","nextSteps","description","resourceBlock","push","migration","exists"],"mappings":"AAAA;;;CAGC,GAED,SAASA,WAAW,EAAEC,WAAW,QAAQ,wBAAwB;AACjE,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;;;;;;;;;;CAoBC,GAED,MAAMC,0BAA0B,GAAGF,aAAa,CAAC,EAAEC,qBAAqB,8BAA8B,CAAC;AACvG,MAAME,wBAAwB,GAAGH,aAAa,CAAC,EAAEC,qBAAqB,8BAA8B,CAAC;AAErG,MAAMG,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,MAAMC,kBAAkB,CAACC,UAAkBC,WACzC,GAAGD,SAAS,yEAAyE,EAAEC,SAAS,6LAA6L,CAAC;AAEhS;;;CAGC,GACD,MAAMC,gBAAgB,OACpBC,MACAH,UACAI;IAEA,MAAMC,WAAWR,IAAI,CAAC,0BAA0B,EAAEO,SAAS,cAAc,CAAC;IAE1E,kCAAkC;IAClC,IACE,MAAMb,YACJY,MACAH,UACAH,IACE,CAAC,0BAA0B,EAAEO,SAAS,4EAA4E,CAAC,IAGvH;QACA;IACF;IAEA,IAAI,CAAE,MAAMb,YAAYY,MAAMH,UAAUK,WAAY;QAClD,QAAQ,+DAA+D;IACzE;IAEA,MAAMf,YACJa,MACAH,UACAH,IACE,CAAC,0BAA0B,EAAEO,SAAS,wJAAwJ,CAAC;AAGrM;AAEA;;CAEC,GACD,MAAME,sBAAsB,OAC1BH,MACAH,UACAO,cACAC,eACAC,WACAC;IAEA,MAAMC,gBAAgBd,IACpB,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,cAAc,CAAC;IAGhE,IAAI,CAAE,MAAMjB,YAAYY,MAAMH,UAAUW,gBAAiB;QACvDF,UAAUG,IAAI,CAACb,gBAAgBC,UAAUU;QACzC,OAAO;IACT;IAEA,kEAAkE;IAClE,IACE,MAAMnB,YACJY,MACAH,UACAH,IACE,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,oEAAoE,CAAC,IAGxH;QACA,OAAO;IACT;IAEA,MAAMlB,YACJa,MACAH,UACAH,IACE,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,8BAA8B,EAAED,aAAa,GAAG,EAAEC,cAAc,oEAAoE,CAAC;IAIxL,OAAO;AACT;AAEA,eAAe,eAAeK,UAC5BV,IAAU;IAEV,MAAMM,YAAsB,EAAE;IAE9B,IAAIN,KAAKW,MAAM,CAACnB,0BAA0B;QACxC,IACE,MAAMW,oBACJH,MACAR,yBACA,sBACA,SACAc,WACA,mBAEF;YACA,MAAMP,cAAcC,MAAMR,yBAAyB;QACrD;IACF;IAEA,IAAIQ,KAAKW,MAAM,CAAClB,wBAAwB;QACtC,IACE,MAAMU,oBACJH,MACAP,uBACA,mBACA,YACAa,WACA,mBAEF;YACA,MAAMP,cAAcC,MAAMP,uBAAuB;QACnD;IACF;IAEA,MAAMJ,qBAAqBW;IAE3B,OAAO;QAAEM;IAAU;AACrB"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-declare-used-providers/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport {\n captureAllGritQL,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\nimport { TERRAFORM_VERSIONS } from '../../../utils/versions.js';\n\n/**\n * Declare every provider each vended Terraform module uses in its own\n * `required_providers`.\n *\n * A provider a module uses but never declares is resolved by implicit\n * inheritance from the root module, unpinned — so `terraform init` on that\n * module takes whatever the registry serves, bypassing the version pinning the\n * generators vend. Several vended modules referenced `random`, `archive`,\n * `null`, `external` and `aws` without declaring them.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the unpinned shape until this runs. Each missing provider is added to the\n * module's existing `required_providers`; a module with no `terraform` block at\n * all is reported via `nextSteps` rather than having one inserted, since where\n * it belongs depends on how the user has arranged the file.\n */\n\nconst TERRAFORM_SRC = joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n);\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\n/** Providers built in to Terraform, which have no entry and no version. */\nconst BUILTIN_TYPES = new Set(['terraform_data', 'terraform_remote_state']);\n\n/**\n * The provider a resource or data source type resolves to. Terraform derives\n * this from the type's first underscore-delimited word; a type it can't\n * attribute to a provider the plugin vends is not this migration's to touch.\n */\nconst providerOf = (type: string): string | undefined => {\n // `null_resource` and `external` are the whole type, not a prefixed one.\n if (type === 'null_resource') {\n return 'null';\n }\n if (type === 'external') {\n return 'external';\n }\n const [prefix] = type.split('_');\n return prefix in TERRAFORM_VERSIONS ? prefix : undefined;\n};\n\n/**\n * The providers a module requires, read from its `resource` and `data` blocks.\n *\n * Only declarations are read, because Terraform resolves a resource reference\n * within the module that declares it — so a provider a module references is one\n * it also declares a block for, and the blocks alone are complete. Matching the\n * syntax tree also means a type named in a comment, in string prose or inside a\n * `local-exec` heredoc's embedded Python is not a use, since none of those parse\n * as a block.\n */\nconst usedProviders = async (\n tree: Tree,\n filePath: string,\n): Promise<Set<string>> => {\n const used = new Set<string>();\n for (const kind of ['resource', 'data']) {\n for (const block of await captureAllGritQL(\n tree,\n filePath,\n hcl(`\\`${kind} $type $name { $_ }\\``),\n )) {\n // The block's own type, e.g. `resource \"random_string\" \"suffix\" {`.\n const type = /\"([A-Za-z0-9_]+)\"/.exec(block)?.[1];\n if (!type || BUILTIN_TYPES.has(type)) {\n continue;\n }\n const provider = providerOf(type);\n if (provider) {\n used.add(provider);\n }\n }\n }\n return used;\n};\n\n/** Every `.tf` file in the workspace's shared terraform library, by directory. */\nconst terraformModules = (tree: Tree): Map<string, string[]> => {\n const modules = new Map<string, string[]>();\n if (!tree.exists(TERRAFORM_SRC)) {\n return modules;\n }\n visitNotIgnoredFiles(tree, TERRAFORM_SRC, (filePath) => {\n if (!filePath.endsWith('.tf')) {\n return;\n }\n const dir = filePath.slice(0, filePath.lastIndexOf('/'));\n modules.set(dir, [...(modules.get(dir) ?? []), filePath]);\n });\n return modules;\n};\n\n/** The provider entry to vend, at the version this release pins. */\nconst providerEntry = (provider: string) =>\n [\n `${provider} = {`,\n ` source = \"hashicorp/${provider}\"`,\n ` version = \"${TERRAFORM_VERSIONS[provider as keyof typeof TERRAFORM_VERSIONS]}\"`,\n '}',\n ].join('\\n');\n\n/**\n * Add a provider to a file's `required_providers`. Appended after the block's\n * existing body, so an entry the user has customised is left as it is.\n */\nconst declareProvider = (tree: Tree, filePath: string, provider: string) =>\n insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`required_providers { $body }\\` => \\`required_providers {\\n $body\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\`` +\n ` where { $body <: not contains \\`${provider} = { $_ }\\` }`,\n ),\n providerEntry(provider),\n );\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [dir, filePaths] of terraformModules(tree)) {\n const used = new Set<string>();\n // The file holding the module's `required_providers`, which is where the\n // missing declarations are added — Terraform merges every `.tf` in a\n // directory, so one block covers the module.\n let blockFile: string | undefined;\n const declared = new Set<string>();\n\n for (const filePath of filePaths) {\n for (const provider of await usedProviders(tree, filePath)) {\n used.add(provider);\n }\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('required_providers')) {\n continue;\n }\n blockFile ??= filePath;\n for (const provider of Object.keys(TERRAFORM_VERSIONS)) {\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`required_providers { $body }\\` where { $body <: contains \\`${provider} = { $_ }\\` }`,\n ),\n )\n ) {\n declared.add(provider);\n }\n }\n }\n\n const missing = [...used].filter((p) => !declared.has(p)).sort();\n if (missing.length === 0) {\n continue;\n }\n\n if (!blockFile) {\n nextSteps.push(\n `${dir}: has no terraform required_providers block - left untouched. Add one declaring ${missing.join(', ')}, so the module pins the providers it uses rather than inheriting them unpinned from the root module.`,\n );\n continue;\n }\n\n for (const provider of missing) {\n if (!(await declareProvider(tree, blockFile, provider))) {\n nextSteps.push(\n `${blockFile}: its required_providers block has diverged from the generated shape - left untouched. Add a ${provider} entry to it, sourced from hashicorp/${provider}.`,\n );\n }\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","visitNotIgnoredFiles","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_VERSIONS","TERRAFORM_SRC","hcl","pattern","BUILTIN_TYPES","Set","providerOf","type","prefix","split","undefined","usedProviders","tree","filePath","used","kind","block","exec","has","provider","add","terraformModules","modules","Map","exists","endsWith","dir","slice","lastIndexOf","set","get","providerEntry","join","declareProvider","migration","nextSteps","filePaths","blockFile","declared","contents","read","includes","Object","keys","missing","filter","p","sort","length","push"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { AGENTCORE_GATEWAY_GENERATOR_INFO } from '../../../agentcore-gateway/generator.js';\nimport { addDependenciesToPackageJson } from '../../../utils/dependencies.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\nimport { TS_VERSIONS } from '../../../utils/versions.js';\n\n/**\n * Declare `ejs` at the workspace root for Terraform gateways with Cedar\n * policies, which is where `render-cedar.cjs` resolves it from: the script runs\n * in the shared terraform project, which has no `package.json` of its own.\n */\n\nconst RENDER_SCRIPT = 'render-cedar.cjs';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const gatewaysDir = joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'app',\n 'gateways',\n );\n let needsRootEjs = false;\n\n for (const project of getProjects(tree).values()) {\n const metadata = project.metadata as\n | { generator?: string; name?: string }\n | undefined;\n if (metadata?.generator !== AGENTCORE_GATEWAY_GENERATOR_INFO.id) {\n continue;\n }\n\n // Only a Terraform gateway with Cedar policies vends the render script; a\n // CDK one resolves ejs from the shared constructs project instead.\n if (\n tree.exists(\n joinPathFragments(gatewaysDir, metadata.name ?? '', RENDER_SCRIPT),\n )\n ) {\n needsRootEjs = true;\n }\n }\n\n if (needsRootEjs) {\n addDependenciesToPackageJson(tree, {}, { ejs: TS_VERSIONS.ejs });\n }\n\n await formatFilesInSubtree(tree);\n\n return {};\n}\n"],"names":["getProjects","joinPathFragments","AGENTCORE_GATEWAY_GENERATOR_INFO","addDependenciesToPackageJson","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TS_VERSIONS","RENDER_SCRIPT","migration","tree","gatewaysDir","needsRootEjs","project","values","metadata","generator","id","exists","name","ejs"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EACXC,iBAAiB,QAGZ,aAAa;AACpB,SAASC,gCAAgC,QAAQ,0CAA0C;AAC3F,SAASC,4BAA4B,QAAQ,iCAAiC;AAC9E,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AACvD,SAASC,WAAW,QAAQ,6BAA6B;AAEzD;;;;CAIC,GAED,MAAMC,gBAAgB;AAEtB,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,cAAcV,kBAClBI,cACAC,sBACA,OACA,OACA;IAEF,IAAIM,eAAe;IAEnB,KAAK,MAAMC,WAAWb,YAAYU,MAAMI,MAAM,GAAI;QAChD,MAAMC,WAAWF,QAAQE,QAAQ;QAGjC,IAAIA,UAAUC,cAAcd,iCAAiCe,EAAE,EAAE;YAC/D;QACF;QAEA,0EAA0E;QAC1E,mEAAmE;QACnE,IACEP,KAAKQ,MAAM,CACTjB,kBAAkBU,aAAaI,SAASI,IAAI,IAAI,IAAIX,iBAEtD;YACAI,eAAe;QACjB;IACF;IAEA,IAAIA,cAAc;QAChBT,6BAA6BO,MAAM,CAAC,GAAG;YAAEU,KAAKb,YAAYa,GAAG;QAAC;IAChE;IAEA,MAAMhB,qBAAqBM;IAE3B,OAAO,CAAC;AACV"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport {\n applyGritQL,\n captureAllGritQL,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring each vended Terraform AgentCore Harness app module\n * (`app/harnesses/<name>/<name>.tf`) up to the shape the generator now writes:\n *\n * - `enable_vpc` / `vpc_id` / `subnet_ids` with a security group and a 443-only\n * egress rule, so the Harness can reach private resources.\n * - `create_execution_role` / `execution_role_arn`, so an existing role can be\n * supplied. The generated role and its baseline policy are then not created.\n * - `allowed_tools`, `memory`, `environment_variables`, `max_iterations` and\n * `timeout_seconds` as variables rather than HCL edits.\n * - The managed-memory grant moved to its own policy, made conditional and\n * re-scoped from a name-prefix guess to the ARN the service assigns.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the older module until this runs. Modules that have diverged from the\n * generated shape are left untouched and reported via `nextSteps`.\n */\n\nconst TERRAFORM_HARNESS_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/harnesses`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst divergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To pick up VPC support, a supplied execution role, the allowed_tools/memory/environment_variables/max_iterations/timeout_seconds variables and the corrected managed-memory grant, compare it against the agentcore-harness generator's Terraform template and apply the parts you want.`;\n\n/**\n * Variables added ahead of the existing `model_resource_arns` variable.\n * `allowed_tools` is not among them: the earlier `agentcore-harness-no-tools-by-default`\n * migration already declares it, and runs first.\n */\nconst NEW_VARIABLES_TEXT = `variable \"memory\" {\n description = \"Memory configuration for the Harness. Null leaves the service to provision managed memory with default strategies. Set exactly one field: managed_memory_configuration to tune that managed memory, agentcore_memory_configuration to use a memory resource you own, or disabled to turn memory off.\"\n type = object({\n managed_memory_configuration = optional(object({\n strategies = optional(set(string))\n event_expiry_duration = optional(number)\n encryption_key_arn = optional(string)\n }))\n agentcore_memory_configuration = optional(object({\n arn = string\n actor_id = optional(string)\n messages_count = optional(number)\n }))\n disabled = optional(bool, false)\n })\n default = null\n\n validation {\n condition = var.memory == null || length([\n for configured in [\n var.memory.managed_memory_configuration != null,\n var.memory.agentcore_memory_configuration != null,\n var.memory.disabled,\n ] : configured if configured\n ]) == 1\n error_message = \"Set exactly one of memory.managed_memory_configuration, memory.agentcore_memory_configuration or memory.disabled.\"\n }\n}\n\nvariable \"environment_variables\" {\n description = \"Environment variables available to the Harness.\"\n type = map(string)\n default = null\n sensitive = true\n}\n\nvariable \"max_iterations\" {\n description = \"Maximum agent loop iterations per invocation. Uses the service default when null.\"\n type = number\n default = null\n}\n\nvariable \"timeout_seconds\" {\n description = \"Maximum seconds an invocation may run. Uses the service default when null.\"\n type = number\n default = null\n}\n\nvariable \"create_execution_role\" {\n description = \"Create the execution role with the baseline permissions below. Set false alongside execution_role_arn to use a role you already have, which is then used as-is.\"\n type = bool\n default = true\n\n validation {\n condition = var.create_execution_role || var.execution_role_arn != null\n error_message = \"execution_role_arn must be set when create_execution_role is false.\"\n }\n\n validation {\n condition = var.create_execution_role || (\n var.model_resource_arns == null && length(var.additional_execution_role_policy_statements) == 0\n )\n error_message = \"model_resource_arns and additional_execution_role_policy_statements configure the generated execution role, which is not created when create_execution_role is false. Grant those permissions on the supplied role instead.\"\n }\n}\n\nvariable \"execution_role_arn\" {\n description = \"ARN of an existing IAM role for the Harness to assume. Requires create_execution_role = false.\"\n type = string\n default = null\n}`;\n\n/** VPC and tag variables added after `additional_execution_role_policy_statements`. */\nconst VPC_VARIABLES_TEXT = `# VPC Configuration (optional)\nvariable \"enable_vpc\" {\n description = \"Run the Harness inside a VPC so it can reach private resources. Set alongside vpc_id/subnet_ids.\"\n type = bool\n default = false\n}\n\nvariable \"vpc_id\" {\n description = \"VPC ID to run the Harness in. Required when enable_vpc is true.\"\n type = string\n default = null\n\n validation {\n condition = !var.enable_vpc || (var.vpc_id != null && var.subnet_ids != null)\n error_message = \"vpc_id and subnet_ids must be set when enable_vpc is true.\"\n }\n}\n\nvariable \"subnet_ids\" {\n description = \"Subnet IDs to run the Harness in. Required when enable_vpc is true.\"\n type = list(string)\n default = null\n}\n\nvariable \"tags\" {\n description = \"Tags to apply to resources\"\n type = map(string)\n default = {}\n}`;\n\n/** Locals added to the module's existing `locals` block. */\nconst NEW_LOCALS_TEXT = ` execution_role_arn = var.create_execution_role ? aws_iam_role.execution_role[0].arn : var.execution_role_arn\n\n # The service provisions managed memory unless var.memory redirects it\n # elsewhere or turns it off.\n has_managed_memory = var.memory == null || var.memory.managed_memory_configuration != null`;\n\n/** Security group and egress rule, added before the Harness resource. */\nconst SECURITY_GROUP_TEXT = `# Security group for the Harness, used when running inside a VPC\nresource \"aws_security_group\" \"harness\" {\n count = var.enable_vpc ? 1 : 0\n\n #checkov:skip=CKV2_AWS_5:Attached to the Harness via its network configuration; Checkov cannot resolve this reference\n name_prefix = \"\\${local.harness_name_prefix}-harness-\"\n description = \"Security group for the \\${local.harness_name_prefix} Harness\"\n vpc_id = var.vpc_id\n tags = var.tags\n}\n\nresource \"aws_vpc_security_group_egress_rule\" \"harness_https\" {\n count = var.enable_vpc ? 1 : 0\n\n security_group_id = aws_security_group.harness[0].id\n cidr_ipv4 = \"0.0.0.0/0\"\n from_port = 443\n to_port = 443\n ip_protocol = \"tcp\"\n description = \"Allow outbound HTTPS to AWS service endpoints\"\n}`;\n\n/**\n * Native fields added to the Harness resource. `allowed_tools` is re-emitted\n * here, aligned with the rest, replacing the line the earlier\n * no-tools-by-default migration added.\n */\nconst HARNESS_FIELDS_TEXT = ` allowed_tools = var.allowed_tools\n environment_variables = var.environment_variables\n max_iterations = var.max_iterations\n timeout_seconds = var.timeout_seconds`;\n\nconst HARNESS_BLOCKS_TEXT = ` dynamic \"memory\" {\n for_each = var.memory != null ? [var.memory] : []\n content {\n dynamic \"managed_memory_configuration\" {\n for_each = memory.value.managed_memory_configuration != null ? [memory.value.managed_memory_configuration] : []\n content {\n strategies = managed_memory_configuration.value.strategies\n event_expiry_duration = managed_memory_configuration.value.event_expiry_duration\n encryption_key_arn = managed_memory_configuration.value.encryption_key_arn\n }\n }\n\n dynamic \"agentcore_memory_configuration\" {\n for_each = memory.value.agentcore_memory_configuration != null ? [memory.value.agentcore_memory_configuration] : []\n content {\n arn = agentcore_memory_configuration.value.arn\n actor_id = agentcore_memory_configuration.value.actor_id\n messages_count = agentcore_memory_configuration.value.messages_count\n }\n }\n\n dynamic \"disabled\" {\n for_each = memory.value.disabled ? [1] : []\n content {}\n }\n }\n }\n\n dynamic \"environment\" {\n for_each = var.enable_vpc ? [1] : []\n content {\n agentcore_runtime_environment {\n network_configuration {\n network_mode = \"VPC\"\n network_mode_config {\n security_groups = [aws_security_group.harness[0].id]\n subnets = var.subnet_ids\n }\n }\n }\n }\n }\n\n tags = var.tags`;\n\n/** The managed-memory grant, as its own policy scoped to the assigned ARN. */\nconst MANAGED_MEMORY_POLICY_TEXT = `# Scoped to the memory ARN the service assigns, which is only readable after the\n# Harness exists, so this is a policy of its own rather than a baseline\n# statement. Skipped when the Harness uses a role or memory the module did not\n# create.\nresource \"aws_iam_role_policy\" \"managed_memory\" {\n count = var.create_execution_role && local.has_managed_memory ? 1 : 0\n\n name = \"\\${local.harness_name_prefix}-HarnessManagedMemoryPolicy\"\n role = aws_iam_role.execution_role[0].id\n\n policy = jsonencode({\n Version = \"2012-10-17\"\n Statement = [{\n Sid = \"AgentCoreManagedMemory\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:CreateEvent\",\n \"bedrock-agentcore:DeleteEvent\",\n \"bedrock-agentcore:GetEvent\",\n \"bedrock-agentcore:ListEvents\",\n \"bedrock-agentcore:RetrieveMemoryRecords\",\n ]\n Resource = [\n aws_bedrockagentcore_harness.this.memory_actual[0].managed_memory_configuration[0].arn,\n ]\n }]\n })\n}`;\n\nconst SECURITY_GROUP_OUTPUT_TEXT = `output \"security_group_id\" {\n description = \"Security group ID of the Harness, for use in ingress rules on resources it must reach (e.g. a database). Null unless enable_vpc is true.\"\n value = var.enable_vpc ? aws_security_group.harness[0].id : null\n}`;\n\n/**\n * The baseline policy, replaced whole: the managed-memory statement leaves it\n * for a policy of its own, and the resource gains a `count`. Rewriting the\n * statement list in place would strand the comment and comma around the\n * statement being removed.\n */\nconst BASELINE_POLICY_TEXT = `resource \"aws_iam_role_policy\" \"execution_role\" {\n count = var.create_execution_role ? 1 : 0\n\n name = \"\\${local.harness_name_prefix}-HarnessPolicy\"\n role = aws_iam_role.execution_role[0].id\n\n policy = jsonencode({\n Version = \"2012-10-17\"\n Statement = concat([\n {\n Sid = \"BedrockModelInvocation\"\n Effect = \"Allow\"\n Action = [\n \"bedrock:InvokeModel\",\n \"bedrock:InvokeModelWithResponseStream\",\n ]\n Resource = local.model_resource_arns\n },\n {\n #checkov:skip=CKV_AWS_355:EcrPublicTokenAccess requires a wildcard resource; ecr-public:GetAuthorizationToken has no resource-level permission\n Sid = \"EcrPublicTokenAccess\"\n Effect = \"Allow\"\n Action = [\"ecr-public:GetAuthorizationToken\"]\n Resource = [\"*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:StsForEcrPublicPull requires a wildcard resource; sts:GetServiceBearerToken has no resource-level permission\n Sid = \"StsForEcrPublicPull\"\n Effect = \"Allow\"\n Action = [\"sts:GetServiceBearerToken\"]\n Resource = [\"*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission\n #checkov:skip=CKV_AWS_290:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission\n Sid = \"XRayTracingAccess\"\n Effect = \"Allow\"\n Action = [\n \"xray:PutTraceSegments\",\n \"xray:PutTelemetryRecords\",\n \"xray:GetSamplingRules\",\n \"xray:GetSamplingTargets\",\n ]\n Resource = [\"*\"]\n },\n {\n Sid = \"CloudWatchLogsGroup\"\n Effect = \"Allow\"\n Action = [\n \"logs:CreateLogGroup\",\n \"logs:DescribeLogStreams\",\n ]\n Resource = [\n \"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*\",\n ]\n },\n {\n Sid = \"CloudWatchLogsDescribeGroups\"\n Effect = \"Allow\"\n Action = [\"logs:DescribeLogGroups\"]\n Resource = [\"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:*\"]\n },\n {\n Sid = \"CloudWatchLogsStream\"\n Effect = \"Allow\"\n Action = [\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n ]\n Resource = [\n \"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*\",\n ]\n },\n {\n #checkov:skip=CKV_AWS_355:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead\n #checkov:skip=CKV_AWS_290:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead\n Sid = \"CloudWatchMetricsPublish\"\n Effect = \"Allow\"\n Action = [\"cloudwatch:PutMetricData\"]\n Resource = [\"*\"]\n Condition = {\n StringEquals = {\n \"cloudwatch:namespace\" = \"bedrock-agentcore\"\n }\n }\n },\n {\n Sid = \"AgentCoreWorkloadIdentity\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:GetWorkloadAccessToken\",\n \"bedrock-agentcore:GetWorkloadAccessTokenForJWT\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:\\${local.account_id}:workload-identity-directory/default\",\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:\\${local.account_id}:workload-identity-directory/default/workload-identity/harness_\\${local.harness_name}-*\",\n ]\n },\n {\n Sid = \"AgentCoreBrowserDefault\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:StartBrowserSession\",\n \"bedrock-agentcore:StopBrowserSession\",\n \"bedrock-agentcore:GetBrowserSession\",\n \"bedrock-agentcore:ListBrowserSessions\",\n \"bedrock-agentcore:UpdateBrowserStream\",\n \"bedrock-agentcore:ConnectBrowserAutomationStream\",\n \"bedrock-agentcore:ConnectBrowserLiveViewStream\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:aws:browser/*\",\n ]\n },\n {\n Sid = \"AgentCoreCodeInterpreterDefault\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:StartCodeInterpreterSession\",\n \"bedrock-agentcore:StopCodeInterpreterSession\",\n \"bedrock-agentcore:GetCodeInterpreterSession\",\n \"bedrock-agentcore:ListCodeInterpreterSessions\",\n \"bedrock-agentcore:InvokeCodeInterpreter\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:aws:code-interpreter/*\",\n ]\n },\n ],\n # In VPC mode the Harness pulls its managed container from a private ECR\n # repository in the region rather than ECR Public, so it needs pull\n # permissions on it. The repository is service-owned, hence the wildcard\n # account.\n var.enable_vpc ? [\n {\n Sid = \"EcrManagedImagePull\"\n Effect = \"Allow\"\n Action = [\n \"ecr:BatchGetImage\",\n \"ecr:GetDownloadUrlForLayer\",\n \"ecr:BatchCheckLayerAvailability\",\n ]\n Resource = [\"arn:\\${local.partition}:ecr:\\${local.region}:*:repository/harness-*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:EcrManagedImageToken requires a wildcard resource; ecr:GetAuthorizationToken has no resource-level permission\n Sid = \"EcrManagedImageToken\"\n Effect = \"Allow\"\n Action = [\"ecr:GetAuthorizationToken\"]\n Resource = [\"*\"]\n },\n ] : [],\n [\n # Null Sid/Condition fields are dropped from the rendered policy.\n for statement in var.additional_execution_role_policy_statements :\n { for key, value in statement : key => value if value != null }\n ])\n })\n}`;\n\n/**\n * Every shape the migration rewrites, so a module missing any of them is\n * reported rather than half-migrated.\n */\nconst REQUIRED_SHAPES = [\n '`variable \"model_resource_arns\" { $_ }`',\n '`variable \"additional_execution_role_policy_statements\" { $_ }`',\n '`locals { $_ }`',\n '`resource \"aws_iam_role\" \"execution_role\" { $_ }`',\n '`resource \"aws_iam_role_policy\" \"execution_role\" { $_ }`',\n '`resource \"aws_bedrockagentcore_harness\" \"this\" { $_ }`',\n '`output \"execution_role_arn\" { $_ }`',\n // The no-tools-by-default migration runs first, so its assignment is present\n // and is what the native fields below are anchored on.\n '`allowed_tools = var.allowed_tools`',\n];\n\n/**\n * The baseline policy is replaced whole, so it is only safe to touch when it\n * still carries exactly the statements the generator wrote. Any addition,\n * removal or reordering means the user has edited it, and the replacement would\n * discard that.\n */\nconst BASELINE_SIDS = [\n 'BedrockModelInvocation',\n 'EcrPublicTokenAccess',\n 'StsForEcrPublicPull',\n 'XRayTracingAccess',\n 'CloudWatchLogsGroup',\n 'CloudWatchLogsDescribeGroups',\n 'CloudWatchLogsStream',\n 'CloudWatchMetricsPublish',\n 'AgentCoreWorkloadIdentity',\n 'AgentCoreManagedMemory',\n 'AgentCoreBrowserDefault',\n 'AgentCoreCodeInterpreterDefault',\n];\n\n/**\n * Whether the module's baseline policy still carries exactly the generated\n * statements, in order. Sids are read from the whole file: the only other `Sid`\n * is the `optional(string)` field of the additional-statements variable, which\n * carries no string literal and so is filtered out.\n */\nconst hasUneditedBaselinePolicy = async (\n tree: Tree,\n filePath: string,\n): Promise<boolean> => {\n const sids = (await captureAllGritQL(tree, filePath, hcl('`Sid = $sid`')))\n .map((text) => /=\\s*\"([^\"]+)\"/.exec(text)?.[1])\n .filter((sid): sid is string => sid !== undefined);\n return (\n sids.length === BASELINE_SIDS.length &&\n sids.every((sid, index) => sid === BASELINE_SIDS[index])\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(TERRAFORM_HARNESS_APP_DIR)) {\n return { nextSteps }; // This workspace has no Terraform harness modules.\n }\n\n for (const dirName of tree.children(TERRAFORM_HARNESS_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_HARNESS_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a harness app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"enable_vpc\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const shapeChecks = await Promise.all(\n REQUIRED_SHAPES.map((shape) => matchGritQL(tree, filePath, hcl(shape))),\n );\n if (\n shapeChecks.includes(false) ||\n !(await hasUneditedBaselinePolicy(tree, filePath))\n ) {\n nextSteps.push(divergedMessage(filePath));\n continue;\n }\n\n // Variables, ahead of the model/role variables they sit beside in the\n // template.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"model_resource_arns\" { $body }\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\nvariable \"model_resource_arns\" {\\n $body\\n}\\``,\n ),\n NEW_VARIABLES_TEXT,\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"additional_execution_role_policy_statements\" { $body }\\` => \\`variable \"additional_execution_role_policy_statements\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n VPC_VARIABLES_TEXT,\n );\n\n // Locals resolving the role ARN and whether managed memory applies.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`locals { $body }\\` => \\`locals {\\n $body\\n\\n${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n ),\n NEW_LOCALS_TEXT,\n );\n\n // The role becomes conditional on none being supplied, so it is indexed\n // wherever it is referenced, and gains the shared tags.\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`resource \"aws_iam_role\" \"execution_role\" { $body }` => `resource \"aws_iam_role\" \"execution_role\" {\\n count = var.create_execution_role ? 1 : 0\\n\\n $body\\n\\n tags = var.tags\\n}`',\n ),\n );\n\n // The baseline policy is replaced whole, dropping the managed-memory\n // statement, which becomes a policy of its own scoped to the assigned ARN.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_iam_role_policy\" \"execution_role\" { $_ }\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n BASELINE_POLICY_TEXT,\n );\n\n // The role ARN follows whichever role is in use, and the `allowed_tools`\n // line the no-tools-by-default migration added is re-aligned with the\n // native fields that now sit beside it.\n await applyGritQL(\n tree,\n filePath,\n hcl('`allowed_tools = var.allowed_tools` => ``'),\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`execution_role_arn = aws_iam_role.execution_role.arn\\` => \\`execution_role_arn = local.execution_role_arn\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n HARNESS_FIELDS_TEXT,\n );\n // Ahead of `depends_on`, which the resource ends with.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`depends_on = [aws_iam_role_policy.execution_role]\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\n depends_on = [aws_iam_role_policy.execution_role]\\``,\n ),\n // The placeholder already sits at the block's indentation.\n HARNESS_BLOCKS_TEXT.replace(/^ {2}/, ''),\n );\n\n // Security group ahead of the Harness resource, and the managed-memory\n // policy after it, since it reads an attribute of the deployed Harness.\n // Anchored on the comment introducing the resource so it stays attached.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`# Omitting authorizer_configuration uses IAM inbound authorization; add a\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\n# Omitting authorizer_configuration uses IAM inbound authorization; add a\\``,\n ),\n SECURITY_GROUP_TEXT,\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_bedrockagentcore_harness\" \"this\" { $body }\\` => \\`resource \"aws_bedrockagentcore_harness\" \"this\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n MANAGED_MEMORY_POLICY_TEXT,\n );\n\n // The role ARN output follows whichever role is in use, and the security\n // group is exposed for ingress rules on resources the Harness reaches.\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`output \"execution_role_arn\" { $_ }` => `output \"execution_role_arn\" {\\n description = \"ARN of the IAM role assumed by the Harness, whether generated or supplied via var.execution_role_arn\"\\n value = local.execution_role_arn\\n}`',\n ),\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`output \"execution_role_arn\" { $body }\\` => \\`output \"execution_role_arn\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n SECURITY_GROUP_OUTPUT_TEXT,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","applyGritQL","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_HARNESS_APP_DIR","hcl","pattern","divergedMessage","filePath","NEW_VARIABLES_TEXT","VPC_VARIABLES_TEXT","NEW_LOCALS_TEXT","SECURITY_GROUP_TEXT","HARNESS_FIELDS_TEXT","HARNESS_BLOCKS_TEXT","MANAGED_MEMORY_POLICY_TEXT","SECURITY_GROUP_OUTPUT_TEXT","BASELINE_POLICY_TEXT","REQUIRED_SHAPES","BASELINE_SIDS","hasUneditedBaselinePolicy","tree","sids","map","text","exec","filter","sid","undefined","length","every","index","migration","nextSteps","exists","dirName","children","shapeChecks","Promise","all","shape","includes","push","replace"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-load-runtime-config-src-path/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE } from '../../../utils/shared-constructs-constants.js';\n\n/**\n * The Terraform `load-runtime-config` target copied\n * `dist/packages/common/terraform/runtime-config.json`, which nothing writes:\n * the vended runtime-config modules aggregate per namespace into the\n * `runtime-config` *directory*, so the website's config lands at\n * `runtime-config/connection.json`. Copying the directory path always failed\n * with ENOENT, so point it at the file the aggregation actually writes.\n *\n * Both target names are rewritten — `load-runtime-config` and the\n * `load:runtime-config` it was cloned from, which is left in place for\n * workspaces that still reference it.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n */\n\nconst TARGETS = ['load-runtime-config', 'load:runtime-config'];\n\n/** The path the target used to name, which never existed on disk. */\nconst BROKEN_SRC_FILE = 'dist/packages/common/terraform/runtime-config.json';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n for (const [projectName, project] of getProjects(tree)) {\n let updated = false;\n\n for (const targetName of TARGETS) {\n const env = project.targets?.[targetName]?.options?.env;\n // Only the broken path is rewritten, so a workspace that pointed the\n // target somewhere of its own keeps it.\n if (env?.SRC_FILE !== BROKEN_SRC_FILE) {\n continue;\n }\n env.SRC_FILE = TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE;\n updated = true;\n }\n\n if (updated) {\n updateProjectConfiguration(tree, projectName, project);\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return {};\n}\n"],"names":["getProjects","updateProjectConfiguration","formatFilesInSubtree","TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE","TARGETS","BROKEN_SRC_FILE","migration","tree","projectName","project","updated","targetName","env","targets","options","SRC_FILE"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EAGXC,0BAA0B,QACrB,aAAa;AACpB,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SAASC,qCAAqC,QAAQ,gDAAgD;AAEtG;;;;;;;;;;;;;;;CAeC,GAED,MAAMC,UAAU;IAAC;IAAuB;CAAsB;AAE9D,mEAAmE,GACnE,MAAMC,kBAAkB;AAExB,eAAe,eAAeC,UAC5BC,IAAU;IAEV,KAAK,MAAM,CAACC,aAAaC,QAAQ,IAAIT,YAAYO,MAAO;QACtD,IAAIG,UAAU;QAEd,KAAK,MAAMC,cAAcP,QAAS;YAChC,MAAMQ,MAAMH,QAAQI,OAAO,EAAE,CAACF,WAAW,EAAEG,SAASF;YACpD,qEAAqE;YACrE,wCAAwC;YACxC,IAAIA,KAAKG,aAAaV,iBAAiB;gBACrC;YACF;YACAO,IAAIG,QAAQ,GAAGZ;YACfO,UAAU;QACZ;QAEA,IAAIA,SAAS;YACXT,2BAA2BM,MAAMC,aAAaC;QAChD;IACF;IAEA,MAAMP,qBAAqBK;IAE3B,OAAO,CAAC;AACV"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n generateFiles,\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n OverwriteStrategy,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { TERRAFORM_PROJECT_GENERATOR_INFO } from '../../../terraform/project/generator.js';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { kebabCase } from '../../../utils/names.js';\nimport { normalizeTargetKeyOrder } from '../../../utils/nx.js';\nimport { sortObjectKeys } from '../../../utils/object.js';\n\n/**\n * Terraform projects gained three fixes:\n *\n * - A `checkov` target carries the security scan, matching the name CDK\n * infrastructure projects already use, so `nx run-many --target checkov` no\n * longer skips Terraform projects. An existing `test` target is the user's,\n * so it is left exactly as it is.\n * - A `checkov.yml` gives Terraform users the same central place to configure\n * skips that the CDK app has, wired in via `--config-file`.\n * - `bootstrap-destroy` runs a script that resolves the region from the AWS SDK\n * credential chain and passes it as `-var=aws_region`. The vended\n * `aws_region` variable has no default, so the previous bare\n * `terraform destroy` blocked forever on an input prompt in any non-TTY\n * context.\n * - Both vended `providers.tf` declare `required_version = \">= 1.0\"`, matching\n * every other `.tf` file the plugin vends, and the bootstrap one pins the AWS\n * provider.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n */\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\n/** The scan command, as the pre-fix generator vended it on `test`. */\nconst CHECKOV_COMMAND_PATTERN = /uvx --from checkov==/;\n\nconst divergedTargetsStep = (projectName: string) =>\n `${projectName}: no 'test' target carrying the checkov scan was found, so no 'checkov' target was added - add one running checkov over the project so 'nx run-many --target checkov' includes this project.`;\n\nconst divergedProvidersStep = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. Add \\`required_version = \">= 1.0\"\\` to its \\`terraform\\` block to pin the minimum Terraform version.`;\n\nconst divergedBootstrapDestroyStep = (projectName: string) =>\n `${projectName}: its 'bootstrap-destroy' target no longer matches the shape the generator produced - left untouched. Pass \\`-var=aws_region=<region>\\` to \\`terraform destroy\\`, otherwise it blocks on an input prompt when run without a TTY.`;\n\n/**\n * Add `required_version` to a `providers.tf` that omits it.\n *\n * The bootstrap copy has no `terraform` block at all, so it is created;\n * the src copy has one holding `required_providers`, so it is added to.\n */\nconst migrateRequiredVersion = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(filePath)) return;\n\n // Already declared - nothing to do, whatever constraint the user chose.\n if (await matchGritQL(tree, filePath, hcl('`required_version = $_`'))) {\n return;\n }\n\n const hasRequiredProviders = await matchGritQL(\n tree,\n filePath,\n hcl(\n '`terraform { $body }` where { $body <: contains `required_providers` }',\n ),\n );\n\n if (hasRequiredProviders) {\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`terraform { $body }` => `terraform {\\n required_version = \">= 1.0\"\\n\\n $body\\n}`',\n ),\n );\n return;\n }\n\n // No `terraform` block: prepend one ahead of the aws provider block.\n const providerBlock = hcl('`provider \"aws\" { $body }`');\n if (!(await matchGritQL(tree, filePath, providerBlock))) {\n nextSteps.push(divergedProvidersStep(filePath));\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`provider \"aws\" { $body }` => `terraform {\\n required_version = \">= 1.0\"\\n}\\n\\nprovider \"aws\" {\\n $body\\n}`',\n ),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n const generator = (project.metadata as { generator?: string } | undefined)\n ?.generator;\n if (generator !== TERRAFORM_PROJECT_GENERATOR_INFO.id) continue;\n\n const targets = project.targets ?? {};\n let changed = false;\n\n // Add `checkov`, copying the scan from the `test` target the pre-fix\n // generator put it on. `test` itself belongs to the user and is left as it\n // is. Guarded on `checkov` being absent so a re-run is a no-op.\n if (!targets.checkov) {\n const command = targets.test?.options?.command;\n\n if (\n typeof command !== 'string' ||\n !CHECKOV_COMMAND_PATTERN.test(command)\n ) {\n nextSteps.push(divergedTargetsStep(projectName));\n } else {\n // The config file is vended below, so wire it in at the same time.\n const withConfigFile = command.includes('--config-file')\n ? command\n : command.replace(\n 'checkov --directory',\n 'checkov --config-file ../checkov.yml --directory',\n );\n\n targets.checkov = normalizeTargetKeyOrder({\n ...targets.test,\n options: { ...targets.test.options, command: withConfigFile },\n });\n changed = true;\n }\n }\n\n // Point `bootstrap-destroy` at the vended script, which resolves the\n // region rather than leaving terraform to prompt for it.\n const bootstrapDestroy = targets['bootstrap-destroy'];\n if (bootstrapDestroy) {\n const scriptCommand = `tsx {projectRoot}/scripts/bootstrap-destroy.ts {projectRoot}`;\n const alreadyMigrated =\n bootstrapDestroy.options?.commands?.[0] === scriptCommand;\n\n if (!alreadyMigrated) {\n const command = bootstrapDestroy.options?.command;\n // Only rewrite the exact shape the generator produced.\n const isVendedShape =\n typeof command === 'string' &&\n /^terraform destroy -state=\\S+bootstrap\\.tfstate$/.test(\n command.trim(),\n );\n\n if (!isVendedShape) {\n nextSteps.push(divergedBootstrapDestroyStep(projectName));\n } else {\n targets['bootstrap-destroy'] = normalizeTargetKeyOrder({\n executor: bootstrapDestroy.executor,\n options: {\n forwardAllArgs: true,\n commands: [scriptCommand],\n cwd: '{workspaceRoot}',\n },\n });\n changed = true;\n }\n }\n }\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, {\n ...project,\n targets: sortObjectKeys(targets),\n });\n }\n\n // Vend the checkov config and the bootstrap-destroy script. KeepExisting\n // leaves a user's own copy alone and makes a re-run a no-op.\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '../../../terraform/project/files/checkov',\n ),\n project.root,\n {},\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n\n // Only application projects have a bootstrap dir to destroy. The scripts\n // dir is shared with the generator's templates, so `stateKeyPrefix` is\n // resolved the same way it computes it; KeepExisting leaves the scripts\n // that already exist untouched regardless.\n if (tree.exists(joinPathFragments(project.root, 'bootstrap'))) {\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '../../../terraform/project/files/application/scripts',\n ),\n joinPathFragments(project.root, 'scripts'),\n { stateKeyPrefix: kebabCase(projectName) },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n }\n\n await migrateRequiredVersion(\n tree,\n joinPathFragments(project.root, 'src/providers.tf'),\n nextSteps,\n );\n await migrateRequiredVersion(\n tree,\n joinPathFragments(project.root, 'bootstrap/providers.tf'),\n nextSteps,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["generateFiles","getProjects","joinPathFragments","OverwriteStrategy","updateProjectConfiguration","TERRAFORM_PROJECT_GENERATOR_INFO","applyGritQL","matchGritQL","formatFilesInSubtree","kebabCase","normalizeTargetKeyOrder","sortObjectKeys","hcl","pattern","CHECKOV_COMMAND_PATTERN","divergedTargetsStep","projectName","divergedProvidersStep","filePath","divergedBootstrapDestroyStep","migrateRequiredVersion","tree","nextSteps","exists","hasRequiredProviders","providerBlock","push","migration","project","generator","metadata","id","targets","changed","checkov","command","test","options","withConfigFile","includes","replace","bootstrapDestroy","scriptCommand","alreadyMigrated","commands","isVendedShape","trim","executor","forwardAllArgs","cwd","dirname","root","overwriteStrategy","KeepExisting","stateKeyPrefix"],"mappings":"AAAA;;;CAGC,GACD,SACEA,aAAa,EACbC,WAAW,EACXC,iBAAiB,EAEjBC,iBAAiB,EAEjBC,0BAA0B,QACrB,aAAa;AACpB,SAASC,gCAAgC,QAAQ,0CAA0C;AAC3F,SAASC,WAAW,EAAEC,WAAW,QAAQ,wBAAwB;AACjE,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SAASC,SAAS,QAAQ,0BAA0B;AACpD,SAASC,uBAAuB,QAAQ,uBAAuB;AAC/D,SAASC,cAAc,QAAQ,2BAA2B;AAE1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,MAAMC,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,oEAAoE,GACpE,MAAMC,0BAA0B;AAEhC,MAAMC,sBAAsB,CAACC,cAC3B,GAAGA,YAAY,4LAA4L,CAAC;AAE9M,MAAMC,wBAAwB,CAACC,WAC7B,GAAGA,SAAS,8JAA8J,CAAC;AAE7K,MAAMC,+BAA+B,CAACH,cACpC,GAAGA,YAAY,gOAAgO,CAAC;AAElP;;;;;CAKC,GACD,MAAMI,yBAAyB,OAC7BC,MACAH,UACAI;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACL,WAAW;IAE5B,wEAAwE;IACxE,IAAI,MAAMX,YAAYc,MAAMH,UAAUN,IAAI,6BAA6B;QACrE;IACF;IAEA,MAAMY,uBAAuB,MAAMjB,YACjCc,MACAH,UACAN,IACE;IAIJ,IAAIY,sBAAsB;QACxB,MAAMlB,YACJe,MACAH,UACAN,IACE;QAGJ;IACF;IAEA,qEAAqE;IACrE,MAAMa,gBAAgBb,IAAI;IAC1B,IAAI,CAAE,MAAML,YAAYc,MAAMH,UAAUO,gBAAiB;QACvDH,UAAUI,IAAI,CAACT,sBAAsBC;QACrC;IACF;IAEA,MAAMZ,YACJe,MACAH,UACAN,IACE;AAGN;AAEA,eAAe,eAAee,UAC5BN,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,KAAK,MAAM,CAACN,aAAaY,QAAQ,IAAI3B,YAAYoB,MAAO;QACtD,MAAMQ,YAAaD,QAAQE,QAAQ,EAC/BD;QACJ,IAAIA,cAAcxB,iCAAiC0B,EAAE,EAAE;QAEvD,MAAMC,UAAUJ,QAAQI,OAAO,IAAI,CAAC;QACpC,IAAIC,UAAU;QAEd,qEAAqE;QACrE,2EAA2E;QAC3E,gEAAgE;QAChE,IAAI,CAACD,QAAQE,OAAO,EAAE;YACpB,MAAMC,UAAUH,QAAQI,IAAI,EAAEC,SAASF;YAEvC,IACE,OAAOA,YAAY,YACnB,CAACrB,wBAAwBsB,IAAI,CAACD,UAC9B;gBACAb,UAAUI,IAAI,CAACX,oBAAoBC;YACrC,OAAO;gBACL,mEAAmE;gBACnE,MAAMsB,iBAAiBH,QAAQI,QAAQ,CAAC,mBACpCJ,UACAA,QAAQK,OAAO,CACb,uBACA;gBAGNR,QAAQE,OAAO,GAAGxB,wBAAwB;oBACxC,GAAGsB,QAAQI,IAAI;oBACfC,SAAS;wBAAE,GAAGL,QAAQI,IAAI,CAACC,OAAO;wBAAEF,SAASG;oBAAe;gBAC9D;gBACAL,UAAU;YACZ;QACF;QAEA,qEAAqE;QACrE,yDAAyD;QACzD,MAAMQ,mBAAmBT,OAAO,CAAC,oBAAoB;QACrD,IAAIS,kBAAkB;YACpB,MAAMC,gBAAgB,CAAC,4DAA4D,CAAC;YACpF,MAAMC,kBACJF,iBAAiBJ,OAAO,EAAEO,UAAU,CAAC,EAAE,KAAKF;YAE9C,IAAI,CAACC,iBAAiB;gBACpB,MAAMR,UAAUM,iBAAiBJ,OAAO,EAAEF;gBAC1C,uDAAuD;gBACvD,MAAMU,gBACJ,OAAOV,YAAY,YACnB,mDAAmDC,IAAI,CACrDD,QAAQW,IAAI;gBAGhB,IAAI,CAACD,eAAe;oBAClBvB,UAAUI,IAAI,CAACP,6BAA6BH;gBAC9C,OAAO;oBACLgB,OAAO,CAAC,oBAAoB,GAAGtB,wBAAwB;wBACrDqC,UAAUN,iBAAiBM,QAAQ;wBACnCV,SAAS;4BACPW,gBAAgB;4BAChBJ,UAAU;gCAACF;6BAAc;4BACzBO,KAAK;wBACP;oBACF;oBACAhB,UAAU;gBACZ;YACF;QACF;QAEA,IAAIA,SAAS;YACX7B,2BAA2BiB,MAAML,aAAa;gBAC5C,GAAGY,OAAO;gBACVI,SAASrB,eAAeqB;YAC1B;QACF;QAEA,yEAAyE;QACzE,6DAA6D;QAC7DhC,cACEqB,MACAnB,kBACE,YAAYgD,OAAO,EACnB,6CAEFtB,QAAQuB,IAAI,EACZ,CAAC,GACD;YAAEC,mBAAmBjD,kBAAkBkD,YAAY;QAAC;QAGtD,yEAAyE;QACzE,uEAAuE;QACvE,wEAAwE;QACxE,2CAA2C;QAC3C,IAAIhC,KAAKE,MAAM,CAACrB,kBAAkB0B,QAAQuB,IAAI,EAAE,eAAe;YAC7DnD,cACEqB,MACAnB,kBACE,YAAYgD,OAAO,EACnB,yDAEFhD,kBAAkB0B,QAAQuB,IAAI,EAAE,YAChC;gBAAEG,gBAAgB7C,UAAUO;YAAa,GACzC;gBAAEoC,mBAAmBjD,kBAAkBkD,YAAY;YAAC;QAExD;QAEA,MAAMjC,uBACJC,MACAnB,kBAAkB0B,QAAQuB,IAAI,EAAE,qBAChC7B;QAEF,MAAMF,uBACJC,MACAnB,kBAAkB0B,QAAQuB,IAAI,EAAE,2BAChC7B;IAEJ;IAEA,MAAMd,qBAAqBa;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/user-identity-configurable-mfa/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { MigrationReturnObject, Tree } from '@nx/devkit';\nimport {\n addDestructuredImport,\n applyGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Surface mfa and mfaSecondFactor on the vended UserIdentity construct/module\n *\n * CDK's `mfa` / `mfaSecondFactor` props default to what the construct already\n * hard-coded (`Mfa.REQUIRED` / `{ sms: true, otp: true }`), so this just makes\n * them overridable, matching the existing `enableWaf` prop pattern.\n *\n * Terraform has no equivalent of CloudFormation's `EnabledMfas` - SMS MFA is\n * inseparable from the `sms_configuration` block, which phone number\n * verification also relies on. So `mfa_second_factor_sms` gates\n * `sms_configuration` (and, with it, SMS phone verification) rather than only\n * MFA, unlike the CDK prop.\n *\n * CDK has the same underlying coupling for any non-OFF `mfa`: Cognito rejects\n * `SmsConfiguration` (which CDK emits whenever phone is auto-verified) unless\n * SMS is also an enabled MFA method. So `phoneVerificationEnabled` gates\n * `autoVerify`/`keepOriginal`'s `phone` flag on whether SMS is actually usable\n * as a second factor, and a `mfa: REQUIRED` with neither `sms` nor `otp`\n * enabled - a pool nobody could ever sign into - is rejected at synth time.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, rather than\n * clobbering the user's changes.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files your\n * migration wrote are formatted correctly.\n */\n\nconst CDK_USER_IDENTITY_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/user-identity.ts`;\nconst TERRAFORM_IDENTITY_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/user-identity/identity/identity.tf`;\nconst TERRAFORM_MAIN_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/user-identity/main.tf`;\n\nconst NEW_TERRAFORM_VARIABLES = `variable \"mfa\" {\n description = \"Whether users must configure MFA, may optionally configure MFA, or cannot use MFA at all\"\n type = string\n default = \"ON\"\n\n validation {\n condition = contains([\"OFF\", \"OPTIONAL\", \"ON\"], var.mfa)\n error_message = \"mfa must be one of OFF, OPTIONAL, ON\"\n }\n}\n\nvariable \"mfa_second_factor_otp\" {\n description = \"Whether users can use a time-based one time password (TOTP), generated by a hardware or software token, as an MFA method\"\n type = bool\n default = true\n}\n\nvariable \"mfa_second_factor_sms\" {\n description = \"Whether users can use SMS to their verified phone numbers as an MFA method. Due to a Terraform AWS provider limitation, disabling this also disables SMS-based phone number verification, unlike the equivalent CDK construct prop\"\n type = bool\n default = true\n}`;\n\n// Guards each file: present only once it has been migrated.\nconst CDK_MIGRATED_PATTERN = '`mfa === Mfa.OFF || mfaSecondFactor.sms`';\nconst TERRAFORM_IDENTITY_MIGRATED_PATTERN =\n 'language hcl\\n`mfa_configuration = var.mfa`';\nconst TERRAFORM_MAIN_MIGRATED_PATTERN = 'language hcl\\n`mfa = var.mfa`';\n\n// Interface members don't parse as standalone snippets, so this anchors on the\n// whole interface body (preserved via $body) rather than the enableWaf member.\nconst CDK_INTERFACE_BODY_PATTERN =\n '`export interface UserIdentityProps { $body }`';\n// A parameter's type annotation blocks matching the destructure sub-pattern in\n// isolation, so this anchors on the full constructor signature instead\n// (literal params so a customised signature is correctly seen as diverged).\nconst CDK_CONSTRUCTOR_PATTERN =\n '`constructor(scope: Construct, id: string, { enableWaf = true }: UserIdentityProps = {}) { $body }`';\nconst CDK_CREATE_CALL_PATTERN = '`this.createUserPool()`';\nconst CDK_CREATE_SIGNATURE_PATTERN = '`private createUserPool = () => $body`';\n// Trailing commas block matching a bare object property in isolation; the\n// separator is outside the matched span either way, so it survives untouched.\nconst CDK_MFA_FIELD_PATTERN = '`mfa: Mfa.REQUIRED`';\nconst CDK_MFA_SECOND_FACTOR_FIELD_PATTERN =\n '`mfaSecondFactor: { sms: true, otp: true }`';\nconst CDK_SUPER_CALL_PATTERN = '`super(scope, id);`';\n// $props preserves the whole (large) UserPool props object untouched while\n// inserting the new const declaration immediately before it.\nconst CDK_NEW_USER_POOL_PATTERN =\n \"`const userPool = new UserPool(this, 'UserPool', { $props })`\";\n// Matches both the autoVerify.phone and keepOriginal.phone occurrences in one pass.\nconst CDK_PHONE_TRUE_PATTERN = '`phone: true`';\n\nconst CDK_EDITS: Array<[string, string]> = [\n [\n CDK_INTERFACE_BODY_PATTERN,\n `${CDK_INTERFACE_BODY_PATTERN} => \\`export interface UserIdentityProps {\n $body\n\n /**\n * Whether users must configure MFA, may optionally configure MFA, or cannot use MFA at all.\n *\n * @default Mfa.REQUIRED\n */\n readonly mfa?: Mfa;\n\n /**\n * The MFA methods available to users when MFA is not off.\n *\n * @default { sms: true, otp: true }\n */\n readonly mfaSecondFactor?: MfaSecondFactor;\n}\\``,\n ],\n [\n CDK_CONSTRUCTOR_PATTERN,\n `${CDK_CONSTRUCTOR_PATTERN} => \\`constructor(\n scope: Construct,\n id: string,\n {\n enableWaf = true,\n mfa = Mfa.REQUIRED,\n mfaSecondFactor = { sms: true, otp: true },\n }: UserIdentityProps = {},\n ) { $body }\\``,\n ],\n [\n CDK_CREATE_CALL_PATTERN,\n `${CDK_CREATE_CALL_PATTERN} => \\`this.createUserPool(mfa, mfaSecondFactor)\\``,\n ],\n [\n CDK_CREATE_SIGNATURE_PATTERN,\n `${CDK_CREATE_SIGNATURE_PATTERN} => \\`private createUserPool = (mfa: Mfa, mfaSecondFactor: MfaSecondFactor) => $body\\``,\n ],\n [CDK_MFA_FIELD_PATTERN, `${CDK_MFA_FIELD_PATTERN} => \\`mfa\\``],\n [\n CDK_MFA_SECOND_FACTOR_FIELD_PATTERN,\n `${CDK_MFA_SECOND_FACTOR_FIELD_PATTERN} => \\`mfaSecondFactor\\``,\n ],\n [\n CDK_SUPER_CALL_PATTERN,\n `${CDK_SUPER_CALL_PATTERN} => \\`super(scope, id);\n\n if (mfa === Mfa.REQUIRED && !mfaSecondFactor.sms && !mfaSecondFactor.otp) {\n throw new Error(\n 'UserIdentity: mfa is REQUIRED but mfaSecondFactor has no methods enabled (sms and otp are both false)',\n );\n }\\``,\n ],\n [\n CDK_NEW_USER_POOL_PATTERN,\n `${CDK_NEW_USER_POOL_PATTERN} => \\`const phoneVerificationEnabled = mfa === Mfa.OFF || mfaSecondFactor.sms;\n\n const userPool = new UserPool(this, 'UserPool', { $props })\\``,\n ],\n [\n CDK_PHONE_TRUE_PATTERN,\n `${CDK_PHONE_TRUE_PATTERN} => \\`phone: phoneVerificationEnabled\\``,\n ],\n];\n\nconst TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR = [\n 'language hcl',\n '`variable \"enable_waf\" {',\n ' description = \"Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet) and associate it with the user pool\"',\n ' type = bool',\n ' default = true',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MFA_CONFIGURATION_PATTERN = [\n 'language hcl',\n '`mfa_configuration = \"ON\"`',\n].join('\\n');\n\nconst TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN = [\n 'language hcl',\n '`software_token_mfa_configuration {',\n ' enabled = true',\n ' }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_CONFIGURATION_PATTERN = [\n 'language hcl',\n '`sms_configuration {',\n ' external_id = random_uuid.sms_external_id.result',\n ' sns_caller_arn = aws_iam_role.cognito_sms_role.arn',\n ' sns_region = data.aws_region.current.region',\n ' }`',\n].join('\\n');\n\nconst TERRAFORM_RANDOM_UUID_PATTERN = [\n 'language hcl',\n '`resource \"random_uuid\" \"sms_external_id\" {}`',\n].join('\\n');\n\nconst TERRAFORM_SMS_ROLE_PATTERN = [\n 'language hcl',\n '`resource \"aws_iam_role\" \"cognito_sms_role\" { $body }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_POLICY_PATTERN = [\n 'language hcl',\n '`resource \"aws_iam_role_policy\" \"cognito_sms_policy\" { $body }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_ROLE_ID_REF_PATTERN = [\n 'language hcl',\n '`aws_iam_role.cognito_sms_role.id`',\n].join('\\n');\n\nconst TERRAFORM_EXTERNAL_ID_REF_PATTERN = [\n 'language hcl',\n '`random_uuid.sms_external_id.result`',\n].join('\\n');\n\nconst TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN = [\n 'language hcl',\n '`auto_verified_attributes = [\"email\", \"phone_number\"]`',\n].join('\\n');\n\nconst TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN = [\n 'language hcl',\n '`attributes_require_verification_before_update = [\"email\", \"phone_number\"]`',\n].join('\\n');\n\nconst TERRAFORM_IDENTITY_EDITS: Array<[string, string]> = [\n [\n TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR,\n `${TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR} => \\`variable \"enable_waf\" {\n description = \"Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet) and associate it with the user pool\"\n type = bool\n default = true\n}\n\n${NEW_TERRAFORM_VARIABLES}\\``,\n ],\n [\n TERRAFORM_RANDOM_UUID_PATTERN,\n `${TERRAFORM_RANDOM_UUID_PATTERN} => \\`resource \"random_uuid\" \"sms_external_id\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n}\\``,\n ],\n [\n TERRAFORM_SMS_ROLE_PATTERN,\n `${TERRAFORM_SMS_ROLE_PATTERN} => \\`resource \"aws_iam_role\" \"cognito_sms_role\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n\n $body\n}\\``,\n ],\n [\n TERRAFORM_SMS_POLICY_PATTERN,\n `${TERRAFORM_SMS_POLICY_PATTERN} => \\`resource \"aws_iam_role_policy\" \"cognito_sms_policy\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n\n $body\n}\\``,\n ],\n [\n TERRAFORM_MFA_CONFIGURATION_PATTERN,\n `${TERRAFORM_MFA_CONFIGURATION_PATTERN} => \\`mfa_configuration = var.mfa\\``,\n ],\n [\n TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN,\n `${TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN} => \\`software_token_mfa_configuration {\n enabled = var.mfa_second_factor_otp\n }\\``,\n ],\n [\n TERRAFORM_SMS_CONFIGURATION_PATTERN,\n `${TERRAFORM_SMS_CONFIGURATION_PATTERN} => \\`# SMS MFA configuration. Due to a Terraform AWS provider limitation, this also gates\n # SMS-based phone number verification below - see the mfa_second_factor_sms variable.\n dynamic \"sms_configuration\" {\n for_each = var.mfa_second_factor_sms ? [1] : []\n\n content {\n external_id = random_uuid.sms_external_id[0].result\n sns_caller_arn = aws_iam_role.cognito_sms_role[0].arn\n sns_region = data.aws_region.current.region\n }\n }\\``,\n ],\n [\n TERRAFORM_SMS_ROLE_ID_REF_PATTERN,\n `${TERRAFORM_SMS_ROLE_ID_REF_PATTERN} => \\`aws_iam_role.cognito_sms_role[0].id\\``,\n ],\n [\n TERRAFORM_EXTERNAL_ID_REF_PATTERN,\n `${TERRAFORM_EXTERNAL_ID_REF_PATTERN} => \\`random_uuid.sms_external_id[0].result\\``,\n ],\n [\n TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN,\n `${TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN} => \\`auto_verified_attributes = var.mfa_second_factor_sms ? [\"email\", \"phone_number\"] : [\"email\"]\\``,\n ],\n [\n TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN,\n `${TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN} => \\`attributes_require_verification_before_update = var.mfa_second_factor_sms ? [\"email\", \"phone_number\"] : [\"email\"]\\``,\n ],\n];\n\nconst TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR = [\n 'language hcl',\n '`variable \"enable_waf\" {',\n ' description = \"Whether to enable AWS WAFv2 with the default managed ruleset and associate it with the user pool\"',\n ' type = bool',\n ' default = true',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MAIN_MODULE_PATTERN = [\n 'language hcl',\n '`module \"identity\" {',\n ' source = \"./identity\"',\n '',\n ' user_pool_domain_prefix = $prefix',\n ' allow_signup = $allowSignup',\n ' enable_waf = var.enable_waf',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MAIN_EDITS: Array<[string, string]> = [\n [\n TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR,\n `${TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR} => \\`variable \"enable_waf\" {\n description = \"Whether to enable AWS WAFv2 with the default managed ruleset and associate it with the user pool\"\n type = bool\n default = true\n}\n\n${NEW_TERRAFORM_VARIABLES}\\``,\n ],\n [\n TERRAFORM_MAIN_MODULE_PATTERN,\n `${TERRAFORM_MAIN_MODULE_PATTERN} => \\`module \"identity\" {\n source = \"./identity\"\n\n user_pool_domain_prefix = $prefix\n allow_signup = $allowSignup\n enable_waf = var.enable_waf\n mfa = var.mfa\n mfa_second_factor_otp = var.mfa_second_factor_otp\n mfa_second_factor_sms = var.mfa_second_factor_sms\n}\\``,\n ],\n];\n\nconst divergedNextStep = (filePath: string) =>\n `${filePath}: the UserIdentity construct/module has diverged from the generated shape - left untouched. To make MFA configurable, add mfa and mfaSecondFactor (CDK) / mfa, mfa_second_factor_otp and mfa_second_factor_sms (Terraform) yourself, following the same pattern as the existing enableWaf prop/variable.`;\n\nconst applyEdits = async (\n tree: Tree,\n filePath: string,\n migratedPattern: string,\n edits: Array<[string, string]>,\n nextSteps: string[],\n beforeApply?: () => Promise<void>,\n) => {\n if (!tree.exists(filePath)) {\n // This workspace doesn't use this IaC provider, or has no UserIdentity.\n return;\n }\n\n if (await matchGritQL(tree, filePath, migratedPattern)) {\n // Already migrated - silent skip keeps re-runs a no-op.\n return;\n }\n\n // Confirm every edit site is present before writing any of them, so a file\n // that only partly matches is left whole rather than half-edited.\n const allSitesPresent = (\n await Promise.all(\n edits.map(([match]) => matchGritQL(tree, filePath, match)),\n )\n ).every(Boolean);\n\n if (!allSitesPresent) {\n nextSteps.push(divergedNextStep(filePath));\n return;\n }\n\n await beforeApply?.();\n\n for (const [, rewrite] of edits) {\n await applyGritQL(tree, filePath, rewrite);\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await applyEdits(\n tree,\n CDK_USER_IDENTITY_FILE,\n CDK_MIGRATED_PATTERN,\n CDK_EDITS,\n nextSteps,\n () =>\n addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['type MfaSecondFactor'],\n 'aws-cdk-lib/aws-cognito',\n ),\n );\n\n await applyEdits(\n tree,\n TERRAFORM_IDENTITY_FILE,\n TERRAFORM_IDENTITY_MIGRATED_PATTERN,\n TERRAFORM_IDENTITY_EDITS,\n nextSteps,\n );\n\n await applyEdits(\n tree,\n TERRAFORM_MAIN_FILE,\n TERRAFORM_MAIN_MIGRATED_PATTERN,\n TERRAFORM_MAIN_EDITS,\n nextSteps,\n );\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["addDestructuredImport","applyGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_USER_IDENTITY_FILE","TERRAFORM_IDENTITY_FILE","TERRAFORM_MAIN_FILE","NEW_TERRAFORM_VARIABLES","CDK_MIGRATED_PATTERN","TERRAFORM_IDENTITY_MIGRATED_PATTERN","TERRAFORM_MAIN_MIGRATED_PATTERN","CDK_INTERFACE_BODY_PATTERN","CDK_CONSTRUCTOR_PATTERN","CDK_CREATE_CALL_PATTERN","CDK_CREATE_SIGNATURE_PATTERN","CDK_MFA_FIELD_PATTERN","CDK_MFA_SECOND_FACTOR_FIELD_PATTERN","CDK_SUPER_CALL_PATTERN","CDK_NEW_USER_POOL_PATTERN","CDK_PHONE_TRUE_PATTERN","CDK_EDITS","TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR","join","TERRAFORM_MFA_CONFIGURATION_PATTERN","TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN","TERRAFORM_SMS_CONFIGURATION_PATTERN","TERRAFORM_RANDOM_UUID_PATTERN","TERRAFORM_SMS_ROLE_PATTERN","TERRAFORM_SMS_POLICY_PATTERN","TERRAFORM_SMS_ROLE_ID_REF_PATTERN","TERRAFORM_EXTERNAL_ID_REF_PATTERN","TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN","TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN","TERRAFORM_IDENTITY_EDITS","TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR","TERRAFORM_MAIN_MODULE_PATTERN","TERRAFORM_MAIN_EDITS","divergedNextStep","filePath","applyEdits","tree","migratedPattern","edits","nextSteps","beforeApply","exists","allSitesPresent","Promise","all","map","match","every","Boolean","push","rewrite","migration"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/waf-log-group-cmk-encryption/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport {\n addDestructuredImport,\n applyGritQL,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * WAF request logs carry full request metadata - URIs, headers and client IPs -\n * so the vended WAF log groups are encrypted with a customer-managed KMS key,\n * matching how the vended access log groups beside them already are. Three were\n * plaintext: the Terraform REST API and user-pool WAF log groups, the Terraform\n * AgentCore gateway WAF log group, and the CDK user-pool WAF log group. Each now\n * gets a key whose policy grants CloudWatch Logs use of it, scoped by\n * `kms:EncryptionContext:aws:logs:arn` to that log group alone.\n *\n * CloudWatch Logs cannot encrypt a log group that already exists in place, so\n * this only rewrites the vended templates. On the next deploy Terraform sets\n * `kms_key_id` on the existing group (an in-place update) and CloudFormation\n * replaces the CDK one, which the vended `RemovalPolicy.DESTROY` already allows.\n * Events written before then stay unencrypted; the `nextSteps` note says so.\n */\n\nconst CONSTRUCTS_SRC = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src`;\nconst TERRAFORM_SRC = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src`;\n\nconst CDK_USER_IDENTITY_FILE = `${CONSTRUCTS_SRC}/core/user-identity.ts`;\nconst TERRAFORM_REST_API_FILE = `${TERRAFORM_SRC}/core/api/rest-api/rest-api.tf`;\nconst TERRAFORM_USER_IDENTITY_FILE = `${TERRAFORM_SRC}/core/user-identity/identity/identity.tf`;\nconst TERRAFORM_GATEWAYS_DIR = `${TERRAFORM_SRC}/app/gateways`;\n\nconst divergedMessage = (file: string) =>\n `${file}: the WAF log group has diverged from the generated shape - left untouched. Its request logs stay unencrypted. To encrypt them, add a customer-managed KMS key granting the \\`logs.<region>.amazonaws.com\\` service principal kms:Encrypt/Decrypt/ReEncrypt*/GenerateDataKey*/DescribeKey, conditioned on \\`kms:EncryptionContext:aws:logs:arn\\` matching the log group, and point the log group at it (see the vended WAF log group template).`;\n\nconst ALREADY_ENCRYPTED_TERRAFORM = 'aws_kms_key.';\n\n// Terraform (HCL) patterns are built from plain strings rather than JS template\n// literals, since the HCL text itself is full of `${...}` interpolations that\n// would otherwise be parsed as JS interpolation.\nconst hcl = (pattern: string) => 'language hcl\\n' + pattern;\n\n/** A vended Terraform WAF log group and the key this migration gives it. */\ninterface TerraformWafLogGroup {\n /** The `aws_cloudwatch_log_group` resource label. */\n readonly logGroupLabel: string;\n /** The `aws_kms_key` / `aws_kms_alias` label to create. */\n readonly keyLabel: string;\n /** The `locals` entry the log group name moves into. */\n readonly nameLocal: string;\n /** The log group's name expression, which becomes the local's value. */\n readonly nameExpression: string;\n /** The KMS key description. */\n readonly keyDescription: string;\n /** The KMS alias name expression. */\n readonly aliasName: string;\n /** How the account id is referenced in this module. */\n readonly accountId: string;\n /** How the region is referenced in this module. */\n readonly region: string;\n /** Whether the module tags its resources. */\n readonly tagged: boolean;\n}\n\nconst REST_API_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'api_waf_logs',\n keyLabel: 'waf_logs',\n nameLocal: 'waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}\" : null',\n keyDescription: '\"${var.api_name} API WAF log encryption\"',\n aliasName:\n '\"alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: true,\n};\n\nconst USER_IDENTITY_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'user_pool_waf_logs',\n keyLabel: 'user_pool_waf_logs',\n nameLocal: 'user_pool_waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-UserPool-${random_id.unique_suffix.hex}\" : null',\n keyDescription: '\"User pool WAF log encryption\"',\n aliasName: '\"alias/user-pool-${random_id.unique_suffix.hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: false,\n};\n\nconst gatewayLogGroup = (nameKebabCase: string): TerraformWafLogGroup => ({\n logGroupLabel: 'gateway_waf_logs',\n keyLabel: 'gateway_waf_logs',\n nameLocal: 'gateway_waf_log_group_name',\n nameExpression: `var.enable_waf ? \"aws-waf-logs-${nameKebabCase}-\\${random_id.unique_suffix.hex}\" : null`,\n keyDescription: `\"${nameKebabCase} gateway WAF log encryption\"`,\n aliasName: `\"alias/${nameKebabCase}-\\${random_id.unique_suffix.hex}-waf-logs\"`,\n accountId: 'local.aws_account_id',\n region: 'local.aws_region',\n tagged: false,\n});\n\n/** The KMS key and alias resources, as the templates now vend them. */\nconst keyResourcesText = (group: TerraformWafLogGroup): string =>\n [\n '# KMS key for encrypting WAF request logs at rest',\n `resource \"aws_kms_key\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` description = ${group.keyDescription}`,\n ' deletion_window_in_days = 7',\n ' enable_key_rotation = true',\n '',\n ' policy = jsonencode({',\n ' Version = \"2012-10-17\"',\n ' Statement = [',\n ' {',\n ' Sid = \"Enable IAM User Permissions\"',\n ' Effect = \"Allow\"',\n ` Principal = { AWS = \"arn:aws:iam::\\${${group.accountId}}:root\" }`,\n ' Action = \"kms:*\"',\n ' Resource = \"*\"',\n ' },',\n ' {',\n ' Sid = \"Allow CloudWatch Logs\"',\n ' Effect = \"Allow\"',\n ` Principal = { Service = \"logs.\\${${group.region}}.amazonaws.com\" }`,\n ' Action = [',\n ' \"kms:Encrypt\",',\n ' \"kms:Decrypt\",',\n ' \"kms:ReEncrypt*\",',\n ' \"kms:GenerateDataKey*\",',\n ' \"kms:DescribeKey\"',\n ' ]',\n ' Resource = \"*\"',\n ' Condition = {',\n ' ArnEquals = {',\n ` \"kms:EncryptionContext:aws:logs:arn\" = \"arn:aws:logs:\\${${group.region}}:\\${${group.accountId}}:log-group:\\${local.${group.nameLocal}}\"`,\n ' }',\n ' }',\n ' }',\n ' ]',\n ' })',\n ...(group.tagged ? ['', ' tags = var.tags'] : []),\n '}',\n '',\n `resource \"aws_kms_alias\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` name = ${group.aliasName}`,\n ` target_key_id = aws_kms_key.${group.keyLabel}[0].key_id`,\n '}',\n ].join('\\n');\n\nconst migrateTerraformLogGroup = async (\n tree: Tree,\n file: string,\n group: TerraformWafLogGroup,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(file, 'utf-8') ?? '';\n\n // The WAF log group is only vended when WAF is enabled, so it may be absent.\n if (\n !contents.includes(`\"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\"`)\n ) {\n return;\n }\n\n const LOG_GROUP_BLOCK = hcl(\n `\\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $body }\\``,\n );\n const NAME_LINE = hcl(\n `\\`name = ${group.nameExpression.replace(/^var\\.enable_waf \\? /, '').replace(/ : null$/, '')}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n );\n const SUPPRESSION_LINE =\n '#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption';\n const LOCALS_BLOCK = hcl('`locals { $body }`');\n\n if (contents.includes(ALREADY_ENCRYPTED_TERRAFORM + group.keyLabel)) {\n return; // Already migrated.\n }\n\n const ready =\n (await matchGritQL(tree, file, LOG_GROUP_BLOCK)) &&\n (await matchGritQL(tree, file, NAME_LINE)) &&\n (await matchGritQL(tree, file, LOCALS_BLOCK)) &&\n contents.includes(SUPPRESSION_LINE);\n\n if (!ready) {\n nextSteps.push(divergedMessage(file));\n return;\n }\n\n // 1. Route the log group name through a local, so the key policy's encryption\n // context condition can name the same log group.\n await insertViaGritQL(\n tree,\n file,\n LOCALS_BLOCK +\n ` as $l => \\`locals {\\n $body\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n `${group.nameLocal} = ${group.nameExpression}`,\n );\n await applyGritQL(\n tree,\n file,\n NAME_LINE + ` => \\`name = local.${group.nameLocal}\\``,\n );\n\n // 2. Point the log group at a new key, added after it so the log group keeps\n // the comment above it.\n await insertViaGritQL(\n tree,\n file,\n hcl(\n `\\`name = local.${group.nameLocal}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n ) + ` => \\`$line\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n `kms_key_id = aws_kms_key.${group.keyLabel}[0].arn`,\n );\n await insertViaGritQL(\n tree,\n file,\n LOG_GROUP_BLOCK + ` as $lg => \\`$lg\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n keyResourcesText(group),\n );\n\n // 3. Drop the suppression the encryption makes redundant.\n const updated = (tree.read(file, 'utf-8') ?? '')\n .split('\\n')\n .filter((line) => line.trim() !== SUPPRESSION_LINE)\n .join('\\n');\n tree.write(file, updated);\n};\n\n/**\n * The vended CDK user-pool WAF log group gains an `encryptionKey`, matching the\n * eight sibling CDK WAF log groups which already pass one.\n */\nconst migrateCdkUserIdentity = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_USER_IDENTITY_FILE)) {\n return; // This workspace has no CDK user identity construct.\n }\n\n const contents = tree.read(CDK_USER_IDENTITY_FILE, 'utf-8') ?? '';\n\n // The WAF web ACL (and its log group) is optional, so it may be absent.\n if (!contents.includes(`new LogGroup(this, 'WebAclLogs'`)) {\n return;\n }\n\n if (contents.includes('WebAclLogsKey')) {\n return; // Already migrated.\n }\n\n // Already encrypted, whether by this migration or by the user's own key.\n if (\n await matchGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`new LogGroup(this, 'WebAclLogs', { $props })` where { $props <: contains `encryptionKey` }\",\n )\n ) {\n return;\n }\n\n const LOG_GROUP =\n \"`new LogGroup(this, 'WebAclLogs', { $props })` as $lg where { $props <: contains `logGroupName: $name` }\";\n const SUPPRESS_CALL =\n \"`suppressRules(wafLogGroup, ['CKV_AWS_158'], $reason)` as $call\";\n\n const ready =\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, LOG_GROUP)) &&\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL));\n\n if (!ready) {\n nextSteps.push(divergedMessage(CDK_USER_IDENTITY_FILE));\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Key'],\n 'aws-cdk-lib/aws-kms',\n );\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Effect', 'PolicyStatement', 'ServicePrincipal'],\n 'aws-cdk-lib/aws-iam',\n );\n\n // Hoist the log group name into a const so the key policy's encryption context\n // condition can name the same log group, then create the key above the group.\n await insertViaGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`const wafLogGroup = new LogGroup(this, 'WebAclLogs', { $props })` as $decl => \" +\n `\\`${GRIT_INSERT_PLACEHOLDER}\\n\\n$decl\\``,\n `const stack = Stack.of(this);\n const wafLogGroupName = \\`aws-waf-logs-\\${id}-\\${this.node.addr.slice(-8)}\\`;\n\n // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs\n const logsKey = new Key(this, 'WebAclLogsKey', {\n enableKeyRotation: true,\n });\n logsKey.addToResourcePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n principals: [new ServicePrincipal(\\`logs.\\${stack.region}.amazonaws.com\\`)],\n actions: [\n 'kms:Encrypt',\n 'kms:Decrypt',\n 'kms:ReEncrypt*',\n 'kms:GenerateDataKey*',\n 'kms:DescribeKey',\n ],\n resources: ['*'],\n conditions: {\n ArnEquals: {\n 'kms:EncryptionContext:aws:logs:arn': \\`arn:aws:logs:\\${stack.region}:\\${stack.account}:log-group:\\${wafLogGroupName}\\`,\n },\n },\n }),\n );`,\n );\n\n await applyGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`logGroupName: $name` as $prop where { $prop <: within `new LogGroup(this, 'WebAclLogs', $_)` } => `logGroupName: wafLogGroupName,\\n encryptionKey: logsKey`\",\n );\n\n // Drop the suppression the encryption makes redundant.\n await applyGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL + ' => ``');\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (tree.exists(TERRAFORM_REST_API_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_REST_API_FILE,\n REST_API_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_USER_IDENTITY_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_USER_IDENTITY_FILE,\n USER_IDENTITY_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_GATEWAYS_DIR)) {\n for (const dirName of tree.children(TERRAFORM_GATEWAYS_DIR)) {\n const file = joinPathFragments(\n TERRAFORM_GATEWAYS_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (tree.exists(file)) {\n await migrateTerraformLogGroup(\n tree,\n file,\n gatewayLogGroup(dirName),\n nextSteps,\n );\n }\n }\n }\n\n await migrateCdkUserIdentity(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CONSTRUCTS_SRC","TERRAFORM_SRC","CDK_USER_IDENTITY_FILE","TERRAFORM_REST_API_FILE","TERRAFORM_USER_IDENTITY_FILE","TERRAFORM_GATEWAYS_DIR","divergedMessage","file","ALREADY_ENCRYPTED_TERRAFORM","hcl","pattern","REST_API_LOG_GROUP","logGroupLabel","keyLabel","nameLocal","nameExpression","keyDescription","aliasName","accountId","region","tagged","USER_IDENTITY_LOG_GROUP","gatewayLogGroup","nameKebabCase","keyResourcesText","group","join","migrateTerraformLogGroup","tree","nextSteps","contents","read","includes","LOG_GROUP_BLOCK","NAME_LINE","replace","SUPPRESSION_LINE","LOCALS_BLOCK","ready","push","updated","split","filter","line","trim","write","migrateCdkUserIdentity","exists","LOG_GROUP","SUPPRESS_CALL","migration","dirName","children"],"mappings":"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"}