@aws/nx-plugin 1.0.0-rc.96 → 1.0.0-rc.97

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 (159) hide show
  1. package/migrations.json +33 -3
  2. package/package.json +1 -1
  3. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
  4. package/src/agentcore-gateway/generator.js +6 -0
  5. package/src/agentcore-gateway/generator.js.map +1 -1
  6. package/src/agentcore-gateway/react-connection/__snapshots__/generator.spec.ts.snap +4 -1
  7. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +68 -4
  8. package/src/agentcore-harness/generator.js +6 -0
  9. package/src/agentcore-harness/generator.js.map +1 -1
  10. package/src/connection/gateway-runtime-config.js +1 -1
  11. package/src/connection/gateway-runtime-config.js.map +1 -1
  12. package/src/infra/app/__snapshots__/generator.spec.ts.snap +6 -6
  13. package/src/infra/app/generator.js +34 -7
  14. package/src/infra/app/generator.js.map +1 -1
  15. package/src/init/generator.d.ts +1 -1
  16. package/src/init/generator.js +8 -6
  17. package/src/init/generator.js.map +1 -1
  18. package/src/init/schema.d.js.map +1 -1
  19. package/src/init/schema.d.ts +1 -0
  20. package/src/init/schema.json +5 -0
  21. package/src/mcp-server/server.js +2 -0
  22. package/src/mcp-server/server.js.map +1 -1
  23. package/src/mcp-server/tools/best-practices.d.ts +28 -0
  24. package/src/mcp-server/tools/best-practices.js +43 -0
  25. package/src/mcp-server/tools/best-practices.js.map +1 -0
  26. package/src/mcp-server/tools/general-guidance.d.ts +1 -1
  27. package/src/mcp-server/tools/general-guidance.js +13 -4
  28. package/src/mcp-server/tools/general-guidance.js.map +1 -1
  29. package/src/migrations/latest/checkov-scan-cache-inputs/metadata.json +3 -0
  30. package/src/migrations/latest/checkov-scan-cache-inputs/migration.d.ts +6 -0
  31. package/src/migrations/latest/checkov-scan-cache-inputs/migration.js +58 -0
  32. package/src/migrations/latest/checkov-scan-cache-inputs/migration.js.map +1 -0
  33. package/src/migrations/latest/gateway-url-accessor/__snapshots__/migration.spec.ts.snap +84 -0
  34. package/src/migrations/latest/gateway-url-accessor/metadata.json +3 -0
  35. package/src/migrations/latest/gateway-url-accessor/migration.d.ts +6 -0
  36. package/src/migrations/latest/gateway-url-accessor/migration.js +94 -0
  37. package/src/migrations/latest/gateway-url-accessor/migration.js.map +1 -0
  38. package/src/migrations/latest/infra-deploy-express-mode/metadata.json +1 -1
  39. package/src/migrations/latest/infra-deploy-express-mode/migration.js +26 -28
  40. package/src/migrations/latest/infra-deploy-express-mode/migration.js.map +1 -1
  41. package/src/migrations/latest/py-lambda-powertools-context/metadata.json +3 -0
  42. package/src/migrations/latest/py-lambda-powertools-context/migration.d.ts +6 -0
  43. package/src/migrations/latest/py-lambda-powertools-context/migration.js +58 -0
  44. package/src/migrations/latest/py-lambda-powertools-context/migration.js.map +1 -0
  45. package/src/migrations/latest/script-project-quality-gates/metadata.json +3 -0
  46. package/src/migrations/latest/script-project-quality-gates/migration.d.ts +6 -0
  47. package/src/migrations/latest/script-project-quality-gates/migration.js +53 -0
  48. package/src/migrations/latest/script-project-quality-gates/migration.js.map +1 -0
  49. package/src/migrations/latest/smithy-extension-trait-aliases/__snapshots__/migration.spec.ts.snap +25 -0
  50. package/src/migrations/latest/smithy-extension-trait-aliases/metadata.json +3 -0
  51. package/src/migrations/latest/smithy-extension-trait-aliases/migration.d.ts +28 -0
  52. package/src/migrations/latest/smithy-extension-trait-aliases/migration.js +75 -0
  53. package/src/migrations/latest/smithy-extension-trait-aliases/migration.js.map +1 -0
  54. package/src/migrations/latest/terraform-validate-without-backend/metadata.json +3 -0
  55. package/src/migrations/latest/terraform-validate-without-backend/migration.d.ts +6 -0
  56. package/src/migrations/latest/terraform-validate-without-backend/migration.js +84 -0
  57. package/src/migrations/latest/terraform-validate-without-backend/migration.js.map +1 -0
  58. package/src/preset/generator.js +4 -25
  59. package/src/preset/generator.js.map +1 -1
  60. package/src/py/agent/a2a-connection/generator.d.ts +1 -1
  61. package/src/py/agent/gateway-connection/generator.d.ts +1 -1
  62. package/src/py/agent/mcp-connection/generator.d.ts +1 -1
  63. package/src/py/agent/react-connection/__snapshots__/generator.spec.ts.snap +4 -1
  64. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +8 -10
  65. package/src/py/dynamodb/generator.js +34 -7
  66. package/src/py/dynamodb/generator.js.map +1 -1
  67. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -1
  68. package/src/py/lambda-function/files/handler/__lambdaFunctionSnakeCase__.py.template +2 -1
  69. package/src/py/project/schema.json +3 -1
  70. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +28 -12
  71. package/src/py/rdb/files/Dockerfile.create-db-user.template +7 -3
  72. package/src/py/rdb/files/Dockerfile.migration.template +7 -3
  73. package/src/py/rdb/generator.js +29 -28
  74. package/src/py/rdb/generator.js.map +1 -1
  75. package/src/smithy/react-connection/files/model/extensions.smithy.template +3 -3
  76. package/src/smithy/ts/api/generator.js +7 -2
  77. package/src/smithy/ts/api/generator.js.map +1 -1
  78. package/src/terraform/project/generator.js +34 -10
  79. package/src/terraform/project/generator.js.map +1 -1
  80. package/src/trpc/backend/generator.js +7 -1
  81. package/src/trpc/backend/generator.js.map +1 -1
  82. package/src/trpc/react/generator.js +6 -0
  83. package/src/trpc/react/generator.js.map +1 -1
  84. package/src/ts/agent/generator.js +3 -3
  85. package/src/ts/agent/generator.js.map +1 -1
  86. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -0
  87. package/src/ts/dynamodb/generator.js +30 -6
  88. package/src/ts/dynamodb/generator.js.map +1 -1
  89. package/src/ts/lib/biome.d.ts +15 -1
  90. package/src/ts/lib/biome.js +79 -63
  91. package/src/ts/lib/biome.js.map +1 -1
  92. package/src/ts/lib/generator.js +11 -7
  93. package/src/ts/lib/generator.js.map +1 -1
  94. package/src/ts/lib/ts-project-utils.d.ts +14 -0
  95. package/src/ts/lib/ts-project-utils.js +24 -0
  96. package/src/ts/lib/ts-project-utils.js.map +1 -1
  97. package/src/ts/mcp-server/generator.js +3 -3
  98. package/src/ts/mcp-server/generator.js.map +1 -1
  99. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +5 -0
  100. package/src/ts/rdb/files/Dockerfile.template +5 -0
  101. package/src/ts/rdb/generator.d.ts +8 -0
  102. package/src/ts/rdb/generator.js +28 -24
  103. package/src/ts/rdb/generator.js.map +1 -1
  104. package/src/ts/rdb/trpc-connection/__snapshots__/generator.spec.ts.snap +39 -0
  105. package/src/ts/rdb/trpc-connection/generator.js +13 -0
  106. package/src/ts/rdb/trpc-connection/generator.js.map +1 -1
  107. package/src/ts/react-website/agui/files/common/src/components/__agentNameKebabCase__-chat.tsx.template +9 -0
  108. package/src/ts/react-website/agui/files/common/src/hooks/useAgui__agentNameClassName__.tsx.template +4 -1
  109. package/src/ts/react-website/agui/generator.js +6 -2
  110. package/src/ts/react-website/agui/generator.js.map +1 -1
  111. package/src/ts/react-website/app/generator.js +3 -8
  112. package/src/ts/react-website/app/generator.js.map +1 -1
  113. package/src/utils/agent-connection/agent-connection.d.ts +2 -1
  114. package/src/utils/agent-connection/agent-connection.js +6 -1
  115. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  116. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +11 -7
  117. package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +14 -6
  118. package/src/utils/agent-core-packaging.js +5 -5
  119. package/src/utils/agent-core-packaging.js.map +1 -1
  120. package/src/utils/api-constructs/api-constructs.d.ts +5 -0
  121. package/src/utils/api-constructs/api-constructs.js +8 -0
  122. package/src/utils/api-constructs/api-constructs.js.map +1 -1
  123. package/src/utils/api-constructs/files/cdk/app/apis/http/__apiNameKebabCase__.ts.template +4 -2
  124. package/src/utils/api-constructs/files/cdk/app/apis/rest/__apiNameKebabCase__.ts.template +4 -2
  125. package/src/utils/ast.d.ts +10 -0
  126. package/src/utils/ast.js +33 -0
  127. package/src/utils/ast.js.map +1 -1
  128. package/src/utils/docker.d.ts +11 -0
  129. package/src/utils/docker.js +16 -3
  130. package/src/utils/docker.js.map +1 -1
  131. package/src/utils/dynamodb-config.d.ts +48 -0
  132. package/src/utils/dynamodb-config.js +44 -0
  133. package/src/utils/dynamodb-config.js.map +1 -0
  134. package/src/utils/dynamodb-constructs/files/cdk/app/dynamodb/__nameKebabCase__.ts.template +8 -4
  135. package/src/utils/git-secrets.d.ts +28 -0
  136. package/src/utils/git-secrets.js +66 -0
  137. package/src/utils/git-secrets.js.map +1 -0
  138. package/src/utils/init.js +7 -6
  139. package/src/utils/init.js.map +1 -1
  140. package/src/utils/local-project-dependency.d.ts +28 -0
  141. package/src/utils/local-project-dependency.js +40 -0
  142. package/src/utils/local-project-dependency.js.map +1 -0
  143. package/src/utils/nx.d.ts +14 -0
  144. package/src/utils/nx.js +30 -0
  145. package/src/utils/nx.js.map +1 -1
  146. package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +11 -6
  147. package/src/utils/script-project-targets.d.ts +26 -0
  148. package/src/utils/script-project-targets.js +75 -0
  149. package/src/utils/script-project-targets.js.map +1 -0
  150. package/src/utils/versions.d.ts +3 -0
  151. package/src/utils/versions.js +7 -0
  152. package/src/utils/versions.js.map +1 -1
  153. package/src/utils/website-constructs/files/cdk/app/static-websites/__websiteNameKebabCase__.ts.template +8 -4
  154. package/src/py/dynamodb/files/__name__/__init__.py.template +0 -6
  155. package/src/py/dynamodb/files/__name__/entities/__init__.py.template +0 -4
  156. package/src/py/dynamodb/files/config.json.template +0 -24
  157. package/src/ts/dynamodb/files/config.json.template +0 -24
  158. package/src/ts/dynamodb/files/src/entities/index.ts.template +0 -1
  159. package/src/ts/dynamodb/files/src/index.ts.template +0 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/agent-core-packaging.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 ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport { addArtifactDependencyToTargets } from './nx.js';\nimport {\n agentCoreRuntime,\n type ITsDepVersion,\n TS_VERSIONS,\n} from './versions.js';\n\n/**\n * How an AgentCore Runtime's artifact is packaged and hosted.\n *\n * `agentcore` uploads a `.zip` of the built code to S3 and runs it on an\n * AgentCore managed language runtime, which skips the container build, ECR\n * repository and image push entirely — the fastest build and deploy cycle.\n * `agentcore-ecr` builds an arm64 container image and hosts it from ECR, for\n * workloads needing OS-level control (e.g. native system libraries) or an\n * established container pipeline.\n */\nexport const AGENT_CORE_INFRA = ['agentcore', 'agentcore-ecr', 'none'] as const;\n\nexport type AgentCoreInfra = (typeof AGENT_CORE_INFRA)[number];\n\n/** Whether the given infra option hosts on AgentCore Runtime at all. */\nexport const isAgentCoreHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore' || infra === 'agentcore-ecr';\n\n/** Whether the given infra option builds and hosts a container image. */\nexport const isContainerHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore-ecr';\n\n/** The AgentCore managed runtime a TypeScript component targets. */\nexport const agentCoreNodeRuntime = (): string => agentCoreRuntime('node');\n\n/** The AgentCore managed runtime a Python component targets. */\nexport const agentCorePythonRuntime = (): string => agentCoreRuntime('python');\n\n/**\n * Dependencies a caller must declare to add a code (`.zip`) packaging target.\n *\n * The TypeScript package target vendors the AWS Distro for OpenTelemetry into\n * the package directory, so its version is declared here (spread through\n * `ownedElsewhere` by callers, since the install targets the built package\n * rather than any workspace manifest).\n */\nexport const CODE_PACKAGE_DEPENDENCIES = [\n ...FS_DEPENDENCIES,\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface AddTypeScriptCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the target producing the bundled `index.js`, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the bundled `index.js`, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n}\n\n/**\n * Name of the target that installs ADOT for the project's code packages. Every\n * code package in a project vendors the same thing, so they share one.\n */\nexport const ADOT_VENDOR_TARGET_NAME = 'agent-core-vendor';\n\n/**\n * Directory the vendor target installs into, relative to the workspace root.\n *\n * A sibling of the `package` directory rather than a path inside it: Nx replaces\n * a cached target's whole declared output directory on restore, so an output\n * nested inside another target's output would have each wipe the other's work on\n * a cache hit.\n */\nexport const adotVendorDir = (projectRoot: string): string =>\n joinPathFragments('dist', projectRoot, 'agent-core-vendor');\n\n/**\n * Add a target assembling the deployable code package for a TypeScript\n * AgentCore Runtime.\n *\n * The package is the rolldown bundle plus a vendored AWS Distro for\n * OpenTelemetry install. AgentCore rejects a code package with no OpenTelemetry\n * dependencies present (`CREATE_FAILED`: \"OpenTelemetry instrumentation\n * executable not found\"), so ADOT must ship inside the package rather than\n * being installed at runtime — the `opentelemetry-instrument` entry point\n * prefix resolves it from there.\n *\n * The install is scoped to its own directory with `--prefix` and `--no-save`, so\n * it never touches the project's own manifest. `--omit=dev` keeps the package to\n * what the runtime loads. ADOT is pure JavaScript with no native binaries, so the\n * package is architecture independent even though AgentCore only runs arm64.\n *\n * Vendoring is a separate target so Nx caches it independently of the source. It\n * depends on nothing but the pinned ADOT version, which the install command\n * carries and Nx hashes as part of the target's configuration, so it declares no\n * `inputs`: editing an agent replays a directory copy rather than re-running the\n * install, while bumping the version still invalidates it.\n */\nexport const addTypeScriptCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddTypeScriptCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof CODE_PACKAGE_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n const adotVersion =\n TS_VERSIONS['@aws/aws-distro-opentelemetry-node-autoinstrumentation'];\n const vendorDir = adotVendorDir(project.root);\n\n project.targets ??= {};\n\n // Shared by every code package in the project, so only added once.\n project.targets[ADOT_VENDOR_TARGET_NAME] ??= {\n cache: true,\n inputs: [],\n outputs: [`{workspaceRoot}/${vendorDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(vendorDir),\n fs.mkdir(vendorDir),\n `npm install --prefix ${vendorDir} --no-save --no-audit --no-fund --omit=dev @aws/aws-distro-opentelemetry-node-autoinstrumentation@${adotVersion}`,\n ],\n parallel: false,\n },\n };\n\n project.targets[targetName] = {\n cache: true,\n inputs: ['default'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cpFile(\n joinPathFragments(bundleOutputDir, 'index.js'),\n joinPathFragments(packageOutputDir, 'index.js'),\n ),\n fs.cpDir(\n joinPathFragments(vendorDir, 'node_modules'),\n joinPathFragments(packageOutputDir, 'node_modules'),\n ),\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName, ADOT_VENDOR_TARGET_NAME],\n };\n\n addArtifactDependencyToTargets(project, targetName);\n};\n\nexport interface AddPythonCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the arm64 bundle target, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the arm64 bundle, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n /**\n * The project's source root, which is itself the module directory — copied\n * into the package under {@link moduleName} so the agent's imports resolve.\n */\n readonly sourceRoot: string;\n /** Python module name (the source root's last segment). */\n readonly moduleName: string;\n /**\n * Path to the entry point script, relative to the workspace root. Copied to\n * the package root, where AgentCore's `entryPoint` runs it.\n */\n readonly entryPointPath: string;\n /** File name the entry point takes at the package root. */\n readonly entryPointFileName: string;\n}\n\n/**\n * Add a target assembling the deployable code package for a Python AgentCore\n * Runtime.\n *\n * The package is the arm64 dependency bundle (which already carries\n * `bin/opentelemetry-instrument` from `aws-opentelemetry-distro`, satisfying\n * AgentCore's requirement that OpenTelemetry be present), the project's own\n * module tree, and a root entry point script.\n *\n * The entry point sits at the package root rather than inside the module:\n * AgentCore runs it as a file path, and Python gives a script run that way no\n * parent package, so the agent's own `from .init import app` would raise\n * `ImportError: attempted relative import with no known parent package`. The\n * root script imports the module absolutely instead, which resolves those\n * relative imports normally.\n */\nexport const addPythonCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddPythonCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof FS_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n sourceRoot,\n moduleName,\n entryPointPath,\n entryPointFileName,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n\n project.targets ??= {};\n project.targets[targetName] = {\n cache: true,\n inputs: ['production', '^production'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cpDir(bundleOutputDir, packageOutputDir),\n fs.cpDir(sourceRoot, joinPathFragments(packageOutputDir, moduleName)),\n fs.cpFile(\n entryPointPath,\n joinPathFragments(packageOutputDir, entryPointFileName),\n ),\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName],\n };\n\n addArtifactDependencyToTargets(project, targetName);\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addArtifactDependencyToTargets","agentCoreRuntime","TS_VERSIONS","AGENT_CORE_INFRA","isAgentCoreHosted","infra","isContainerHosted","agentCoreNodeRuntime","agentCorePythonRuntime","CODE_PACKAGE_DEPENDENCIES","name","ADOT_VENDOR_TARGET_NAME","adotVendorDir","projectRoot","addTypeScriptCodePackageTarget","tree","options","declaration","project","targetName","bundleTargetName","bundleOutputDir","packageOutputDir","fs","adotVersion","vendorDir","root","targets","cache","inputs","outputs","executor","commands","rm","mkdir","parallel","cpFile","cpDir","dependsOn","addPythonCodePackageTarget","sourceRoot","moduleName","entryPointPath","entryPointFileName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/agent-core-packaging.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 ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport {\n addArtifactDependencyToTargets,\n normalizeTargetKeyOrder,\n} from './nx.js';\nimport {\n agentCoreRuntime,\n type ITsDepVersion,\n TS_VERSIONS,\n} from './versions.js';\n\n/**\n * How an AgentCore Runtime's artifact is packaged and hosted.\n *\n * `agentcore` uploads a `.zip` of the built code to S3 and runs it on an\n * AgentCore managed language runtime, which skips the container build, ECR\n * repository and image push entirely — the fastest build and deploy cycle.\n * `agentcore-ecr` builds an arm64 container image and hosts it from ECR, for\n * workloads needing OS-level control (e.g. native system libraries) or an\n * established container pipeline.\n */\nexport const AGENT_CORE_INFRA = ['agentcore', 'agentcore-ecr', 'none'] as const;\n\nexport type AgentCoreInfra = (typeof AGENT_CORE_INFRA)[number];\n\n/** Whether the given infra option hosts on AgentCore Runtime at all. */\nexport const isAgentCoreHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore' || infra === 'agentcore-ecr';\n\n/** Whether the given infra option builds and hosts a container image. */\nexport const isContainerHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore-ecr';\n\n/** The AgentCore managed runtime a TypeScript component targets. */\nexport const agentCoreNodeRuntime = (): string => agentCoreRuntime('node');\n\n/** The AgentCore managed runtime a Python component targets. */\nexport const agentCorePythonRuntime = (): string => agentCoreRuntime('python');\n\n/**\n * Dependencies a caller must declare to add a code (`.zip`) packaging target.\n *\n * The TypeScript package target vendors the AWS Distro for OpenTelemetry into\n * the package directory, so its version is declared here (spread through\n * `ownedElsewhere` by callers, since the install targets the built package\n * rather than any workspace manifest).\n */\nexport const CODE_PACKAGE_DEPENDENCIES = [\n ...FS_DEPENDENCIES,\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface AddTypeScriptCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the target producing the bundled `index.js`, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the bundled `index.js`, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n}\n\n/**\n * Name of the target that installs ADOT for the project's code packages. Every\n * code package in a project vendors the same thing, so they share one.\n */\nexport const ADOT_VENDOR_TARGET_NAME = 'agent-core-vendor';\n\n/**\n * Directory the vendor target installs into, relative to the workspace root.\n *\n * A sibling of the `package` directory rather than a path inside it: Nx replaces\n * a cached target's whole declared output directory on restore, so an output\n * nested inside another target's output would have each wipe the other's work on\n * a cache hit.\n */\nexport const adotVendorDir = (projectRoot: string): string =>\n joinPathFragments('dist', projectRoot, 'agent-core-vendor');\n\n/**\n * Add a target assembling the deployable code package for a TypeScript\n * AgentCore Runtime.\n *\n * The package is the rolldown bundle plus a vendored AWS Distro for\n * OpenTelemetry install. AgentCore rejects a code package with no OpenTelemetry\n * dependencies present (`CREATE_FAILED`: \"OpenTelemetry instrumentation\n * executable not found\"), so ADOT must ship inside the package rather than\n * being installed at runtime — the `opentelemetry-instrument` entry point\n * prefix resolves it from there.\n *\n * The install is scoped to its own directory with `--prefix` and `--no-save`, so\n * it never touches the project's own manifest. `--omit=dev` keeps the package to\n * what the runtime loads. ADOT is pure JavaScript with no native binaries, so the\n * package is architecture independent even though AgentCore only runs arm64.\n *\n * Vendoring is a separate target so Nx caches it independently of the source. It\n * depends on nothing but the pinned ADOT version, which the install command\n * carries and Nx hashes as part of the target's configuration, so it declares no\n * `inputs`: editing an agent replays a directory copy rather than re-running the\n * install, while bumping the version still invalidates it.\n */\nexport const addTypeScriptCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddTypeScriptCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof CODE_PACKAGE_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n const adotVersion =\n TS_VERSIONS['@aws/aws-distro-opentelemetry-node-autoinstrumentation'];\n const vendorDir = adotVendorDir(project.root);\n\n project.targets ??= {};\n\n // Shared by every code package in the project, so only added once.\n project.targets[ADOT_VENDOR_TARGET_NAME] ??= {\n cache: true,\n inputs: [],\n outputs: [`{workspaceRoot}/${vendorDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(vendorDir),\n fs.mkdir(vendorDir),\n `npm install --prefix ${vendorDir} --no-save --no-audit --no-fund --omit=dev @aws/aws-distro-opentelemetry-node-autoinstrumentation@${adotVersion}`,\n ],\n parallel: false,\n },\n };\n\n project.targets[targetName] = normalizeTargetKeyOrder({\n cache: true,\n inputs: ['default'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cpFile(\n joinPathFragments(bundleOutputDir, 'index.js'),\n joinPathFragments(packageOutputDir, 'index.js'),\n ),\n fs.cpDir(\n joinPathFragments(vendorDir, 'node_modules'),\n joinPathFragments(packageOutputDir, 'node_modules'),\n ),\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName, ADOT_VENDOR_TARGET_NAME],\n });\n\n addArtifactDependencyToTargets(project, targetName);\n};\n\nexport interface AddPythonCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the arm64 bundle target, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the arm64 bundle, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n /**\n * The project's source root, which is itself the module directory — copied\n * into the package under {@link moduleName} so the agent's imports resolve.\n */\n readonly sourceRoot: string;\n /** Python module name (the source root's last segment). */\n readonly moduleName: string;\n /**\n * Path to the entry point script, relative to the workspace root. Copied to\n * the package root, where AgentCore's `entryPoint` runs it.\n */\n readonly entryPointPath: string;\n /** File name the entry point takes at the package root. */\n readonly entryPointFileName: string;\n}\n\n/**\n * Add a target assembling the deployable code package for a Python AgentCore\n * Runtime.\n *\n * The package is the arm64 dependency bundle (which already carries\n * `bin/opentelemetry-instrument` from `aws-opentelemetry-distro`, satisfying\n * AgentCore's requirement that OpenTelemetry be present), the project's own\n * module tree, and a root entry point script.\n *\n * The entry point sits at the package root rather than inside the module:\n * AgentCore runs it as a file path, and Python gives a script run that way no\n * parent package, so the agent's own `from .init import app` would raise\n * `ImportError: attempted relative import with no known parent package`. The\n * root script imports the module absolutely instead, which resolves those\n * relative imports normally.\n */\nexport const addPythonCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddPythonCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof FS_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n sourceRoot,\n moduleName,\n entryPointPath,\n entryPointFileName,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n\n project.targets ??= {};\n project.targets[targetName] = normalizeTargetKeyOrder({\n cache: true,\n inputs: ['production', '^production'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cpDir(bundleOutputDir, packageOutputDir),\n fs.cpDir(sourceRoot, joinPathFragments(packageOutputDir, moduleName)),\n fs.cpFile(\n entryPointPath,\n joinPathFragments(packageOutputDir, entryPointFileName),\n ),\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName],\n });\n\n addArtifactDependencyToTargets(project, targetName);\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addArtifactDependencyToTargets","normalizeTargetKeyOrder","agentCoreRuntime","TS_VERSIONS","AGENT_CORE_INFRA","isAgentCoreHosted","infra","isContainerHosted","agentCoreNodeRuntime","agentCorePythonRuntime","CODE_PACKAGE_DEPENDENCIES","name","ADOT_VENDOR_TARGET_NAME","adotVendorDir","projectRoot","addTypeScriptCodePackageTarget","tree","options","declaration","project","targetName","bundleTargetName","bundleOutputDir","packageOutputDir","fs","adotVersion","vendorDir","root","targets","cache","inputs","outputs","executor","commands","rm","mkdir","parallel","cpFile","cpDir","dependsOn","addPythonCodePackageTarget","sourceRoot","moduleName","entryPointPath","entryPointFileName"],"mappings":"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"}
@@ -39,6 +39,11 @@ interface BackendOptions {
39
39
  export interface TrpcBackendOptions extends BackendOptions {
40
40
  type: 'trpc';
41
41
  projectAlias: string;
42
+ /**
43
+ * Root of the tRPC project. The generated construct imports its router, so
44
+ * shared constructs declares a dependency and project reference on it.
45
+ */
46
+ projectRoot: string;
42
47
  bundleOutputDir: string;
43
48
  authorizerBundleOutputDir?: string;
44
49
  }
@@ -5,6 +5,7 @@
5
5
  import { addStarExport } from "../ast.js";
6
6
  import { forDependencies } from "../declared-dependencies.js";
7
7
  import { addDependenciesToPackageJson } from "../dependencies.js";
8
+ import { addLocalProjectDependency } from "../local-project-dependency.js";
8
9
  import { esmVars } from "../module-format.js";
9
10
  import { addArtifactProjectToTargets } from "../nx.js";
10
11
  import { generatedInfrastructure, generatedTerraform, PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../shared-constructs-constants.js";
@@ -100,6 +101,13 @@ export const addApiGatewayInfra = async (tree, options, declaration)=>{
100
101
  }, {
101
102
  overwriteStrategy: OverwriteStrategy.KeepExisting
102
103
  });
104
+ // The generated tRPC construct imports the API project's router.
105
+ if (options.backend.type === 'trpc') {
106
+ addLocalProjectDependency(tree, {
107
+ consumerRoot: joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR),
108
+ dependencyRoot: options.backend.projectRoot
109
+ });
110
+ }
103
111
  // Export app specific CDK construct
104
112
  await addStarExport(tree, joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'app', 'apis', 'index.ts'), `./${options.apiNameKebabCase}.js`);
105
113
  await addStarExport(tree, joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'app', 'index.ts'), './apis/index.js');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/api-constructs/api-constructs.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 joinPathFragments,\n OverwriteStrategy,\n type ProjectConfiguration,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport { addStarExport } from '../ast.js';\nimport {\n type DeclaredPyDependency,\n type DeclaredTsDependency,\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from '../declared-dependencies.js';\nimport { addDependenciesToPackageJson } from '../dependencies.js';\nimport type { Iac } from '../iac.js';\nimport { esmVars } from '../module-format.js';\nimport { addArtifactProjectToTargets } from '../nx.js';\nimport {\n generatedInfrastructure,\n generatedTerraform,\n type IacMetadata,\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport {\n cdkLambdaRuntimeVars,\n type IPyDepVersion,\n type ITsDepVersion,\n PY_VERSIONS,\n terraformLambdaRuntimeVars,\n terraformProviderVersions,\n withVersions,\n} from '../versions.js';\n\n/**\n * Dependencies a caller must declare to add API Gateway infrastructure.\n *\n * Gated on infrastructure having been generated: `addApiGatewayInfra` only runs\n * on that branch, so a project generated with `infra: 'none'` never receives\n * these.\n */\nexport const API_CONSTRUCTS_DEPENDENCIES = [\n { name: '@aws-sdk/client-api-gateway', when: generatedInfrastructure },\n { name: '@aws-sdk/client-iam', when: generatedInfrastructure },\n { name: '@trpc/server', when: generatedInfrastructure },\n] as const satisfies readonly DeclaredTsDependency<\n ITsDepVersion,\n IacMetadata\n>[];\n\n/**\n * Python version the generated Terraform pins in the account module's inline\n * `uv run --with` script. Nothing installs it, so it is declared for its version\n * alone, and only on the Terraform branch that writes the script.\n */\nexport const API_CONSTRUCTS_PY_DEPENDENCIES = [\n { name: 'boto3', when: generatedTerraform },\n] as const satisfies readonly DeclaredPyDependency<\n IPyDepVersion,\n IacMetadata\n>[];\n\n/**\n * Path segments a REST API operation may have in the generated Terraform.\n *\n * API Gateway REST APIs need a resource per path segment, and instances of a\n * Terraform resource cannot reference one another\n * (https://github.com/hashicorp/terraform/issues/26697), so the module declares\n * one resource per level up to this depth. Deeper paths fail the plan with a\n * message pointing at the fix. Empty levels create no resources, so the only\n * cost of the headroom is the generated configuration itself.\n */\nconst MAX_REST_PATH_DEPTH = 16;\n\ninterface BackendOptions {\n type: 'trpc' | 'fastapi' | 'smithy';\n integrationPattern: 'isolated' | 'shared';\n}\n\nexport interface TrpcBackendOptions extends BackendOptions {\n type: 'trpc';\n projectAlias: string;\n bundleOutputDir: string;\n authorizerBundleOutputDir?: string;\n}\n\nexport interface FastApiBackendOptions extends BackendOptions {\n type: 'fastapi';\n moduleName: string;\n bundleOutputDir: string;\n}\n\nexport interface SmithyBackendOptions extends BackendOptions {\n type: 'smithy';\n bundleOutputDir: string;\n authorizerBundleOutputDir?: string;\n}\n\nexport interface AddApiGatewayConstructOptions {\n apiProjectName: string;\n apiNameClassName: string;\n apiNameKebabCase: string;\n constructType: 'http' | 'rest';\n backend: TrpcBackendOptions | FastApiBackendOptions | SmithyBackendOptions;\n auth: 'iam' | 'cognito' | 'custom';\n}\n\nexport const addApiGatewayInfra = async <const D extends DependencyDeclaration>(\n tree: Tree,\n options: AddApiGatewayConstructOptions & { iac: Iac },\n declaration: D & MustDeclare<typeof API_CONSTRUCTS_DEPENDENCIES, D>,\n) => {\n if (options.iac === 'cdk') {\n await addApiGatewayCdkConstructs(tree, options, declaration);\n } else if (options.iac === 'terraform') {\n addApiGatewayTerraformModules(tree, options);\n } else {\n throw new Error(`Unsupported iac ${options.iac}`);\n }\n\n updateJson(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n options.iac === 'cdk' ? SHARED_CONSTRUCTS_DIR : SHARED_TERRAFORM_DIR,\n 'project.json',\n ),\n (config: ProjectConfiguration) => {\n addArtifactProjectToTargets(config, options.apiProjectName);\n return config;\n },\n );\n};\n\n/**\n * Add an API CDK construct, and update the Runtime Config type to export its url\n */\nconst addApiGatewayCdkConstructs = async (\n tree: Tree,\n options: AddApiGatewayConstructOptions,\n declaration: DependencyDeclaration,\n) => {\n const generateCoreApiFile = (name: string) => {\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'core',\n 'api',\n name,\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'api',\n ),\n { ...esmVars(tree), ...cdkLambdaRuntimeVars() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n };\n\n // Generate relevant core CDK construct and utilities\n generateCoreApiFile(options.constructType);\n generateCoreApiFile('utils');\n if (options.backend.type === 'trpc') {\n generateCoreApiFile('trpc');\n }\n\n // Declare the deps the generated core construct files import.\n const constructDeps: (typeof API_CONSTRUCTS_DEPENDENCIES)[number]['name'][] =\n [];\n if (options.constructType === 'rest') {\n // REST account construct configures the account via the AWS SDK.\n constructDeps.push('@aws-sdk/client-api-gateway', '@aws-sdk/client-iam');\n }\n if (options.backend.type === 'trpc') {\n // trpc-utils.ts types the router with @trpc/server.\n constructDeps.push('@trpc/server');\n }\n if (constructDeps.length > 0) {\n addDependenciesToPackageJson(\n tree,\n withVersions(\n forDependencies<typeof API_CONSTRUCTS_DEPENDENCIES>(declaration),\n constructDeps,\n ),\n {},\n joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'package.json'),\n );\n }\n\n // Generate app specific CDK construct\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'app',\n 'apis',\n options.constructType,\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'apis',\n ),\n { ...options, ...esmVars(tree), ...cdkLambdaRuntimeVars() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Export app specific CDK construct\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'apis',\n 'index.ts',\n ),\n `./${options.apiNameKebabCase}.js`,\n );\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'index.ts',\n ),\n './apis/index.js',\n );\n};\n\n/**\n * Add an API terraform module, and update the Runtime Config type to export its url\n */\nconst addApiGatewayTerraformModules = (\n tree: Tree,\n options: AddApiGatewayConstructOptions,\n) => {\n // Generate core terraform module\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'core',\n 'api',\n options.constructType,\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'api'),\n { boto3Version: PY_VERSIONS.boto3, ...terraformProviderVersions() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Generate app specific terraform module\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'app',\n 'apis',\n options.constructType,\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'app', 'apis'),\n {\n ...options,\n maxRestPathDepth: MAX_REST_PATH_DEPTH,\n ...terraformProviderVersions(),\n ...terraformLambdaRuntimeVars(),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","updateJson","addStarExport","forDependencies","addDependenciesToPackageJson","esmVars","addArtifactProjectToTargets","generatedInfrastructure","generatedTerraform","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","cdkLambdaRuntimeVars","PY_VERSIONS","terraformLambdaRuntimeVars","terraformProviderVersions","withVersions","API_CONSTRUCTS_DEPENDENCIES","name","when","API_CONSTRUCTS_PY_DEPENDENCIES","MAX_REST_PATH_DEPTH","addApiGatewayInfra","tree","options","declaration","iac","addApiGatewayCdkConstructs","addApiGatewayTerraformModules","Error","config","apiProjectName","generateCoreApiFile","dirname","overwriteStrategy","KeepExisting","constructType","backend","type","constructDeps","push","length","apiNameKebabCase","boto3Version","boto3","maxRestPathDepth"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/api-constructs/api-constructs.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 joinPathFragments,\n OverwriteStrategy,\n type ProjectConfiguration,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport { addStarExport } from '../ast.js';\nimport {\n type DeclaredPyDependency,\n type DeclaredTsDependency,\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from '../declared-dependencies.js';\nimport { addDependenciesToPackageJson } from '../dependencies.js';\nimport type { Iac } from '../iac.js';\nimport { addLocalProjectDependency } from '../local-project-dependency.js';\nimport { esmVars } from '../module-format.js';\nimport { addArtifactProjectToTargets } from '../nx.js';\nimport {\n generatedInfrastructure,\n generatedTerraform,\n type IacMetadata,\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport {\n cdkLambdaRuntimeVars,\n type IPyDepVersion,\n type ITsDepVersion,\n PY_VERSIONS,\n terraformLambdaRuntimeVars,\n terraformProviderVersions,\n withVersions,\n} from '../versions.js';\n\n/**\n * Dependencies a caller must declare to add API Gateway infrastructure.\n *\n * Gated on infrastructure having been generated: `addApiGatewayInfra` only runs\n * on that branch, so a project generated with `infra: 'none'` never receives\n * these.\n */\nexport const API_CONSTRUCTS_DEPENDENCIES = [\n { name: '@aws-sdk/client-api-gateway', when: generatedInfrastructure },\n { name: '@aws-sdk/client-iam', when: generatedInfrastructure },\n { name: '@trpc/server', when: generatedInfrastructure },\n] as const satisfies readonly DeclaredTsDependency<\n ITsDepVersion,\n IacMetadata\n>[];\n\n/**\n * Python version the generated Terraform pins in the account module's inline\n * `uv run --with` script. Nothing installs it, so it is declared for its version\n * alone, and only on the Terraform branch that writes the script.\n */\nexport const API_CONSTRUCTS_PY_DEPENDENCIES = [\n { name: 'boto3', when: generatedTerraform },\n] as const satisfies readonly DeclaredPyDependency<\n IPyDepVersion,\n IacMetadata\n>[];\n\n/**\n * Path segments a REST API operation may have in the generated Terraform.\n *\n * API Gateway REST APIs need a resource per path segment, and instances of a\n * Terraform resource cannot reference one another\n * (https://github.com/hashicorp/terraform/issues/26697), so the module declares\n * one resource per level up to this depth. Deeper paths fail the plan with a\n * message pointing at the fix. Empty levels create no resources, so the only\n * cost of the headroom is the generated configuration itself.\n */\nconst MAX_REST_PATH_DEPTH = 16;\n\ninterface BackendOptions {\n type: 'trpc' | 'fastapi' | 'smithy';\n integrationPattern: 'isolated' | 'shared';\n}\n\nexport interface TrpcBackendOptions extends BackendOptions {\n type: 'trpc';\n projectAlias: string;\n /**\n * Root of the tRPC project. The generated construct imports its router, so\n * shared constructs declares a dependency and project reference on it.\n */\n projectRoot: string;\n bundleOutputDir: string;\n authorizerBundleOutputDir?: string;\n}\n\nexport interface FastApiBackendOptions extends BackendOptions {\n type: 'fastapi';\n moduleName: string;\n bundleOutputDir: string;\n}\n\nexport interface SmithyBackendOptions extends BackendOptions {\n type: 'smithy';\n bundleOutputDir: string;\n authorizerBundleOutputDir?: string;\n}\n\nexport interface AddApiGatewayConstructOptions {\n apiProjectName: string;\n apiNameClassName: string;\n apiNameKebabCase: string;\n constructType: 'http' | 'rest';\n backend: TrpcBackendOptions | FastApiBackendOptions | SmithyBackendOptions;\n auth: 'iam' | 'cognito' | 'custom';\n}\n\nexport const addApiGatewayInfra = async <const D extends DependencyDeclaration>(\n tree: Tree,\n options: AddApiGatewayConstructOptions & { iac: Iac },\n declaration: D & MustDeclare<typeof API_CONSTRUCTS_DEPENDENCIES, D>,\n) => {\n if (options.iac === 'cdk') {\n await addApiGatewayCdkConstructs(tree, options, declaration);\n } else if (options.iac === 'terraform') {\n addApiGatewayTerraformModules(tree, options);\n } else {\n throw new Error(`Unsupported iac ${options.iac}`);\n }\n\n updateJson(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n options.iac === 'cdk' ? SHARED_CONSTRUCTS_DIR : SHARED_TERRAFORM_DIR,\n 'project.json',\n ),\n (config: ProjectConfiguration) => {\n addArtifactProjectToTargets(config, options.apiProjectName);\n return config;\n },\n );\n};\n\n/**\n * Add an API CDK construct, and update the Runtime Config type to export its url\n */\nconst addApiGatewayCdkConstructs = async (\n tree: Tree,\n options: AddApiGatewayConstructOptions,\n declaration: DependencyDeclaration,\n) => {\n const generateCoreApiFile = (name: string) => {\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'core',\n 'api',\n name,\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'api',\n ),\n { ...esmVars(tree), ...cdkLambdaRuntimeVars() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n };\n\n // Generate relevant core CDK construct and utilities\n generateCoreApiFile(options.constructType);\n generateCoreApiFile('utils');\n if (options.backend.type === 'trpc') {\n generateCoreApiFile('trpc');\n }\n\n // Declare the deps the generated core construct files import.\n const constructDeps: (typeof API_CONSTRUCTS_DEPENDENCIES)[number]['name'][] =\n [];\n if (options.constructType === 'rest') {\n // REST account construct configures the account via the AWS SDK.\n constructDeps.push('@aws-sdk/client-api-gateway', '@aws-sdk/client-iam');\n }\n if (options.backend.type === 'trpc') {\n // trpc-utils.ts types the router with @trpc/server.\n constructDeps.push('@trpc/server');\n }\n if (constructDeps.length > 0) {\n addDependenciesToPackageJson(\n tree,\n withVersions(\n forDependencies<typeof API_CONSTRUCTS_DEPENDENCIES>(declaration),\n constructDeps,\n ),\n {},\n joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'package.json'),\n );\n }\n\n // Generate app specific CDK construct\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'app',\n 'apis',\n options.constructType,\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'apis',\n ),\n { ...options, ...esmVars(tree), ...cdkLambdaRuntimeVars() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // The generated tRPC construct imports the API project's router.\n if (options.backend.type === 'trpc') {\n addLocalProjectDependency(tree, {\n consumerRoot: joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR),\n dependencyRoot: options.backend.projectRoot,\n });\n }\n\n // Export app specific CDK construct\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'apis',\n 'index.ts',\n ),\n `./${options.apiNameKebabCase}.js`,\n );\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'index.ts',\n ),\n './apis/index.js',\n );\n};\n\n/**\n * Add an API terraform module, and update the Runtime Config type to export its url\n */\nconst addApiGatewayTerraformModules = (\n tree: Tree,\n options: AddApiGatewayConstructOptions,\n) => {\n // Generate core terraform module\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'core',\n 'api',\n options.constructType,\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'api'),\n { boto3Version: PY_VERSIONS.boto3, ...terraformProviderVersions() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Generate app specific terraform module\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'app',\n 'apis',\n options.constructType,\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'app', 'apis'),\n {\n ...options,\n maxRestPathDepth: MAX_REST_PATH_DEPTH,\n ...terraformProviderVersions(),\n ...terraformLambdaRuntimeVars(),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","updateJson","addStarExport","forDependencies","addDependenciesToPackageJson","addLocalProjectDependency","esmVars","addArtifactProjectToTargets","generatedInfrastructure","generatedTerraform","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","cdkLambdaRuntimeVars","PY_VERSIONS","terraformLambdaRuntimeVars","terraformProviderVersions","withVersions","API_CONSTRUCTS_DEPENDENCIES","name","when","API_CONSTRUCTS_PY_DEPENDENCIES","MAX_REST_PATH_DEPTH","addApiGatewayInfra","tree","options","declaration","iac","addApiGatewayCdkConstructs","addApiGatewayTerraformModules","Error","config","apiProjectName","generateCoreApiFile","dirname","overwriteStrategy","KeepExisting","constructType","backend","type","constructDeps","push","length","consumerRoot","dependencyRoot","projectRoot","apiNameKebabCase","boto3Version","boto3","maxRestPathDepth"],"mappings":"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"}
@@ -43,7 +43,9 @@ import {
43
43
  IntegrationBuilder,
44
44
  } from '../../core/api/utils<% if (esm) { %>.js<% } %>';
45
45
  import { findCloudFrontDomainNames } from '../../core/cloudfront<% if (esm) { %>.js<% } %>';
46
- import { HttpApi } from '../../core/api/http-api<% if (esm) { %>.js<% } %>';
46
+ <%_ /* Alias the core class only when this API's name would shadow it. */ _%>
47
+ <%_ const coreHttpApi = apiNameClassName === 'HttpApi' ? 'CoreHttpApi' : 'HttpApi'; _%>
48
+ import { HttpApi<% if (coreHttpApi !== 'HttpApi') { %> as <%= coreHttpApi %><% } %> } from '../../core/api/http-api<% if (esm) { %>.js<% } %>';
47
49
  <%_ if (backend.type === 'trpc') { _%>
48
50
  import { Procedures, routerToOperations } from '../../core/api/trpc-utils<% if (esm) { %>.js<% } %>';
49
51
  import { AppRouter, appRouter } from '<%= backend.projectAlias %>';
@@ -87,7 +89,7 @@ export interface <%= apiNameClassName %>Props<
87
89
  */
88
90
  export class <%= apiNameClassName %><
89
91
  TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
90
- > extends HttpApi<Operations, TIntegrations> {
92
+ > extends <%= coreHttpApi %><Operations, TIntegrations> {
91
93
  /**
92
94
  <%_ if (backend.integrationPattern === 'shared') { _%>
93
95
  * Creates default integrations for all operations using a single shared
@@ -46,7 +46,9 @@ import {
46
46
  RestApiIntegration,
47
47
  } from '../../core/api/utils<% if (esm) { %>.js<% } %>';
48
48
  import { findCloudFrontDomainNames } from '../../core/cloudfront<% if (esm) { %>.js<% } %>';
49
- import { AddCorsPreflightAspect, RestApi } from '../../core/api/rest-api<% if (esm) { %>.js<% } %>';
49
+ <%_ /* Alias the core class only when this API's name would shadow it. */ _%>
50
+ <%_ const coreRestApi = apiNameClassName === 'RestApi' ? 'CoreRestApi' : 'RestApi'; _%>
51
+ import { AddCorsPreflightAspect, RestApi<% if (coreRestApi !== 'RestApi') { %> as <%= coreRestApi %><% } %> } from '../../core/api/rest-api<% if (esm) { %>.js<% } %>';
50
52
  <%_ if (backend.type === 'trpc') { _%>
51
53
  import { Procedures, routerToOperations } from '../../core/api/trpc-utils<% if (esm) { %>.js<% } %>';
52
54
  import { AppRouter, appRouter } from '<%= backend.projectAlias %>';
@@ -95,7 +97,7 @@ export interface <%= apiNameClassName %>Props<
95
97
  */
96
98
  export class <%= apiNameClassName %><
97
99
  TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
98
- > extends RestApi<Operations, TIntegrations> {
100
+ > extends <%= coreRestApi %><Operations, TIntegrations> {
99
101
  private allowedOrigins: readonly string[] = ['*'];
100
102
 
101
103
  /**
@@ -28,6 +28,16 @@ export declare const addSingleImport: (tree: Tree, filePath: string, variableNam
28
28
  * Note that this will create the file if it does not exist in the tree.
29
29
  */
30
30
  export declare const addStarExport: (tree: Tree, filePath: string, from: string) => Promise<void>;
31
+ /**
32
+ * Adds an `export { <names> } from '<from>';` statement to the given TypeScript
33
+ * file, merging into an existing re-export of the same module when there is one.
34
+ * Note that this will create the file if it does not exist in the tree.
35
+ *
36
+ * Adding to whatever is already there is what lets a barrel the user curates —
37
+ * their own entities, models or components — survive a generator re-run while
38
+ * the generated entry is still registered.
39
+ */
40
+ export declare const addNamedExport: (tree: Tree, filePath: string, names: string[], from: string) => Promise<void>;
31
41
  /**
32
42
  * Return whether or not the given identifier is exported in the source file.
33
43
  * Checks for both `export { Identifier }` and `export type Identifier = ...`.
package/src/utils/ast.js CHANGED
@@ -138,6 +138,39 @@ export const addDestructuredImport = async (tree, filePath, variableNames, from)
138
138
  // Prepend new export to file
139
139
  tree.write(filePath, `export * from '${from}';\n${contents}`);
140
140
  };
141
+ /**
142
+ * Adds an `export { <names> } from '<from>';` statement to the given TypeScript
143
+ * file, merging into an existing re-export of the same module when there is one.
144
+ * Note that this will create the file if it does not exist in the tree.
145
+ *
146
+ * Adding to whatever is already there is what lets a barrel the user curates —
147
+ * their own entities, models or components — survive a generator re-run while
148
+ * the generated entry is still registered.
149
+ */ export const addNamedExport = async (tree, filePath, names, from)=>{
150
+ from = normalizeModuleSpecifier(tree, from);
151
+ const contents = tree.read(filePath)?.toString() ?? '';
152
+ if (!contents.trim()) {
153
+ tree.write(filePath, `export { ${names.join(', ')} } from '${from}';\n`);
154
+ return;
155
+ }
156
+ const hasExistingExport = await matchGritQL(tree, filePath, `\`export { $_ } from '${from}'\``);
157
+ if (hasExistingExport) {
158
+ for (const name of names){
159
+ await applyGritQL(tree, filePath, `\`export { $names } from '${from}'\` => \`export { $names, ${name} } from '${from}'\` where { $names <: not contains \`${name}\` }`);
160
+ }
161
+ return;
162
+ }
163
+ const missing = [];
164
+ for (const name of names){
165
+ if (!await matchGritQL(tree, filePath, `\`${name}\``)) {
166
+ missing.push(name);
167
+ }
168
+ }
169
+ if (missing.length === 0) {
170
+ return;
171
+ }
172
+ tree.write(filePath, `export { ${missing.join(', ')} } from '${from}';\n${contents}`);
173
+ };
141
174
  /**
142
175
  * Return whether or not the given identifier is exported in the source file.
143
176
  * Checks for both `export { Identifier }` and `export type Identifier = ...`.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git.js';\nimport { isEsmWorkspace } from './module-format.js';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // The same identifier bound to a different module would be a duplicate\n // declaration, so refuse rather than emit a file that cannot compile.\n const identifierTaken = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from $module\\``,\n );\n if (identifierTaken) {\n throw new Error(\n `Cannot import ${variableName} from '${from}' in ${filePath}: ${variableName} is already imported from a different module. Rename the project so its generated identifier does not collide.`,\n );\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`\n * for `$mod`) instead of the whole matched node.\n */\nexport const captureGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.var(name) ?? undefined;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like {@link captureAllGritQL}, but returns one metavariable's binding for every\n * match rather than the whole matched node.\n *\n * This is what lets a caller work on the value a pattern isolated — matching\n * `` `runtime: $value` `` and reading `$value` gives `Runtime.NODEJS_24_X` alone,\n * so nothing downstream has to re-derive the assignment around it.\n *\n * A match whose metavariable is unbound contributes nothing, so the result holds\n * only real bindings. Returns undefined if the pattern could not be applied,\n * which an empty array cannot express.\n */\nexport const captureAllGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string[] | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n const bound = node.var(name);\n if (bound !== null && bound !== undefined) {\n captured.push(bound);\n }\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n\n/**\n * Append `entry` to an array, skipping arrays that already contain it, e.g.\n * `` appendToArrayViaGritQL(tree, file, 'tools:', 'myTool', { scope: 'new Agent($_)' }) ``\n * turns `tools: [a, b]` into `tools: [a, b, myTool]`.\n *\n * `arrayPrefix` is whatever precedes the array literal, including its separator —\n * `'tools:'` for an object property, `'tools='` for a keyword argument or\n * assignment, `'\"tools\":'` for a quoted key. Which separator applies depends on\n * the construct rather than the language, so the caller states it.\n *\n * Appends after the last *element* rather than rewriting the element list with\n * `=>`. GritQL binds a `[$items]` list to its trailing comma too, so\n * `` `[$items]` => `[$items, entry]` `` emits the sparse array `[a, b, , entry]`\n * once the formatter has wrapped the array — a hole that is `undefined` at\n * runtime. (Accumulating with `$items += \", entry\"` is safe by comparison, since\n * it appends within the list rather than re-emitting it; both forms appear in\n * this repo.)\n *\n * `scope` limits the match to an enclosing pattern, so same-named arrays\n * elsewhere in the file are left alone. `skipIfContains` overrides what the\n * append dedupes on, for when the rendered entry is not the identifier to match.\n *\n * The caller formats afterwards. Returns true if the entry was appended.\n */\nexport const appendToArrayViaGritQL = async (\n tree: Tree,\n filePath: string,\n arrayPrefix: string,\n entry: string,\n options: { scope?: string; skipIfContains?: string; language?: 'py' } = {},\n): Promise<boolean> => {\n const { scope, skipIfContains, language } = options;\n const within = (variable: string) =>\n scope ? `${variable} <: within \\`${scope}\\`, ` : '';\n const pattern =\n `or {` +\n // An empty array has no last element to append after.\n ` \\`${arrayPrefix}$arr\\` where { $arr <: \\`[]\\`, ${within('$arr')}` +\n ` $arr => \\`[${GRIT_INSERT_PLACEHOLDER}]\\` },` +\n // Dedupe on the list, append after its last element.\n ` \\`${arrayPrefix}[$items]\\` where {` +\n ` $items <: not contains \\`${skipIfContains ?? entry}\\`, ${within('$items')}` +\n ` $items <: [$..., $last], $last += \\`, ${GRIT_INSERT_PLACEHOLDER}\\` }` +\n ` }`;\n\n return insertViaGritQL(\n tree,\n filePath,\n language === 'py' ? `language python\\n${pattern}` : pattern,\n entry,\n );\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","identifierTaken","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureGritQLVariable","name","var","captureAllGritQLVariable","bound","push","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace","appendToArrayViaGritQL","arrayPrefix","entry","options","scope","skipIfContains","language","within","variable"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git.js';\nimport { isEsmWorkspace } from './module-format.js';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // The same identifier bound to a different module would be a duplicate\n // declaration, so refuse rather than emit a file that cannot compile.\n const identifierTaken = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from $module\\``,\n );\n if (identifierTaken) {\n throw new Error(\n `Cannot import ${variableName} from '${from}' in ${filePath}: ${variableName} is already imported from a different module. Rename the project so its generated identifier does not collide.`,\n );\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export { <names> } from '<from>';` statement to the given TypeScript\n * file, merging into an existing re-export of the same module when there is one.\n * Note that this will create the file if it does not exist in the tree.\n *\n * Adding to whatever is already there is what lets a barrel the user curates —\n * their own entities, models or components — survive a generator re-run while\n * the generated entry is still registered.\n */\nexport const addNamedExport = async (\n tree: Tree,\n filePath: string,\n names: string[],\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n if (!contents.trim()) {\n tree.write(filePath, `export { ${names.join(', ')} } from '${from}';\\n`);\n return;\n }\n\n const hasExistingExport = await matchGritQL(\n tree,\n filePath,\n `\\`export { $_ } from '${from}'\\``,\n );\n\n if (hasExistingExport) {\n for (const name of names) {\n await applyGritQL(\n tree,\n filePath,\n `\\`export { $names } from '${from}'\\` => \\`export { $names, ${name} } from '${from}'\\` where { $names <: not contains \\`${name}\\` }`,\n );\n }\n return;\n }\n\n const missing: string[] = [];\n for (const name of names) {\n if (!(await matchGritQL(tree, filePath, `\\`${name}\\``))) {\n missing.push(name);\n }\n }\n if (missing.length === 0) {\n return;\n }\n\n tree.write(\n filePath,\n `export { ${missing.join(', ')} } from '${from}';\\n${contents}`,\n );\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`\n * for `$mod`) instead of the whole matched node.\n */\nexport const captureGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.var(name) ?? undefined;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like {@link captureAllGritQL}, but returns one metavariable's binding for every\n * match rather than the whole matched node.\n *\n * This is what lets a caller work on the value a pattern isolated — matching\n * `` `runtime: $value` `` and reading `$value` gives `Runtime.NODEJS_24_X` alone,\n * so nothing downstream has to re-derive the assignment around it.\n *\n * A match whose metavariable is unbound contributes nothing, so the result holds\n * only real bindings. Returns undefined if the pattern could not be applied,\n * which an empty array cannot express.\n */\nexport const captureAllGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string[] | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n const bound = node.var(name);\n if (bound !== null && bound !== undefined) {\n captured.push(bound);\n }\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n\n/**\n * Append `entry` to an array, skipping arrays that already contain it, e.g.\n * `` appendToArrayViaGritQL(tree, file, 'tools:', 'myTool', { scope: 'new Agent($_)' }) ``\n * turns `tools: [a, b]` into `tools: [a, b, myTool]`.\n *\n * `arrayPrefix` is whatever precedes the array literal, including its separator —\n * `'tools:'` for an object property, `'tools='` for a keyword argument or\n * assignment, `'\"tools\":'` for a quoted key. Which separator applies depends on\n * the construct rather than the language, so the caller states it.\n *\n * Appends after the last *element* rather than rewriting the element list with\n * `=>`. GritQL binds a `[$items]` list to its trailing comma too, so\n * `` `[$items]` => `[$items, entry]` `` emits the sparse array `[a, b, , entry]`\n * once the formatter has wrapped the array — a hole that is `undefined` at\n * runtime. (Accumulating with `$items += \", entry\"` is safe by comparison, since\n * it appends within the list rather than re-emitting it; both forms appear in\n * this repo.)\n *\n * `scope` limits the match to an enclosing pattern, so same-named arrays\n * elsewhere in the file are left alone. `skipIfContains` overrides what the\n * append dedupes on, for when the rendered entry is not the identifier to match.\n *\n * The caller formats afterwards. Returns true if the entry was appended.\n */\nexport const appendToArrayViaGritQL = async (\n tree: Tree,\n filePath: string,\n arrayPrefix: string,\n entry: string,\n options: { scope?: string; skipIfContains?: string; language?: 'py' } = {},\n): Promise<boolean> => {\n const { scope, skipIfContains, language } = options;\n const within = (variable: string) =>\n scope ? `${variable} <: within \\`${scope}\\`, ` : '';\n const pattern =\n `or {` +\n // An empty array has no last element to append after.\n ` \\`${arrayPrefix}$arr\\` where { $arr <: \\`[]\\`, ${within('$arr')}` +\n ` $arr => \\`[${GRIT_INSERT_PLACEHOLDER}]\\` },` +\n // Dedupe on the list, append after its last element.\n ` \\`${arrayPrefix}[$items]\\` where {` +\n ` $items <: not contains \\`${skipIfContains ?? entry}\\`, ${within('$items')}` +\n ` $items <: [$..., $last], $last += \\`, ${GRIT_INSERT_PLACEHOLDER}\\` }` +\n ` }`;\n\n return insertViaGritQL(\n tree,\n filePath,\n language === 'py' ? `language python\\n${pattern}` : pattern,\n entry,\n );\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","identifierTaken","addStarExport","trim","alreadyExported","addNamedExport","names","hasExistingExport","name","missing","push","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureGritQLVariable","var","captureAllGritQLVariable","bound","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace","appendToArrayViaGritQL","arrayPrefix","entry","options","scope","skipIfContains","language","within","variable"],"mappings":"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"}
@@ -43,6 +43,17 @@ export declare const NODE_IMAGE_DEPENDENCIES: readonly [{
43
43
  }, {
44
44
  readonly name: "minimatch";
45
45
  }];
46
+ /**
47
+ * Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.
48
+ * The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so
49
+ * the image overrides them. Spread through `ownedElsewhere` for the same reason
50
+ * as {@link NODE_IMAGE_DEPENDENCIES}.
51
+ */
52
+ export declare const PRISMA_IMAGE_DEPENDENCIES: readonly [{
53
+ readonly name: "deepmerge-ts";
54
+ }, {
55
+ readonly name: "mysql2";
56
+ }];
46
57
  /**
47
58
  * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`
48
59
  * pins, for the images that auto-instrument with it. Spread through
@@ -4,7 +4,7 @@
4
4
  */ import { joinPathFragments } from "@nx/devkit";
5
5
  import { forDependencies } from "./declared-dependencies.js";
6
6
  import { FS_DEPENDENCIES, FsCommands } from "./fs.js";
7
- import { addDependencyToTargetIfNotPresent } from "./nx.js";
7
+ import { addDependencyToTargetIfNotPresent, normalizeTargetKeyOrder } from "./nx.js";
8
8
  import { containerImage, TS_VERSIONS } from "./versions.js";
9
9
  /** Dependencies a caller must declare to add a Docker scan target. */ export const DOCKER_DEPENDENCIES = [
10
10
  ...FS_DEPENDENCIES
@@ -49,6 +49,19 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
49
49
  name: 'minimatch'
50
50
  }
51
51
  ];
52
+ /**
53
+ * Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.
54
+ * The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so
55
+ * the image overrides them. Spread through `ownedElsewhere` for the same reason
56
+ * as {@link NODE_IMAGE_DEPENDENCIES}.
57
+ */ export const PRISMA_IMAGE_DEPENDENCIES = [
58
+ {
59
+ name: 'deepmerge-ts'
60
+ },
61
+ {
62
+ name: 'mysql2'
63
+ }
64
+ ];
52
65
  /**
53
66
  * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`
54
67
  * pins, for the images that auto-instrument with it. Spread through
@@ -102,7 +115,7 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
102
115
  commands.push(`${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`, `${containerEngine} run --rm -v "./${scanDir}":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`);
103
116
  });
104
117
  project.targets ??= {};
105
- project.targets[trivyTargetName] = {
118
+ project.targets[trivyTargetName] = normalizeTargetKeyOrder({
106
119
  // The image being scanned is only reachable through the container engine,
107
120
  // so a restored cache entry would report a pass for an image that may no
108
121
  // longer exist. The scan re-runs and either scans the real image or fails.
@@ -120,7 +133,7 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
120
133
  dependsOn: [
121
134
  dockerTargetName
122
135
  ]
123
- };
136
+ });
124
137
  // Aggregate per-component scan targets under a single `trivy` target. The
125
138
  // scan is intentionally NOT wired into `build`: image scanning is slow and
126
139
  // its result depends on the ever-changing vulnerability database (a scan
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.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 ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport { addDependencyToTargetIfNotPresent } from './nx.js';\nimport { containerImage, type ITsDepVersion, TS_VERSIONS } from './versions.js';\n\n/** Dependencies a caller must declare to add a Docker scan target. */\nexport const DOCKER_DEPENDENCIES = [...FS_DEPENDENCIES] as const;\n\n/**\n * The `cache` setting every target that builds a container image must use.\n *\n * A built image lives in the container engine's own store, not under any\n * `outputs` path, so Nx has nothing to restore a cache hit from: it would\n * report the build complete while the image is missing, and the `trivy` scan\n * that depends on it would report a cached pass against an image that isn't\n * there. The container engine's layer cache is the sound cache here — it is\n * keyed on the build context and lives alongside the image, so an unchanged\n * context rebuilds in well under a second and always leaves the image present.\n */\nexport const IMAGE_BUILD_CACHE = false;\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan (\\`nx run-many --target trivy\\`).\n# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids\n`;\n\n/**\n * Substitution variables for the vended Node `Dockerfile` templates: the npm\n * version to install globally, and the versions its dependency overrides pin.\n */\nexport const nodeImageVersions = () => ({\n npmVersion: TS_VERSIONS.npm,\n minimatchVersion: TS_VERSIONS.minimatch,\n});\n\n/**\n * Packages a vended Node `Dockerfile` pins, which every generator writing one\n * must declare so the version sync keeps those pins current.\n *\n * Spread through `ownedElsewhere`: the pin lives in the image build rather than\n * in any manifest, so nothing is installed into the workspace. Left undeclared\n * these would be the only vended versions never upgraded — and they are\n * precisely the ones held at a version clear of a known HIGH/CRITICAL\n * vulnerability.\n */\nexport const NODE_IMAGE_DEPENDENCIES = [\n { name: 'npm' },\n { name: 'minimatch' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`\n * pins, for the images that auto-instrument with it. Spread through\n * `ownedElsewhere` for the same reason as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const ADOT_IMAGE_DEPENDENCIES = [\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n { name: '@opentelemetry/propagator-jaeger' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface DockerScanTargetOptions {\n /**\n * Project configuration to add the scan target to (mutated in place).\n */\n readonly project: ProjectConfiguration;\n /**\n * Container engine command to invoke (\\`docker\\` or \\`finch\\`).\n */\n readonly containerEngine: string;\n /**\n * Name of the scan target to add, e.g. \\`my-agent-trivy\\`.\n */\n readonly trivyTargetName: string;\n /**\n * Name of the docker target that builds the image(s) to scan. The scan\n * target depends on it.\n */\n readonly dockerTargetName: string;\n /**\n * Tags of the images built by the docker target which should be scanned.\n */\n readonly imageTags: string[];\n}\n\n/**\n * Add a Trivy scan target for the images built by a docker target and wire it\n * under the aggregate \\`trivy\\` target. The scan is not part of \\`build\\` — run\n * \\`nx run-many --target trivy\\` (the root \\`trivy\\` script) in CI.\n *\n * The scan target depends on the docker target and is not cacheable, because\n * what it scans lives in the container engine rather than under its \\`outputs\\`.\n * Each image is saved to a tarball under \\`dist/<projectRoot>/trivy/<scan-key>\\`\n * (kept out of any Docker build context) and scanned with the pinned ECR-hosted\n * Trivy image via a workspace-relative bind mount, so the same commands work\n * under both docker and finch. The scan exits non-zero (exit code 1) on\n * HIGH/CRITICAL vulnerabilities.\n *\n * A \\`.trivyignore\\` is vended at the project root (kept if it already exists)\n * for suppressing findings.\n */\nexport const addDockerScanTarget = <const D extends DependencyDeclaration>(\n tree: Tree,\n options: DockerScanTargetOptions,\n declaration: D & MustDeclare<typeof DOCKER_DEPENDENCIES, D>,\n): void => {\n const { project, containerEngine, trivyTargetName, dockerTargetName } =\n options;\n const { imageTags } = options;\n const projectRoot = project.root;\n\n const ignoreFilePath = joinPathFragments(projectRoot, TRIVY_IGNORE_FILE);\n if (!tree.exists(ignoreFilePath)) {\n tree.write(ignoreFilePath, TRIVY_IGNORE_CONTENTS);\n }\n\n // Stage scan artifacts in a directory unique to this scan target so that\n // sibling targets in the same project (e.g. multiple agents) don't clobber\n // each other's tarballs when run in parallel. The first image tag is unique\n // per target, so it makes a stable, collision-free key.\n const scanKey = imageTags[0].replace(/[^a-zA-Z0-9-]/g, '-');\n const scanDir = joinPathFragments('dist', projectRoot, 'trivy', scanKey);\n const trivyImage = containerImage('trivy');\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof DOCKER_DEPENDENCIES>(declaration),\n );\n const commands = [\n fs.rm(scanDir),\n fs.mkdir(scanDir),\n fs.cpFile(ignoreFilePath, joinPathFragments(scanDir, TRIVY_IGNORE_FILE)),\n ];\n\n imageTags.forEach((imageTag, index) => {\n const tarName = `image-${index}.tar`;\n commands.push(\n `${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`,\n `${containerEngine} run --rm -v \"./${scanDir}\":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`,\n );\n });\n\n project.targets ??= {};\n project.targets[trivyTargetName] = {\n // The image being scanned is only reachable through the container engine,\n // so a restored cache entry would report a pass for an image that may no\n // longer exist. The scan re-runs and either scans the real image or fails.\n // Nx only hashes a cacheable task, so declaring `inputs` here would have no\n // effect.\n cache: false,\n outputs: [`{workspaceRoot}/${scanDir}`],\n executor: 'nx:run-commands',\n options: {\n commands,\n parallel: false,\n },\n dependsOn: [dockerTargetName],\n };\n\n // Aggregate per-component scan targets under a single `trivy` target. The\n // scan is intentionally NOT wired into `build`: image scanning is slow and\n // its result depends on the ever-changing vulnerability database (a scan\n // that passes today can fail tomorrow when a new CVE is published), so\n // coupling it to `build` would make local and CI builds non-deterministic.\n // Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.\n if (trivyTargetName !== 'trivy') {\n addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);\n }\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addDependencyToTargetIfNotPresent","containerImage","TS_VERSIONS","DOCKER_DEPENDENCIES","IMAGE_BUILD_CACHE","TRIVY_IGNORE_FILE","TRIVY_IGNORE_CONTENTS","nodeImageVersions","npmVersion","npm","minimatchVersion","minimatch","NODE_IMAGE_DEPENDENCIES","name","ADOT_IMAGE_DEPENDENCIES","addDockerScanTarget","tree","options","declaration","project","containerEngine","trivyTargetName","dockerTargetName","imageTags","projectRoot","root","ignoreFilePath","exists","write","scanKey","replace","scanDir","trivyImage","fs","commands","rm","mkdir","cpFile","forEach","imageTag","index","tarName","push","targets","cache","outputs","executor","parallel","dependsOn"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SAEEC,eAAe,QAEV,6BAA6B;AACpC,SAASC,eAAe,EAAEC,UAAU,QAAQ,UAAU;AACtD,SAASC,iCAAiC,QAAQ,UAAU;AAC5D,SAASC,cAAc,EAAsBC,WAAW,QAAQ,gBAAgB;AAEhF,oEAAoE,GACpE,OAAO,MAAMC,sBAAsB;OAAIL;CAAgB,CAAU;AAEjE;;;;;;;;;;CAUC,GACD,OAAO,MAAMM,oBAAoB,MAAM;AAEvC,MAAMC,oBAAoB;AAE1B,MAAMC,wBAAwB,CAAC;;;AAG/B,CAAC;AAED;;;CAGC,GACD,OAAO,MAAMC,oBAAoB,IAAO,CAAA;QACtCC,YAAYN,YAAYO,GAAG;QAC3BC,kBAAkBR,YAAYS,SAAS;IACzC,CAAA,EAAG;AAEH;;;;;;;;;CASC,GACD,OAAO,MAAMC,0BAA0B;IACrC;QAAEC,MAAM;IAAM;IACd;QAAEA,MAAM;IAAY;CACrB,CAAuD;AAExD;;;;CAIC,GACD,OAAO,MAAMC,0BAA0B;IACrC;QAAED,MAAM;IAAyD;IACjE;QAAEA,MAAM;IAAmC;CAC5C,CAAuD;AA0BxD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAME,sBAAsB,CACjCC,MACAC,SACAC;IAEA,MAAM,EAAEC,OAAO,EAAEC,eAAe,EAAEC,eAAe,EAAEC,gBAAgB,EAAE,GACnEL;IACF,MAAM,EAAEM,SAAS,EAAE,GAAGN;IACtB,MAAMO,cAAcL,QAAQM,IAAI;IAEhC,MAAMC,iBAAiB9B,kBAAkB4B,aAAanB;IACtD,IAAI,CAACW,KAAKW,MAAM,CAACD,iBAAiB;QAChCV,KAAKY,KAAK,CAACF,gBAAgBpB;IAC7B;IAEA,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,wDAAwD;IACxD,MAAMuB,UAAUN,SAAS,CAAC,EAAE,CAACO,OAAO,CAAC,kBAAkB;IACvD,MAAMC,UAAUnC,kBAAkB,QAAQ4B,aAAa,SAASK;IAChE,MAAMG,aAAa/B,eAAe;IAElC,MAAMgC,KAAK,IAAIlC,WACbiB,MACAnB,gBAA4CqB;IAE9C,MAAMgB,WAAW;QACfD,GAAGE,EAAE,CAACJ;QACNE,GAAGG,KAAK,CAACL;QACTE,GAAGI,MAAM,CAACX,gBAAgB9B,kBAAkBmC,SAAS1B;KACtD;IAEDkB,UAAUe,OAAO,CAAC,CAACC,UAAUC;QAC3B,MAAMC,UAAU,CAAC,MAAM,EAAED,MAAM,IAAI,CAAC;QACpCN,SAASQ,IAAI,CACX,GAAGtB,gBAAgB,SAAS,EAAExB,kBAAkBmC,SAASU,SAAS,CAAC,EAAEF,UAAU,EAC/E,GAAGnB,gBAAgB,gBAAgB,EAAEW,QAAQ,QAAQ,EAAEC,WAAW,qBAAqB,EAAES,QAAQ,oBAAoB,EAAEpC,kBAAkB,yFAAyF,CAAC;IAEvO;IAEAc,QAAQwB,OAAO,KAAK,CAAC;IACrBxB,QAAQwB,OAAO,CAACtB,gBAAgB,GAAG;QACjC,0EAA0E;QAC1E,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,UAAU;QACVuB,OAAO;QACPC,SAAS;YAAC,CAAC,gBAAgB,EAAEd,SAAS;SAAC;QACvCe,UAAU;QACV7B,SAAS;YACPiB;YACAa,UAAU;QACZ;QACAC,WAAW;YAAC1B;SAAiB;IAC/B;IAEA,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAID,oBAAoB,SAAS;QAC/BrB,kCAAkCmB,SAAS,SAASE;IACtD;AACF,EAAE"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.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 ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport {\n addDependencyToTargetIfNotPresent,\n normalizeTargetKeyOrder,\n} from './nx.js';\nimport { containerImage, type ITsDepVersion, TS_VERSIONS } from './versions.js';\n\n/** Dependencies a caller must declare to add a Docker scan target. */\nexport const DOCKER_DEPENDENCIES = [...FS_DEPENDENCIES] as const;\n\n/**\n * The `cache` setting every target that builds a container image must use.\n *\n * A built image lives in the container engine's own store, not under any\n * `outputs` path, so Nx has nothing to restore a cache hit from: it would\n * report the build complete while the image is missing, and the `trivy` scan\n * that depends on it would report a cached pass against an image that isn't\n * there. The container engine's layer cache is the sound cache here — it is\n * keyed on the build context and lives alongside the image, so an unchanged\n * context rebuilds in well under a second and always leaves the image present.\n */\nexport const IMAGE_BUILD_CACHE = false;\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan (\\`nx run-many --target trivy\\`).\n# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids\n`;\n\n/**\n * Substitution variables for the vended Node `Dockerfile` templates: the npm\n * version to install globally, and the versions its dependency overrides pin.\n */\nexport const nodeImageVersions = () => ({\n npmVersion: TS_VERSIONS.npm,\n minimatchVersion: TS_VERSIONS.minimatch,\n});\n\n/**\n * Packages a vended Node `Dockerfile` pins, which every generator writing one\n * must declare so the version sync keeps those pins current.\n *\n * Spread through `ownedElsewhere`: the pin lives in the image build rather than\n * in any manifest, so nothing is installed into the workspace. Left undeclared\n * these would be the only vended versions never upgraded — and they are\n * precisely the ones held at a version clear of a known HIGH/CRITICAL\n * vulnerability.\n */\nexport const NODE_IMAGE_DEPENDENCIES = [\n { name: 'npm' },\n { name: 'minimatch' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.\n * The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so\n * the image overrides them. Spread through `ownedElsewhere` for the same reason\n * as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const PRISMA_IMAGE_DEPENDENCIES = [\n { name: 'deepmerge-ts' },\n { name: 'mysql2' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`\n * pins, for the images that auto-instrument with it. Spread through\n * `ownedElsewhere` for the same reason as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const ADOT_IMAGE_DEPENDENCIES = [\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n { name: '@opentelemetry/propagator-jaeger' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface DockerScanTargetOptions {\n /**\n * Project configuration to add the scan target to (mutated in place).\n */\n readonly project: ProjectConfiguration;\n /**\n * Container engine command to invoke (\\`docker\\` or \\`finch\\`).\n */\n readonly containerEngine: string;\n /**\n * Name of the scan target to add, e.g. \\`my-agent-trivy\\`.\n */\n readonly trivyTargetName: string;\n /**\n * Name of the docker target that builds the image(s) to scan. The scan\n * target depends on it.\n */\n readonly dockerTargetName: string;\n /**\n * Tags of the images built by the docker target which should be scanned.\n */\n readonly imageTags: string[];\n}\n\n/**\n * Add a Trivy scan target for the images built by a docker target and wire it\n * under the aggregate \\`trivy\\` target. The scan is not part of \\`build\\` — run\n * \\`nx run-many --target trivy\\` (the root \\`trivy\\` script) in CI.\n *\n * The scan target depends on the docker target and is not cacheable, because\n * what it scans lives in the container engine rather than under its \\`outputs\\`.\n * Each image is saved to a tarball under \\`dist/<projectRoot>/trivy/<scan-key>\\`\n * (kept out of any Docker build context) and scanned with the pinned ECR-hosted\n * Trivy image via a workspace-relative bind mount, so the same commands work\n * under both docker and finch. The scan exits non-zero (exit code 1) on\n * HIGH/CRITICAL vulnerabilities.\n *\n * A \\`.trivyignore\\` is vended at the project root (kept if it already exists)\n * for suppressing findings.\n */\nexport const addDockerScanTarget = <const D extends DependencyDeclaration>(\n tree: Tree,\n options: DockerScanTargetOptions,\n declaration: D & MustDeclare<typeof DOCKER_DEPENDENCIES, D>,\n): void => {\n const { project, containerEngine, trivyTargetName, dockerTargetName } =\n options;\n const { imageTags } = options;\n const projectRoot = project.root;\n\n const ignoreFilePath = joinPathFragments(projectRoot, TRIVY_IGNORE_FILE);\n if (!tree.exists(ignoreFilePath)) {\n tree.write(ignoreFilePath, TRIVY_IGNORE_CONTENTS);\n }\n\n // Stage scan artifacts in a directory unique to this scan target so that\n // sibling targets in the same project (e.g. multiple agents) don't clobber\n // each other's tarballs when run in parallel. The first image tag is unique\n // per target, so it makes a stable, collision-free key.\n const scanKey = imageTags[0].replace(/[^a-zA-Z0-9-]/g, '-');\n const scanDir = joinPathFragments('dist', projectRoot, 'trivy', scanKey);\n const trivyImage = containerImage('trivy');\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof DOCKER_DEPENDENCIES>(declaration),\n );\n const commands = [\n fs.rm(scanDir),\n fs.mkdir(scanDir),\n fs.cpFile(ignoreFilePath, joinPathFragments(scanDir, TRIVY_IGNORE_FILE)),\n ];\n\n imageTags.forEach((imageTag, index) => {\n const tarName = `image-${index}.tar`;\n commands.push(\n `${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`,\n `${containerEngine} run --rm -v \"./${scanDir}\":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`,\n );\n });\n\n project.targets ??= {};\n project.targets[trivyTargetName] = normalizeTargetKeyOrder({\n // The image being scanned is only reachable through the container engine,\n // so a restored cache entry would report a pass for an image that may no\n // longer exist. The scan re-runs and either scans the real image or fails.\n // Nx only hashes a cacheable task, so declaring `inputs` here would have no\n // effect.\n cache: false,\n outputs: [`{workspaceRoot}/${scanDir}`],\n executor: 'nx:run-commands',\n options: {\n commands,\n parallel: false,\n },\n dependsOn: [dockerTargetName],\n });\n\n // Aggregate per-component scan targets under a single `trivy` target. The\n // scan is intentionally NOT wired into `build`: image scanning is slow and\n // its result depends on the ever-changing vulnerability database (a scan\n // that passes today can fail tomorrow when a new CVE is published), so\n // coupling it to `build` would make local and CI builds non-deterministic.\n // Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.\n if (trivyTargetName !== 'trivy') {\n addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);\n }\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addDependencyToTargetIfNotPresent","normalizeTargetKeyOrder","containerImage","TS_VERSIONS","DOCKER_DEPENDENCIES","IMAGE_BUILD_CACHE","TRIVY_IGNORE_FILE","TRIVY_IGNORE_CONTENTS","nodeImageVersions","npmVersion","npm","minimatchVersion","minimatch","NODE_IMAGE_DEPENDENCIES","name","PRISMA_IMAGE_DEPENDENCIES","ADOT_IMAGE_DEPENDENCIES","addDockerScanTarget","tree","options","declaration","project","containerEngine","trivyTargetName","dockerTargetName","imageTags","projectRoot","root","ignoreFilePath","exists","write","scanKey","replace","scanDir","trivyImage","fs","commands","rm","mkdir","cpFile","forEach","imageTag","index","tarName","push","targets","cache","outputs","executor","parallel","dependsOn"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { type Tree } from '@nx/devkit';
6
+ /** A Global Secondary Index as `config.json` declares it. */
7
+ export interface DynamoDBGlobalSecondaryIndex {
8
+ readonly indexName: string;
9
+ readonly partitionKey: string;
10
+ readonly sortKey?: string;
11
+ }
12
+ export interface DynamoDBLocalDevConfig {
13
+ readonly port: number;
14
+ readonly tableName: string;
15
+ readonly image: string;
16
+ readonly containerName: string;
17
+ readonly containerEngine: string;
18
+ }
19
+ export interface DynamoDBConfig {
20
+ runtimeConfigKey: string;
21
+ localDev: DynamoDBLocalDevConfig;
22
+ tableConfig: {
23
+ globalSecondaryIndexes: DynamoDBGlobalSecondaryIndex[];
24
+ };
25
+ }
26
+ /** The image the local DynamoDB container runs. */
27
+ export declare const DYNAMODB_LOCAL_IMAGE = "public.ecr.aws/aws-dynamodb-local/aws-dynamodb-local:latest";
28
+ /**
29
+ * The two overloadable GSIs a new table starts with, following the single-table
30
+ * design naming convention the guides describe.
31
+ */
32
+ export declare const DEFAULT_GLOBAL_SECONDARY_INDEXES: DynamoDBGlobalSecondaryIndex[];
33
+ /**
34
+ * Write a DynamoDB project's `config.json`.
35
+ *
36
+ * The file has two halves with different owners, so it is merged rather than
37
+ * re-emitted whole:
38
+ *
39
+ * - `runtimeConfigKey` and `localDev` are derived from the generator's options,
40
+ * so they converge on a re-run — changing `--tableName` still takes effect.
41
+ * - `tableConfig.globalSecondaryIndexes` is the user's, since the guides tell
42
+ * them to add an entry per GSI. The defaults seed a new project; an existing
43
+ * list is carried through untouched.
44
+ */
45
+ export declare const writeDynamoDBConfig: (tree: Tree, projectRoot: string, config: {
46
+ runtimeConfigKey: string;
47
+ localDev: DynamoDBLocalDevConfig;
48
+ }) => void;