@aws/nx-plugin 1.2.0 → 1.3.1

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 (88) hide show
  1. package/LICENSE-THIRD-PARTY +107 -259
  2. package/README.md +1 -0
  3. package/generators.json +8 -0
  4. package/migrations.json +53 -10
  5. package/package.json +14 -14
  6. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
  7. package/src/greengrass/component/python/__snapshots__/generator.spec.ts.snap +8 -8
  8. package/src/infra/app/__snapshots__/generator.spec.ts.snap +3 -3
  9. package/src/internal/test-matrix/generator.js +6 -1
  10. package/src/internal/test-matrix/generator.js.map +1 -1
  11. package/src/migrations/latest/biome-markdown-yaml-formatter-off/metadata.json +3 -0
  12. package/src/migrations/latest/biome-markdown-yaml-formatter-off/migration.d.ts +6 -0
  13. package/src/migrations/latest/biome-markdown-yaml-formatter-off/migration.js +113 -0
  14. package/src/migrations/latest/biome-markdown-yaml-formatter-off/migration.js.map +1 -0
  15. package/src/migrations/v1.0.15/0001-py-agent-pillow-override/migration.js.map +1 -0
  16. package/src/migrations/v1.1.1/0001-ts-agent-guardrails-synth-input/migration.js.map +1 -0
  17. package/src/migrations/v1.3.0/0001-ts-agent-guardrails-terraform-version-retention/metadata.json +3 -0
  18. package/src/migrations/v1.3.0/0001-ts-agent-guardrails-terraform-version-retention/migration.d.ts +6 -0
  19. package/src/migrations/v1.3.0/0001-ts-agent-guardrails-terraform-version-retention/migration.js +118 -0
  20. package/src/migrations/v1.3.0/0001-ts-agent-guardrails-terraform-version-retention/migration.js.map +1 -0
  21. package/src/preset/__snapshots__/generator.spec.ts.snap +17 -5
  22. package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +7 -7
  23. package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +7 -7
  24. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +4 -4
  25. package/src/py/agent/guardrails/__snapshots__/generator.spec.ts.snap +3813 -0
  26. package/src/py/agent/guardrails/files/langchain/guardrail.py.template +135 -0
  27. package/src/py/agent/guardrails/files/strands/guardrail.py.template +93 -0
  28. package/src/py/agent/guardrails/generator.d.ts +10 -0
  29. package/src/py/agent/guardrails/generator.js +175 -0
  30. package/src/py/agent/guardrails/generator.js.map +1 -0
  31. package/src/py/agent/guardrails/runtime-check/run_checks.py +250 -0
  32. package/src/py/agent/guardrails/schema.d.js +6 -0
  33. package/src/py/agent/guardrails/schema.d.js.map +1 -0
  34. package/src/py/agent/guardrails/schema.d.ts +9 -0
  35. package/src/py/agent/guardrails/schema.json +25 -0
  36. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
  37. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +12 -12
  38. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
  39. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -4
  40. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +4 -4
  41. package/src/sdk/py.d.ts +2 -0
  42. package/src/sdk/py.js +1 -0
  43. package/src/sdk/py.js.map +1 -1
  44. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +5 -5
  45. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -8
  46. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +12 -12
  47. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +3 -3
  48. package/src/ts/agent/guardrails/__snapshots__/generator.spec.ts.snap +57 -6
  49. package/src/ts/agent/guardrails/generator.js +2 -1
  50. package/src/ts/agent/guardrails/generator.js.map +1 -1
  51. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +1 -1
  52. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
  53. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
  54. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +2 -2
  55. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4 -4
  56. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +53 -33
  57. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +2 -2
  58. package/src/utils/format.d.ts +10 -0
  59. package/src/utils/format.js +11 -0
  60. package/src/utils/format.js.map +1 -1
  61. package/src/utils/guardrail-constructs/files/terraform/app/guardrails/__nameKebabCase__/__nameKebabCase__-guardrail.tf.template +18 -1
  62. package/src/utils/guardrail-constructs/guardrail-constructs.d.ts +5 -0
  63. package/src/utils/guardrail-constructs/guardrail-constructs.js +8 -0
  64. package/src/utils/guardrail-constructs/guardrail-constructs.js.map +1 -1
  65. package/src/utils/guardrail-constructs/guardrail-wiring.d.ts +7 -0
  66. package/src/utils/guardrail-constructs/guardrail-wiring.js +6 -5
  67. package/src/utils/guardrail-constructs/guardrail-wiring.js.map +1 -1
  68. package/src/utils/test/fake-aws/fake_aws.py +205 -0
  69. package/src/utils/test/python-dependencies.d.ts +6 -0
  70. package/src/utils/test/python-dependencies.js +19 -0
  71. package/src/utils/test/python-dependencies.js.map +1 -1
  72. package/src/utils/test/warm-python-cache.js +5 -1
  73. package/src/utils/test/warm-python-cache.js.map +1 -1
  74. package/src/utils/version-upgrade-migration/sync-vended-versions.d.ts +8 -0
  75. package/src/utils/version-upgrade-migration/sync-vended-versions.js +11 -2
  76. package/src/utils/version-upgrade-migration/sync-vended-versions.js.map +1 -1
  77. package/src/utils/versions.d.ts +71 -71
  78. package/src/utils/versions.js +68 -68
  79. package/src/utils/versions.js.map +1 -1
  80. package/src/migrations/latest/py-agent-pillow-override/migration.js.map +0 -1
  81. package/src/migrations/latest/ts-agent-guardrails-synth-input/migration.js.map +0 -1
  82. /package/src/migrations/{latest/py-agent-pillow-override → v1.0.15/0001-py-agent-pillow-override}/metadata.json +0 -0
  83. /package/src/migrations/{latest/py-agent-pillow-override → v1.0.15/0001-py-agent-pillow-override}/migration.d.ts +0 -0
  84. /package/src/migrations/{latest/py-agent-pillow-override → v1.0.15/0001-py-agent-pillow-override}/migration.js +0 -0
  85. /package/src/migrations/{latest/ts-agent-guardrails-synth-input → v1.1.1/0001-ts-agent-guardrails-synth-input}/metadata.json +0 -0
  86. /package/src/migrations/{latest/ts-agent-guardrails-synth-input → v1.1.1/0001-ts-agent-guardrails-synth-input}/migration.d.ts +0 -0
  87. /package/src/migrations/{latest/ts-agent-guardrails-synth-input → v1.1.1/0001-ts-agent-guardrails-synth-input}/migration.js +0 -0
  88. /package/src/{ts/agent/guardrails/files/agent → utils/guardrail-constructs/files/policy}/guardrail.json.template +0 -0
@@ -64,6 +64,17 @@ import { TS_VERSIONS } from "./versions.js";
64
64
  enabled: false
65
65
  }
66
66
  },
67
+ // Markdown and YAML files are left as written.
68
+ markdown: {
69
+ formatter: {
70
+ enabled: false
71
+ }
72
+ },
73
+ yaml: {
74
+ formatter: {
75
+ enabled: false
76
+ }
77
+ },
67
78
  linter: {
68
79
  enabled: true,
69
80
  rules: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport { getProjects, type Tree } from '@nx/devkit';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { type RuffOptions, ruffFixAndFormat } from './ruff.js';\nimport { tryReadToml } from './toml.js';\nimport { TS_VERSIONS } from './versions.js';\n\n/**\n * The Biome release the wasm bindings are built from. Must match\n * `TS_VERSIONS['@biomejs/biome']`, the CLI the vended targets run.\n */\nexport const BIOME_WASM_VERSION: string = createRequire(import.meta.url)(\n '@biomejs/wasm-nodejs/package.json',\n).version;\n\n/**\n * Excludes test reports — including the vendored scripts vitest's coverage HTML\n * reporter writes — from formatting and linting.\n */\nexport const BIOME_TEST_OUTPUT_EXCLUDE = '!**/test-output';\n\n/**\n * The transient bundle rolldown writes beside a `rolldown.config.ts` while\n * loading it.\n *\n * Loading a TypeScript config bundles it to `rolldown.config.<hash>.js` in the\n * config's own directory, imports it, then unlinks it. That file exists for as\n * long as the bundle takes, so a `format` or `lint` target running concurrently\n * in the same project sees a machine-generated file — reported unformatted,\n * failing the target for no fault of the source.\n *\n * `.*js` rather than `.js`, since the extension follows the config's own: a\n * `.mts` config bundles to `.mjs`, a `.cts` one to `.cjs`.\n */\nexport const ROLLDOWN_CONFIG_BUNDLE_GLOB = '**/rolldown.config.*.*js';\n\n/** Keeps rolldown's transient config bundle out of formatting and linting. */\nexport const BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE = `!${ROLLDOWN_CONFIG_BUNDLE_GLOB}`;\n\n/**\n * The biome.json vended into a new workspace. The pnpm catalog resolver is only\n * included on pnpm workspaces, since `experimentalPnpmCatalogs` is Biome's only\n * catalog resolver and reads `pnpm-workspace.yaml` exclusively — it does nothing\n * for yarn or bun catalogs, so vending it there would be misleading.\n */\nexport const getDefaultBiomeConfig = (tree: Tree) => ({\n $schema: `https://biomejs.dev/schemas/${TS_VERSIONS['@biomejs/biome']}/schema.json`,\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n // Resolve `catalog:` versions from pnpm-workspace.yaml (pnpm workspaces only).\n ...(tree.exists('pnpm-workspace.yaml')\n ? { resolver: { experimentalPnpmCatalogs: true } }\n : {}),\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n linter: {\n enabled: true,\n rules: {\n preset: 'none',\n correctness: {\n // Every project must declare the third-party dependencies its source\n // code imports in its own package.json.\n noUndeclaredDependencies: 'error',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n BIOME_TEST_OUTPUT_EXCLUDE,\n BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE,\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n // GritQL codemod cache written by generators — its sample sources\n // otherwise pollute a bare `biome check .` with parse errors.\n '!**/.grit',\n '!**/*.css',\n '!**/*.gen.*',\n '!**/generated/**',\n '!**/tsconfig*.json',\n ],\n },\n // Config files, build scripts and tests use root tooling rather than\n // declaring it per-project, so the undeclared-dependency rule is off for them.\n overrides: [\n {\n includes: [\n '**/*.config.{ts,mts,cts,js,mjs,cjs}',\n '**/*.{spec,test}.{ts,tsx,mts,cts,js,jsx,mjs,cjs}',\n '**/*.stories.{ts,tsx}',\n ],\n linter: {\n rules: {\n correctness: {\n noUndeclaredDependencies: 'off',\n },\n },\n },\n },\n ],\n});\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/** Matches `tsconfig.json` and variants like `tsconfig.lib.json`. */\nconst isTsConfig = (filePath: string): boolean =>\n /(^|\\/)tsconfig[^/]*\\.json$/.test(filePath);\n\nexport interface FormatFilesInSubtreeOptions {\n /**\n * Paths, relative to the workspace root, to leave untouched.\n *\n * For files another tool owns the formatting of: formatting them here makes\n * generation non-idempotent, since the tool rewrites them in its own shape on\n * the next run and the workspace flips between the two forms.\n */\n readonly ignore?: readonly string[];\n}\n\n/**\n * Paths declared ignored for a tree, which stay ignored for every later call.\n *\n * A call formats every change pending in the tree, not only the ones its caller\n * made, so the list cannot be scoped to the call that passes it: generators\n * composing on one tree would reformat a file an earlier generator had excluded.\n * Once the generator that owns a file declares it ignored, it stays ignored for\n * the rest of the run.\n */\nconst treeIgnoredPaths = new WeakMap<Tree, Set<string>>();\n\n/**\n * Tree paths are workspace-relative and forward-slash separated. Normalise so a\n * caller building a path with `path.join` on Windows still matches.\n */\nconst normalizeTreePath = (filePath: string): string =>\n filePath.split(path.sep).join('/').replace(/^\\.\\//, '');\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n options?: FormatFilesInSubtreeOptions,\n): Promise<void> {\n let ignored = treeIgnoredPaths.get(tree);\n if (options?.ignore?.length) {\n if (!ignored) {\n ignored = new Set();\n treeIgnoredPaths.set(tree, ignored);\n }\n for (const filePath of options.ignore) {\n ignored.add(normalizeTreePath(filePath));\n }\n }\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true))\n .filter((file) => !ignored?.has(normalizeTreePath(file.path)));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter(\n (file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)) &&\n // tsconfigs are not biome-managed: they're excluded from the vended\n // format target (Nx's typescript-sync rewrites them without formatting),\n // so formatting them at generation would only diverge from the form\n // written on later runs. Leave them as updateJson/writeJson emit them so\n // repeated generation stays idempotent.\n !isTsConfig(file.path),\n );\n\n // Resolve each project's ruff settings (module names, line-length) so files\n // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).\n const pythonProjectConfigs = pyFiles.length\n ? getPythonProjectRuffConfigs(tree)\n : [];\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n resolveRuffOptions(\n readRuffConfig(tree, file.path),\n getOwningProjectRuffConfig(file.path, pythonProjectConfigs),\n ),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n formatWithBiome(tree, otherFiles);\n}\n\n/**\n * The Biome configuration to format with: the workspace's own `biome.json`, else\n * the config we vend.\n *\n * Read through the tree, which falls back to disk for a file it doesn't hold. So\n * this resolves the most current config either way — the workspace's on-disk one,\n * or the version a generator has just written to the tree and not yet flushed,\n * which shelling out to the CLI could never see.\n */\nfunction readBiomeConfig(tree: Tree): unknown {\n const config = tree.read('biome.json', 'utf-8');\n if (config) {\n try {\n return JSON.parse(config);\n } catch {\n // Malformed config — fall through to the config we vend\n }\n }\n return getDefaultBiomeConfig(tree);\n}\n\n/**\n * Format files with Biome in-process, reusing one instance across the batch.\n *\n * Replaces one `biome format --stdin-file-path` process per file, which\n * dominated generation at ~70ms each: `formatFilesInSubtree` formats every\n * change accumulated in the tree, not only the ones its caller made, so\n * generators sharing a tree reformat the same files once per call. In-process is\n * ~0.5ms per file. Output was verified byte-identical across the plugin's whole\n * source tree, except that the CLI corrupts control characters passed through\n * stdin (a NUL in a template literal) where formatting in-process preserves them.\n */\nfunction formatWithBiome(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n biome.applyConfiguration(projectKey, readBiomeConfig(tree));\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\n/**\n * Read the ruff config for a file, by walking from its directory up to the\n * workspace root looking for `.ruff.toml`, `ruff.toml`, or a `pyproject.toml`\n * with a `[tool.ruff]` section — the same files, in the same order, that ruff\n * itself resolves. The walk stops at the workspace root so a stray config in a\n * parent of the workspace (or the home directory) is never treated as the\n * project's.\n *\n * The settings are read rather than merely detected because formatting runs\n * in-process against tree content, so ruff never sees the file's location and\n * cannot resolve the config itself (see {@link resolveRuffOptions}).\n *\n * Reads go through the tree, which falls through to disk for files this run has\n * not touched, so a config the generator has just written in memory is picked up\n * as well as one already on disk.\n */\nfunction readRuffConfig(tree: Tree, filePath: string): RuffOptions | undefined {\n let dir = path.dirname(filePath);\n while (true) {\n for (const name of ['.ruff.toml', 'ruff.toml']) {\n const config = tryReadToml(tree, path.join(dir, name));\n if (config) {\n return config as RuffOptions;\n }\n }\n const ruff = (tryReadToml(tree, path.join(dir, 'pyproject.toml')) as any)\n ?.tool?.ruff;\n if (ruff) {\n return ruff as RuffOptions;\n }\n // Stop once the workspace root ('.') has been checked.\n if (dir === '.' || dir === '' || dir === path.dirname(dir)) {\n return undefined;\n }\n dir = path.dirname(dir);\n }\n}\n\n/**\n * The `[tool.ruff.lint].select` generated Python projects vend. Generation\n * formats before that config lands on disk, so it is pinned here to keep\n * generated files clean under the project's own `lint` target rather than under\n * whatever ruff's defaults happen to be for the pinned release.\n */\nconst DEFAULT_RUFF_SELECT = ['E', 'F', 'UP', 'B', 'SIM', 'I'];\n\ninterface PythonProjectRuffConfig {\n /** Project root, normalised to use forward slashes. */\n readonly root: string;\n /** Top-level importable module names declared by the project. */\n readonly modules: string[];\n /** The project's `[tool.ruff].line-length`, if set. */\n readonly lineLength?: number;\n /**\n * Ruff `target-version` (eg `py314`) derived from `[project].requires-python`.\n * Generation formats via stdin with no pyproject, so it must be passed\n * explicitly — ruff's formatting differs by target.\n */\n readonly targetVersion?: string;\n /** The project's `[tool.ruff.lint].select`, if set. */\n readonly select?: string[];\n}\n\n/**\n * Derive ruff's `target-version` (eg `py314`) from a PEP 508\n * `requires-python` specifier (eg `>=3.14`). Ruff targets the minimum\n * supported version, so take the lowest `major.minor` mentioned.\n */\nexport const requiresPythonToRuffTarget = (\n requiresPython: unknown,\n): string | undefined => {\n if (typeof requiresPython !== 'string') {\n return undefined;\n }\n let min: { major: number; minor: number } | undefined;\n for (const match of requiresPython.matchAll(/(\\d+)\\.(\\d+)/g)) {\n const major = Number(match[1]);\n const minor = Number(match[2]);\n if (\n !min ||\n major < min.major ||\n (major === min.major && minor < min.minor)\n ) {\n min = { major, minor };\n }\n }\n return min ? `py${min.major}${min.minor}` : undefined;\n};\n\n/**\n * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk\n * build enforces for it: its top-level module names (from\n * `[tool.hatch.build.targets.wheel].packages`), its `[tool.ruff].line-length`\n * and its `[tool.ruff.lint].select`.\n */\nfunction getPythonProjectRuffConfigs(tree: Tree): PythonProjectRuffConfig[] {\n const configs: PythonProjectRuffConfig[] = [];\n\n for (const project of getProjects(tree).values()) {\n // Projects without a pyproject.toml, or whose one cannot be parsed, have no\n // ruff settings to contribute.\n const pyproject = tryReadToml(\n tree,\n path.join(project.root, 'pyproject.toml'),\n ) as any;\n if (!pyproject) {\n continue;\n }\n const wheelPackages: unknown =\n pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;\n // Record the top-level module segment (`pkg/sub` -> `pkg`), which is\n // all `known-first-party` keys off.\n const modules = Array.isArray(wheelPackages)\n ? wheelPackages\n .filter((pkg): pkg is string => typeof pkg === 'string' && !!pkg)\n .map((pkg) => pkg.split('/')[0])\n : [];\n const lineLength: unknown = pyproject?.tool?.ruff?.['line-length'];\n const targetVersion = requiresPythonToRuffTarget(\n pyproject?.project?.['requires-python'],\n );\n const selected: unknown = pyproject?.tool?.ruff?.lint?.select;\n const select = Array.isArray(selected)\n ? selected.filter(\n (rule): rule is string => typeof rule === 'string' && !!rule,\n )\n : undefined;\n if (\n modules.length ||\n typeof lineLength === 'number' ||\n targetVersion ||\n select?.length\n ) {\n configs.push({\n root: project.root.split(path.sep).join('/'),\n modules,\n lineLength: typeof lineLength === 'number' ? lineLength : undefined,\n targetVersion,\n select: select?.length ? select : undefined,\n });\n }\n }\n\n return configs;\n}\n\n/**\n * Resolve the ruff config for the project that owns a file (the project with\n * the longest root that is a prefix of the file path). Ruff runs per-project on\n * disk, so a file's settings come from its own project — only its own module is\n * first-party (sibling workspace packages are third-party) and its own\n * line-length applies — and scoping this way keeps in-tree formatting\n * consistent with the on-disk build.\n */\nfunction getOwningProjectRuffConfig(\n filePath: string,\n configs: PythonProjectRuffConfig[],\n): PythonProjectRuffConfig | undefined {\n let owner: PythonProjectRuffConfig | undefined;\n for (const config of configs) {\n if (\n (filePath === config.root || filePath.startsWith(`${config.root}/`)) &&\n (!owner || config.root.length > owner.root.length)\n ) {\n owner = config;\n }\n }\n return owner;\n}\n\n/**\n * Resolve the ruff settings to format a Python file with, mirroring what the\n * file's build enforces.\n *\n * Ruff's own config discovery never runs: settings are passed to the linter\n * directly rather than resolved from the file's location, which it never sees.\n * That also means a stray config above the workspace (or in the home directory)\n * can never be picked up.\n *\n * `config` is the file's nearest ruff config, whose rule selection is honoured\n * as-is. Without one, ruff would fall back to its own defaults, which change\n * between releases — 0.16 widened them from `E` and `F` to 36 rule prefixes — so\n * generation would apply fixes the project's build never asks for (eg `RUF022`\n * reordering `__all__`). Instead the project's own `lint.select` is pinned, so\n * generation enforces exactly what `lint` does and nothing more.\n *\n * `projectConfig` layers on the settings derived from the owning project rather\n * than declared under `[tool.ruff]`: `known-first-party` (the project's own\n * modules, from its wheel packages) keeps its imports in their own group, and\n * `target-version` (from its `requires-python`) matches the formatting the build\n * produces.\n */\nfunction resolveRuffOptions(\n config: RuffOptions | undefined,\n projectConfig?: PythonProjectRuffConfig,\n): RuffOptions {\n const lint: Record<string, unknown> = { ...config?.lint };\n // Pin the rule selection only when deferring to ruff's defaults would\n // otherwise apply rules the project's build does not enforce.\n if (!config) {\n lint.select = projectConfig?.select ?? DEFAULT_RUFF_SELECT;\n }\n if (projectConfig?.modules.length) {\n lint.isort = {\n ...(lint.isort as object | undefined),\n 'known-first-party': projectConfig.modules,\n };\n }\n return {\n ...config,\n ...(typeof projectConfig?.lineLength === 'number'\n ? { 'line-length': projectConfig.lineLength }\n : {}),\n ...(projectConfig?.targetVersion\n ? { 'target-version': projectConfig.targetVersion }\n : {}),\n lint,\n };\n}\n"],"names":["Biome","getProjects","createRequire","path","ruffFixAndFormat","tryReadToml","TS_VERSIONS","BIOME_WASM_VERSION","url","version","BIOME_TEST_OUTPUT_EXCLUDE","ROLLDOWN_CONFIG_BUNDLE_GLOB","BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE","getDefaultBiomeConfig","tree","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","exists","resolver","experimentalPnpmCatalogs","css","linter","rules","preset","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","overrides","BIOME_FORMATTABLE_EXTENSIONS","Set","isTsConfig","filePath","test","treeIgnoredPaths","WeakMap","normalizeTreePath","split","sep","join","replace","formatFilesInSubtree","dir","options","ignored","get","ignore","length","set","add","changedFiles","listChanges","filter","file","type","startsWith","has","pyFiles","endsWith","otherFiles","extname","pythonProjectConfigs","getPythonProjectRuffConfigs","content","toString","resolveRuffOptions","readRuffConfig","getOwningProjectRuffConfig","write","formatWithBiome","readBiomeConfig","config","read","JSON","parse","biome","projectKey","openProject","applyConfiguration","formatContent","dirname","name","ruff","tool","undefined","DEFAULT_RUFF_SELECT","requiresPythonToRuffTarget","requiresPython","min","match","matchAll","major","Number","minor","configs","project","values","pyproject","wheelPackages","hatch","build","targets","wheel","packages","modules","Array","isArray","pkg","map","lineLength","targetVersion","selected","lint","select","rule","push","owner","projectConfig","isort"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport { getProjects, type Tree } from '@nx/devkit';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { type RuffOptions, ruffFixAndFormat } from './ruff.js';\nimport { tryReadToml } from './toml.js';\nimport { TS_VERSIONS } from './versions.js';\n\n/**\n * The Biome release the wasm bindings are built from. Must match\n * `TS_VERSIONS['@biomejs/biome']`, the CLI the vended targets run.\n */\nexport const BIOME_WASM_VERSION: string = createRequire(import.meta.url)(\n '@biomejs/wasm-nodejs/package.json',\n).version;\n\n/**\n * Excludes test reports — including the vendored scripts vitest's coverage HTML\n * reporter writes — from formatting and linting.\n */\nexport const BIOME_TEST_OUTPUT_EXCLUDE = '!**/test-output';\n\n/**\n * The transient bundle rolldown writes beside a `rolldown.config.ts` while\n * loading it.\n *\n * Loading a TypeScript config bundles it to `rolldown.config.<hash>.js` in the\n * config's own directory, imports it, then unlinks it. That file exists for as\n * long as the bundle takes, so a `format` or `lint` target running concurrently\n * in the same project sees a machine-generated file — reported unformatted,\n * failing the target for no fault of the source.\n *\n * `.*js` rather than `.js`, since the extension follows the config's own: a\n * `.mts` config bundles to `.mjs`, a `.cts` one to `.cjs`.\n */\nexport const ROLLDOWN_CONFIG_BUNDLE_GLOB = '**/rolldown.config.*.*js';\n\n/** Keeps rolldown's transient config bundle out of formatting and linting. */\nexport const BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE = `!${ROLLDOWN_CONFIG_BUNDLE_GLOB}`;\n\n/**\n * The biome.json vended into a new workspace. The pnpm catalog resolver is only\n * included on pnpm workspaces, since `experimentalPnpmCatalogs` is Biome's only\n * catalog resolver and reads `pnpm-workspace.yaml` exclusively — it does nothing\n * for yarn or bun catalogs, so vending it there would be misleading.\n */\nexport const getDefaultBiomeConfig = (tree: Tree) => ({\n $schema: `https://biomejs.dev/schemas/${TS_VERSIONS['@biomejs/biome']}/schema.json`,\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n // Resolve `catalog:` versions from pnpm-workspace.yaml (pnpm workspaces only).\n ...(tree.exists('pnpm-workspace.yaml')\n ? { resolver: { experimentalPnpmCatalogs: true } }\n : {}),\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n // Markdown and YAML files are left as written.\n markdown: { formatter: { enabled: false } },\n yaml: { formatter: { enabled: false } },\n linter: {\n enabled: true,\n rules: {\n preset: 'none',\n correctness: {\n // Every project must declare the third-party dependencies its source\n // code imports in its own package.json.\n noUndeclaredDependencies: 'error',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n BIOME_TEST_OUTPUT_EXCLUDE,\n BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE,\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n // GritQL codemod cache written by generators — its sample sources\n // otherwise pollute a bare `biome check .` with parse errors.\n '!**/.grit',\n '!**/*.css',\n '!**/*.gen.*',\n '!**/generated/**',\n '!**/tsconfig*.json',\n ],\n },\n // Config files, build scripts and tests use root tooling rather than\n // declaring it per-project, so the undeclared-dependency rule is off for them.\n overrides: [\n {\n includes: [\n '**/*.config.{ts,mts,cts,js,mjs,cjs}',\n '**/*.{spec,test}.{ts,tsx,mts,cts,js,jsx,mjs,cjs}',\n '**/*.stories.{ts,tsx}',\n ],\n linter: {\n rules: {\n correctness: {\n noUndeclaredDependencies: 'off',\n },\n },\n },\n },\n ],\n});\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/** Matches `tsconfig.json` and variants like `tsconfig.lib.json`. */\nconst isTsConfig = (filePath: string): boolean =>\n /(^|\\/)tsconfig[^/]*\\.json$/.test(filePath);\n\nexport interface FormatFilesInSubtreeOptions {\n /**\n * Paths, relative to the workspace root, to leave untouched.\n *\n * For files another tool owns the formatting of: formatting them here makes\n * generation non-idempotent, since the tool rewrites them in its own shape on\n * the next run and the workspace flips between the two forms.\n */\n readonly ignore?: readonly string[];\n}\n\n/**\n * Paths declared ignored for a tree, which stay ignored for every later call.\n *\n * A call formats every change pending in the tree, not only the ones its caller\n * made, so the list cannot be scoped to the call that passes it: generators\n * composing on one tree would reformat a file an earlier generator had excluded.\n * Once the generator that owns a file declares it ignored, it stays ignored for\n * the rest of the run.\n */\nconst treeIgnoredPaths = new WeakMap<Tree, Set<string>>();\n\n/**\n * Tree paths are workspace-relative and forward-slash separated. Normalise so a\n * caller building a path with `path.join` on Windows still matches.\n */\nconst normalizeTreePath = (filePath: string): string =>\n filePath.split(path.sep).join('/').replace(/^\\.\\//, '');\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n options?: FormatFilesInSubtreeOptions,\n): Promise<void> {\n let ignored = treeIgnoredPaths.get(tree);\n if (options?.ignore?.length) {\n if (!ignored) {\n ignored = new Set();\n treeIgnoredPaths.set(tree, ignored);\n }\n for (const filePath of options.ignore) {\n ignored.add(normalizeTreePath(filePath));\n }\n }\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true))\n .filter((file) => !ignored?.has(normalizeTreePath(file.path)));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter(\n (file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)) &&\n // tsconfigs are not biome-managed: they're excluded from the vended\n // format target (Nx's typescript-sync rewrites them without formatting),\n // so formatting them at generation would only diverge from the form\n // written on later runs. Leave them as updateJson/writeJson emit them so\n // repeated generation stays idempotent.\n !isTsConfig(file.path),\n );\n\n // Resolve each project's ruff settings (module names, line-length) so files\n // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).\n const pythonProjectConfigs = pyFiles.length\n ? getPythonProjectRuffConfigs(tree)\n : [];\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n resolveRuffOptions(\n readRuffConfig(tree, file.path),\n getOwningProjectRuffConfig(file.path, pythonProjectConfigs),\n ),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n formatWithBiome(tree, otherFiles);\n}\n\n/**\n * The Biome configuration to format with: the workspace's own `biome.json`, else\n * the config we vend.\n *\n * Read through the tree, which falls back to disk for a file it doesn't hold. So\n * this resolves the most current config either way — the workspace's on-disk one,\n * or the version a generator has just written to the tree and not yet flushed,\n * which shelling out to the CLI could never see.\n */\nfunction readBiomeConfig(tree: Tree): unknown {\n const config = tree.read('biome.json', 'utf-8');\n if (config) {\n try {\n return JSON.parse(config);\n } catch {\n // Malformed config — fall through to the config we vend\n }\n }\n return getDefaultBiomeConfig(tree);\n}\n\n/**\n * Format files with Biome in-process, reusing one instance across the batch.\n *\n * Replaces one `biome format --stdin-file-path` process per file, which\n * dominated generation at ~70ms each: `formatFilesInSubtree` formats every\n * change accumulated in the tree, not only the ones its caller made, so\n * generators sharing a tree reformat the same files once per call. In-process is\n * ~0.5ms per file. Output was verified byte-identical across the plugin's whole\n * source tree, except that the CLI corrupts control characters passed through\n * stdin (a NUL in a template literal) where formatting in-process preserves them.\n */\nfunction formatWithBiome(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n biome.applyConfiguration(projectKey, readBiomeConfig(tree));\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\n/**\n * Read the ruff config for a file, by walking from its directory up to the\n * workspace root looking for `.ruff.toml`, `ruff.toml`, or a `pyproject.toml`\n * with a `[tool.ruff]` section — the same files, in the same order, that ruff\n * itself resolves. The walk stops at the workspace root so a stray config in a\n * parent of the workspace (or the home directory) is never treated as the\n * project's.\n *\n * The settings are read rather than merely detected because formatting runs\n * in-process against tree content, so ruff never sees the file's location and\n * cannot resolve the config itself (see {@link resolveRuffOptions}).\n *\n * Reads go through the tree, which falls through to disk for files this run has\n * not touched, so a config the generator has just written in memory is picked up\n * as well as one already on disk.\n */\nfunction readRuffConfig(tree: Tree, filePath: string): RuffOptions | undefined {\n let dir = path.dirname(filePath);\n while (true) {\n for (const name of ['.ruff.toml', 'ruff.toml']) {\n const config = tryReadToml(tree, path.join(dir, name));\n if (config) {\n return config as RuffOptions;\n }\n }\n const ruff = (tryReadToml(tree, path.join(dir, 'pyproject.toml')) as any)\n ?.tool?.ruff;\n if (ruff) {\n return ruff as RuffOptions;\n }\n // Stop once the workspace root ('.') has been checked.\n if (dir === '.' || dir === '' || dir === path.dirname(dir)) {\n return undefined;\n }\n dir = path.dirname(dir);\n }\n}\n\n/**\n * The `[tool.ruff.lint].select` generated Python projects vend. Generation\n * formats before that config lands on disk, so it is pinned here to keep\n * generated files clean under the project's own `lint` target rather than under\n * whatever ruff's defaults happen to be for the pinned release.\n */\nconst DEFAULT_RUFF_SELECT = ['E', 'F', 'UP', 'B', 'SIM', 'I'];\n\ninterface PythonProjectRuffConfig {\n /** Project root, normalised to use forward slashes. */\n readonly root: string;\n /** Top-level importable module names declared by the project. */\n readonly modules: string[];\n /** The project's `[tool.ruff].line-length`, if set. */\n readonly lineLength?: number;\n /**\n * Ruff `target-version` (eg `py314`) derived from `[project].requires-python`.\n * Generation formats via stdin with no pyproject, so it must be passed\n * explicitly — ruff's formatting differs by target.\n */\n readonly targetVersion?: string;\n /** The project's `[tool.ruff.lint].select`, if set. */\n readonly select?: string[];\n}\n\n/**\n * Derive ruff's `target-version` (eg `py314`) from a PEP 508\n * `requires-python` specifier (eg `>=3.14`). Ruff targets the minimum\n * supported version, so take the lowest `major.minor` mentioned.\n */\nexport const requiresPythonToRuffTarget = (\n requiresPython: unknown,\n): string | undefined => {\n if (typeof requiresPython !== 'string') {\n return undefined;\n }\n let min: { major: number; minor: number } | undefined;\n for (const match of requiresPython.matchAll(/(\\d+)\\.(\\d+)/g)) {\n const major = Number(match[1]);\n const minor = Number(match[2]);\n if (\n !min ||\n major < min.major ||\n (major === min.major && minor < min.minor)\n ) {\n min = { major, minor };\n }\n }\n return min ? `py${min.major}${min.minor}` : undefined;\n};\n\n/**\n * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk\n * build enforces for it: its top-level module names (from\n * `[tool.hatch.build.targets.wheel].packages`), its `[tool.ruff].line-length`\n * and its `[tool.ruff.lint].select`.\n */\nfunction getPythonProjectRuffConfigs(tree: Tree): PythonProjectRuffConfig[] {\n const configs: PythonProjectRuffConfig[] = [];\n\n for (const project of getProjects(tree).values()) {\n // Projects without a pyproject.toml, or whose one cannot be parsed, have no\n // ruff settings to contribute.\n const pyproject = tryReadToml(\n tree,\n path.join(project.root, 'pyproject.toml'),\n ) as any;\n if (!pyproject) {\n continue;\n }\n const wheelPackages: unknown =\n pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;\n // Record the top-level module segment (`pkg/sub` -> `pkg`), which is\n // all `known-first-party` keys off.\n const modules = Array.isArray(wheelPackages)\n ? wheelPackages\n .filter((pkg): pkg is string => typeof pkg === 'string' && !!pkg)\n .map((pkg) => pkg.split('/')[0])\n : [];\n const lineLength: unknown = pyproject?.tool?.ruff?.['line-length'];\n const targetVersion = requiresPythonToRuffTarget(\n pyproject?.project?.['requires-python'],\n );\n const selected: unknown = pyproject?.tool?.ruff?.lint?.select;\n const select = Array.isArray(selected)\n ? selected.filter(\n (rule): rule is string => typeof rule === 'string' && !!rule,\n )\n : undefined;\n if (\n modules.length ||\n typeof lineLength === 'number' ||\n targetVersion ||\n select?.length\n ) {\n configs.push({\n root: project.root.split(path.sep).join('/'),\n modules,\n lineLength: typeof lineLength === 'number' ? lineLength : undefined,\n targetVersion,\n select: select?.length ? select : undefined,\n });\n }\n }\n\n return configs;\n}\n\n/**\n * Resolve the ruff config for the project that owns a file (the project with\n * the longest root that is a prefix of the file path). Ruff runs per-project on\n * disk, so a file's settings come from its own project — only its own module is\n * first-party (sibling workspace packages are third-party) and its own\n * line-length applies — and scoping this way keeps in-tree formatting\n * consistent with the on-disk build.\n */\nfunction getOwningProjectRuffConfig(\n filePath: string,\n configs: PythonProjectRuffConfig[],\n): PythonProjectRuffConfig | undefined {\n let owner: PythonProjectRuffConfig | undefined;\n for (const config of configs) {\n if (\n (filePath === config.root || filePath.startsWith(`${config.root}/`)) &&\n (!owner || config.root.length > owner.root.length)\n ) {\n owner = config;\n }\n }\n return owner;\n}\n\n/**\n * Resolve the ruff settings to format a Python file with, mirroring what the\n * file's build enforces.\n *\n * Ruff's own config discovery never runs: settings are passed to the linter\n * directly rather than resolved from the file's location, which it never sees.\n * That also means a stray config above the workspace (or in the home directory)\n * can never be picked up.\n *\n * `config` is the file's nearest ruff config, whose rule selection is honoured\n * as-is. Without one, ruff would fall back to its own defaults, which change\n * between releases — 0.16 widened them from `E` and `F` to 36 rule prefixes — so\n * generation would apply fixes the project's build never asks for (eg `RUF022`\n * reordering `__all__`). Instead the project's own `lint.select` is pinned, so\n * generation enforces exactly what `lint` does and nothing more.\n *\n * `projectConfig` layers on the settings derived from the owning project rather\n * than declared under `[tool.ruff]`: `known-first-party` (the project's own\n * modules, from its wheel packages) keeps its imports in their own group, and\n * `target-version` (from its `requires-python`) matches the formatting the build\n * produces.\n */\nfunction resolveRuffOptions(\n config: RuffOptions | undefined,\n projectConfig?: PythonProjectRuffConfig,\n): RuffOptions {\n const lint: Record<string, unknown> = { ...config?.lint };\n // Pin the rule selection only when deferring to ruff's defaults would\n // otherwise apply rules the project's build does not enforce.\n if (!config) {\n lint.select = projectConfig?.select ?? DEFAULT_RUFF_SELECT;\n }\n if (projectConfig?.modules.length) {\n lint.isort = {\n ...(lint.isort as object | undefined),\n 'known-first-party': projectConfig.modules,\n };\n }\n return {\n ...config,\n ...(typeof projectConfig?.lineLength === 'number'\n ? { 'line-length': projectConfig.lineLength }\n : {}),\n ...(projectConfig?.targetVersion\n ? { 'target-version': projectConfig.targetVersion }\n : {}),\n lint,\n };\n}\n"],"names":["Biome","getProjects","createRequire","path","ruffFixAndFormat","tryReadToml","TS_VERSIONS","BIOME_WASM_VERSION","url","version","BIOME_TEST_OUTPUT_EXCLUDE","ROLLDOWN_CONFIG_BUNDLE_GLOB","BIOME_ROLLDOWN_CONFIG_BUNDLE_EXCLUDE","getDefaultBiomeConfig","tree","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","exists","resolver","experimentalPnpmCatalogs","css","linter","markdown","yaml","rules","preset","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","overrides","BIOME_FORMATTABLE_EXTENSIONS","Set","isTsConfig","filePath","test","treeIgnoredPaths","WeakMap","normalizeTreePath","split","sep","join","replace","formatFilesInSubtree","dir","options","ignored","get","ignore","length","set","add","changedFiles","listChanges","filter","file","type","startsWith","has","pyFiles","endsWith","otherFiles","extname","pythonProjectConfigs","getPythonProjectRuffConfigs","content","toString","resolveRuffOptions","readRuffConfig","getOwningProjectRuffConfig","write","formatWithBiome","readBiomeConfig","config","read","JSON","parse","biome","projectKey","openProject","applyConfiguration","formatContent","dirname","name","ruff","tool","undefined","DEFAULT_RUFF_SELECT","requiresPythonToRuffTarget","requiresPython","min","match","matchAll","major","Number","minor","configs","project","values","pyproject","wheelPackages","hatch","build","targets","wheel","packages","modules","Array","isArray","pkg","map","lineLength","targetVersion","selected","lint","select","rule","push","owner","projectConfig","isort"],"mappings":"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"}
@@ -8,6 +8,10 @@ terraform {
8
8
  source = "hashicorp/random"
9
9
  version = "<%- randomProviderVersion %>"
10
10
  }
11
+ time = {
12
+ source = "hashicorp/time"
13
+ version = "<%- timeProviderVersion %>"
14
+ }
11
15
  }
12
16
  }
13
17
 
@@ -72,12 +76,25 @@ resource "aws_bedrock_guardrail_version" "this" {
72
76
  }
73
77
  }
74
78
 
79
+ resource "time_sleep" "version_retention" {
80
+ destroy_duration = "15s"
81
+
82
+ triggers = {
83
+ guardrail_id = aws_bedrock_guardrail.this.guardrail_id
84
+ guardrail_version = aws_bedrock_guardrail_version.this.version
85
+ }
86
+
87
+ lifecycle {
88
+ create_before_destroy = true
89
+ }
90
+ }
91
+
75
92
  module "add_guardrail_to_runtime_config" {
76
93
  source = "../../../core/runtime-config/entry"
77
94
 
78
95
  namespace = "agentcore"
79
96
  key = "guardrails"
80
- value = { "<%= nameClassName %>" = { guardrailId = aws_bedrock_guardrail.this.guardrail_id, guardrailVersion = aws_bedrock_guardrail_version.this.version } }
97
+ value = { "<%= nameClassName %>" = { guardrailId = time_sleep.version_retention.triggers.guardrail_id, guardrailVersion = time_sleep.version_retention.triggers.guardrail_version } }
81
98
  }
82
99
 
83
100
  output "guardrail_id" {
@@ -16,6 +16,11 @@ export interface AddGuardrailInfraOptions {
16
16
  /** Workspace-root-relative path of the guardrail.json policy file. */
17
17
  policyPath: string;
18
18
  }
19
+ /**
20
+ * Write the guardrail.json policy next to the agent. The policy is the
21
+ * user's to edit, so a re-run never touches an existing file.
22
+ */
23
+ export declare const addGuardrailPolicy: (tree: Tree, agentDir: string) => void;
19
24
  /**
20
25
  * Add the Amazon Bedrock Guardrail infrastructure for the selected IaC
21
26
  * provider: a CDK construct in the shared constructs project, or a Terraform
@@ -6,6 +6,14 @@ import { addStarExport } from "../ast.js";
6
6
  import { esmVars } from "../module-format.js";
7
7
  import { PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../shared-constructs-constants.js";
8
8
  import { terraformProviderVersions } from "../versions.js";
9
+ /**
10
+ * Write the guardrail.json policy next to the agent. The policy is the
11
+ * user's to edit, so a re-run never touches an existing file.
12
+ */ export const addGuardrailPolicy = (tree, agentDir)=>{
13
+ generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'policy'), agentDir, {}, {
14
+ overwriteStrategy: OverwriteStrategy.KeepExisting
15
+ });
16
+ };
9
17
  /**
10
18
  * Add the Amazon Bedrock Guardrail infrastructure for the selected IaC
11
19
  * provider: a CDK construct in the shared constructs project, or a Terraform
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/guardrail-constructs/guardrail-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 Tree,\n} from '@nx/devkit';\nimport { addStarExport } from '../ast.js';\nimport type { Iac } from '../iac.js';\nimport { esmVars } from '../module-format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport { terraformProviderVersions } from '../versions.js';\n\nexport interface AddGuardrailInfraOptions {\n iac: Iac;\n /** Class name of the guarded agent, eg `MyAgent` gives `MyAgentGuardrail`. */\n nameClassName: string;\n /**\n * Kebab-case name of the guarded agent, used for the directory and, with a\n * `-guardrail` suffix, the file.\n */\n nameKebabCase: string;\n /** Workspace-root-relative path of the guardrail.json policy file. */\n policyPath: string;\n}\n\n/**\n * Add the Amazon Bedrock Guardrail infrastructure for the selected IaC\n * provider: a CDK construct in the shared constructs project, or a Terraform\n * module in the shared Terraform project. Both read the policy file at synth\n * or plan time.\n *\n * Files are written with `KeepExisting`, so a re-run leaves them unchanged.\n * No build dependency is added on the agent's project, since the\n * infrastructure reads a source file and imports none of the agent's code;\n * for CDK the generator lists that file as a `synth` input instead, so the\n * infrastructure's cached synth follows it.\n */\nexport const addGuardrailInfra = async (\n tree: Tree,\n options: AddGuardrailInfraOptions,\n): Promise<void> => {\n const templateContext = {\n nameClassName: options.nameClassName,\n nameKebabCase: options.nameKebabCase,\n policyPath: options.policyPath,\n };\n\n switch (options.iac) {\n case 'cdk': {\n const appDir = joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n );\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'app',\n 'guardrails',\n ),\n joinPathFragments(appDir, 'guardrails'),\n { ...templateContext, ...esmVars(tree) },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n await addStarExport(\n tree,\n joinPathFragments(appDir, 'guardrails', 'index.ts'),\n `./${options.nameKebabCase}/${options.nameKebabCase}-guardrail.js`,\n );\n await addStarExport(\n tree,\n joinPathFragments(appDir, 'index.ts'),\n './guardrails/index.js',\n );\n break;\n }\n case 'terraform':\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'app',\n 'guardrails',\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'app',\n 'guardrails',\n ),\n {\n ...templateContext,\n // Guardrail names are at most 50 characters: the prefix, a hyphen\n // and the 8 hex characters of the random suffix.\n guardrailNamePrefix: `${options.nameKebabCase}-guardrail`.slice(\n 0,\n 41,\n ),\n ...terraformProviderVersions(),\n },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n break;\n }\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","addStarExport","esmVars","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","terraformProviderVersions","addGuardrailInfra","tree","options","templateContext","nameClassName","nameKebabCase","policyPath","iac","appDir","dirname","overwriteStrategy","KeepExisting","guardrailNamePrefix","slice"],"mappings":"AAAA;;;CAGC,GACD,SACEA,aAAa,EACbC,iBAAiB,EACjBC,iBAAiB,QAEZ,aAAa;AACpB,SAASC,aAAa,QAAQ,YAAY;AAE1C,SAASC,OAAO,QAAQ,sBAAsB;AAC9C,SACEC,YAAY,EACZC,qBAAqB,EACrBC,oBAAoB,QACf,oCAAoC;AAC3C,SAASC,yBAAyB,QAAQ,iBAAiB;AAe3D;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,oBAAoB,OAC/BC,MACAC;IAEA,MAAMC,kBAAkB;QACtBC,eAAeF,QAAQE,aAAa;QACpCC,eAAeH,QAAQG,aAAa;QACpCC,YAAYJ,QAAQI,UAAU;IAChC;IAEA,OAAQJ,QAAQK,GAAG;QACjB,KAAK;YAAO;gBACV,MAAMC,SAAShB,kBACbI,cACAC,uBACA,OACA;gBAEFN,cACEU,MACAT,kBACE,YAAYiB,OAAO,EACnB,SACA,OACA,OACA,eAEFjB,kBAAkBgB,QAAQ,eAC1B;oBAAE,GAAGL,eAAe;oBAAE,GAAGR,QAAQM,KAAK;gBAAC,GACvC;oBAAES,mBAAmBjB,kBAAkBkB,YAAY;gBAAC;gBAEtD,MAAMjB,cACJO,MACAT,kBAAkBgB,QAAQ,cAAc,aACxC,CAAC,EAAE,EAAEN,QAAQG,aAAa,CAAC,CAAC,EAAEH,QAAQG,aAAa,CAAC,aAAa,CAAC;gBAEpE,MAAMX,cACJO,MACAT,kBAAkBgB,QAAQ,aAC1B;gBAEF;YACF;QACA,KAAK;YACHjB,cACEU,MACAT,kBACE,YAAYiB,OAAO,EACnB,SACA,aACA,OACA,eAEFjB,kBACEI,cACAE,sBACA,OACA,OACA,eAEF;gBACE,GAAGK,eAAe;gBAClB,kEAAkE;gBAClE,iDAAiD;gBACjDS,qBAAqB,GAAGV,QAAQG,aAAa,CAAC,UAAU,CAAC,CAACQ,KAAK,CAC7D,GACA;gBAEF,GAAGd,2BAA2B;YAChC,GACA;gBAAEW,mBAAmBjB,kBAAkBkB,YAAY;YAAC;YAEtD;IACJ;AACF,EAAE"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/guardrail-constructs/guardrail-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 Tree,\n} from '@nx/devkit';\nimport { addStarExport } from '../ast.js';\nimport type { Iac } from '../iac.js';\nimport { esmVars } from '../module-format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport { terraformProviderVersions } from '../versions.js';\n\nexport interface AddGuardrailInfraOptions {\n iac: Iac;\n /** Class name of the guarded agent, eg `MyAgent` gives `MyAgentGuardrail`. */\n nameClassName: string;\n /**\n * Kebab-case name of the guarded agent, used for the directory and, with a\n * `-guardrail` suffix, the file.\n */\n nameKebabCase: string;\n /** Workspace-root-relative path of the guardrail.json policy file. */\n policyPath: string;\n}\n\n/**\n * Write the guardrail.json policy next to the agent. The policy is the\n * user's to edit, so a re-run never touches an existing file.\n */\nexport const addGuardrailPolicy = (tree: Tree, agentDir: string): void => {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'policy'),\n agentDir,\n {},\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n};\n\n/**\n * Add the Amazon Bedrock Guardrail infrastructure for the selected IaC\n * provider: a CDK construct in the shared constructs project, or a Terraform\n * module in the shared Terraform project. Both read the policy file at synth\n * or plan time.\n *\n * Files are written with `KeepExisting`, so a re-run leaves them unchanged.\n * No build dependency is added on the agent's project, since the\n * infrastructure reads a source file and imports none of the agent's code;\n * for CDK the generator lists that file as a `synth` input instead, so the\n * infrastructure's cached synth follows it.\n */\nexport const addGuardrailInfra = async (\n tree: Tree,\n options: AddGuardrailInfraOptions,\n): Promise<void> => {\n const templateContext = {\n nameClassName: options.nameClassName,\n nameKebabCase: options.nameKebabCase,\n policyPath: options.policyPath,\n };\n\n switch (options.iac) {\n case 'cdk': {\n const appDir = joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n );\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'app',\n 'guardrails',\n ),\n joinPathFragments(appDir, 'guardrails'),\n { ...templateContext, ...esmVars(tree) },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n await addStarExport(\n tree,\n joinPathFragments(appDir, 'guardrails', 'index.ts'),\n `./${options.nameKebabCase}/${options.nameKebabCase}-guardrail.js`,\n );\n await addStarExport(\n tree,\n joinPathFragments(appDir, 'index.ts'),\n './guardrails/index.js',\n );\n break;\n }\n case 'terraform':\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'app',\n 'guardrails',\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'app',\n 'guardrails',\n ),\n {\n ...templateContext,\n // Guardrail names are at most 50 characters: the prefix, a hyphen\n // and the 8 hex characters of the random suffix.\n guardrailNamePrefix: `${options.nameKebabCase}-guardrail`.slice(\n 0,\n 41,\n ),\n ...terraformProviderVersions(),\n },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n break;\n }\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","addStarExport","esmVars","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","terraformProviderVersions","addGuardrailPolicy","tree","agentDir","dirname","overwriteStrategy","KeepExisting","addGuardrailInfra","options","templateContext","nameClassName","nameKebabCase","policyPath","iac","appDir","guardrailNamePrefix","slice"],"mappings":"AAAA;;;CAGC,GACD,SACEA,aAAa,EACbC,iBAAiB,EACjBC,iBAAiB,QAEZ,aAAa;AACpB,SAASC,aAAa,QAAQ,YAAY;AAE1C,SAASC,OAAO,QAAQ,sBAAsB;AAC9C,SACEC,YAAY,EACZC,qBAAqB,EACrBC,oBAAoB,QACf,oCAAoC;AAC3C,SAASC,yBAAyB,QAAQ,iBAAiB;AAe3D;;;CAGC,GACD,OAAO,MAAMC,qBAAqB,CAACC,MAAYC;IAC7CX,cACEU,MACAT,kBAAkB,YAAYW,OAAO,EAAE,SAAS,WAChDD,UACA,CAAC,GACD;QAAEE,mBAAmBX,kBAAkBY,YAAY;IAAC;AAExD,EAAE;AAEF;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,oBAAoB,OAC/BL,MACAM;IAEA,MAAMC,kBAAkB;QACtBC,eAAeF,QAAQE,aAAa;QACpCC,eAAeH,QAAQG,aAAa;QACpCC,YAAYJ,QAAQI,UAAU;IAChC;IAEA,OAAQJ,QAAQK,GAAG;QACjB,KAAK;YAAO;gBACV,MAAMC,SAASrB,kBACbI,cACAC,uBACA,OACA;gBAEFN,cACEU,MACAT,kBACE,YAAYW,OAAO,EACnB,SACA,OACA,OACA,eAEFX,kBAAkBqB,QAAQ,eAC1B;oBAAE,GAAGL,eAAe;oBAAE,GAAGb,QAAQM,KAAK;gBAAC,GACvC;oBAAEG,mBAAmBX,kBAAkBY,YAAY;gBAAC;gBAEtD,MAAMX,cACJO,MACAT,kBAAkBqB,QAAQ,cAAc,aACxC,CAAC,EAAE,EAAEN,QAAQG,aAAa,CAAC,CAAC,EAAEH,QAAQG,aAAa,CAAC,aAAa,CAAC;gBAEpE,MAAMhB,cACJO,MACAT,kBAAkBqB,QAAQ,aAC1B;gBAEF;YACF;QACA,KAAK;YACHtB,cACEU,MACAT,kBACE,YAAYW,OAAO,EACnB,SACA,aACA,OACA,eAEFX,kBACEI,cACAE,sBACA,OACA,OACA,eAEF;gBACE,GAAGU,eAAe;gBAClB,kEAAkE;gBAClE,iDAAiD;gBACjDM,qBAAqB,GAAGP,QAAQG,aAAa,CAAC,UAAU,CAAC,CAACK,KAAK,CAC7D,GACA;gBAEF,GAAGhB,2BAA2B;YAChC,GACA;gBAAEK,mBAAmBX,kBAAkBY,YAAY;YAAC;YAEtD;IACJ;AACF,EAAE"}
@@ -11,9 +11,16 @@ export interface GuardrailWiringOptions {
11
11
  nameKebabCase: string;
12
12
  /** npm scope of the workspace without the `@`, eg `my-scope`. */
13
13
  npmScope: string;
14
+ /**
15
+ * The agent guide section that shows how to deploy the agent's Terraform
16
+ * module. Defaults to the ts#agent guide.
17
+ */
18
+ deployGuideUrl?: string;
14
19
  }
15
20
  /** The ts#agent guide section that shows how to deploy the agent's Terraform module. */
16
21
  export declare const TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL = "https://awslabs.github.io/nx-plugin-for-aws/en/guides/ts-agent/#infrastructure-as-code";
22
+ /** The py#agent guide section that shows how to deploy the agent's Terraform module. */
23
+ export declare const PY_AGENT_TERRAFORM_DEPLOY_GUIDE_URL = "https://awslabs.github.io/nx-plugin-for-aws/en/guides/py-agent/#infrastructure-as-code";
17
24
  /**
18
25
  * The code a user adds to their CDK stack to guard the agent.
19
26
  * The guide shows the same code for `my-agent` in scope `my-scope`, which
@@ -4,6 +4,7 @@
4
4
  */ import { snakeCase } from "../names.js";
5
5
  import { SHARED_CONSTRUCTS_NAME } from "../shared-constructs-constants.js";
6
6
  /** The ts#agent guide section that shows how to deploy the agent's Terraform module. */ export const TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL = 'https://awslabs.github.io/nx-plugin-for-aws/en/guides/ts-agent/#infrastructure-as-code';
7
+ /** The py#agent guide section that shows how to deploy the agent's Terraform module. */ export const PY_AGENT_TERRAFORM_DEPLOY_GUIDE_URL = 'https://awslabs.github.io/nx-plugin-for-aws/en/guides/py-agent/#infrastructure-as-code';
7
8
  /**
8
9
  * The code a user adds to their CDK stack to guard the agent.
9
10
  * The guide shows the same code for `my-agent` in scope `my-scope`, which
@@ -39,11 +40,11 @@ module "runtime_config_appconfig_deployment" {
39
40
  }`;
40
41
  };
41
42
  /**
42
- * How to add the printed Terraform code. `ts#agent` does not add the agent to
43
- * the root module, so its `module` block may not exist yet.
44
- */ const terraformSteps = (nameKebabCase)=>{
43
+ * How to add the printed Terraform code. The agent generators do not add the
44
+ * agent to the root module, so its `module` block may not exist yet.
45
+ */ const terraformSteps = (nameKebabCase, deployGuideUrl = TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL)=>{
45
46
  const agentModule = snakeCase(nameKebabCase);
46
- return `Add the module "${agentModule}_guardrail" block as printed below. Add its additional_iam_policy_statements line to your existing module "${agentModule}" block, and add module.${agentModule}_guardrail to the depends_on list of your runtime_config_appconfig_deployment module. If your root module does not deploy the agent yet, add the agent's module first, as described in ${TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL}.`;
47
+ return `Add the module "${agentModule}_guardrail" block as printed below. Add its additional_iam_policy_statements line to your existing module "${agentModule}" block, and add module.${agentModule}_guardrail to the depends_on list of your runtime_config_appconfig_deployment module. If your root module does not deploy the agent yet, add the agent's module first, as described in ${deployGuideUrl}.`;
47
48
  };
48
49
  /**
49
50
  * The message the generator prints after it adds the guardrail
@@ -53,7 +54,7 @@ module "runtime_config_appconfig_deployment" {
53
54
  const missing = `Until you add it, the deployed agent fails on its first request, because runtime config has no guardrail at agentcore.guardrails.${options.nameClassName}.`;
54
55
  return options.iac === 'cdk' ? `Add the guardrail to your agent in your stack, for example packages/infra/src/stacks/application-stack.ts. ${missing}
55
56
 
56
- ${cdkGuardrailWiring(options)}` : `Add the guardrail to your agent in the root Terraform module where you deploy the agent, for example packages/infra/src/main.tf. ${missing} ${terraformSteps(options.nameKebabCase)}
57
+ ${cdkGuardrailWiring(options)}` : `Add the guardrail to your agent in the root Terraform module where you deploy the agent, for example packages/infra/src/main.tf. ${missing} ${terraformSteps(options.nameKebabCase, options.deployGuideUrl)}
57
58
 
58
59
  ${terraformGuardrailWiring(options)}`;
59
60
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/guardrail-constructs/guardrail-wiring.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { Iac } from '../iac.js';\nimport { snakeCase } from '../names.js';\nimport { SHARED_CONSTRUCTS_NAME } from '../shared-constructs-constants.js';\n\nexport interface GuardrailWiringOptions {\n iac: Iac;\n /** Class name of the guarded agent, eg `MyAgent`. */\n nameClassName: string;\n /** Kebab-case name of the guarded agent, eg `my-agent`. */\n nameKebabCase: string;\n /** npm scope of the workspace without the `@`, eg `my-scope`. */\n npmScope: string;\n}\n\n/** The ts#agent guide section that shows how to deploy the agent's Terraform module. */\nexport const TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL =\n 'https://awslabs.github.io/nx-plugin-for-aws/en/guides/ts-agent/#infrastructure-as-code';\n\n/**\n * The code a user adds to their CDK stack to guard the agent.\n * The guide shows the same code for `my-agent` in scope `my-scope`, which\n * generator.spec.ts checks.\n */\nexport const cdkGuardrailWiring = ({\n nameClassName,\n npmScope,\n}: Pick<GuardrailWiringOptions, 'nameClassName' | 'npmScope'>): string =>\n `import { ${nameClassName}, ${nameClassName}Guardrail } from '@${npmScope}/${SHARED_CONSTRUCTS_NAME}';\n\nconst agent = new ${nameClassName}(this, '${nameClassName}');\nnew ${nameClassName}Guardrail(this, '${nameClassName}Guardrail', { agent });`;\n\n/**\n * The code a user adds to their root Terraform module to grant the agent the\n * guardrail and deploy its runtime-config entry, relative to\n * `packages/infra/src`. The guide shows the same code for `my-agent`, which\n * generator.spec.ts checks.\n */\nexport const terraformGuardrailWiring = ({\n nameKebabCase,\n}: Pick<GuardrailWiringOptions, 'nameKebabCase'>): string => {\n const agentModule = snakeCase(nameKebabCase);\n const guardrailModule = `${agentModule}_guardrail`;\n return `module \"${guardrailModule}\" {\n source = \"../../common/terraform/src/app/guardrails/${nameKebabCase}\"\n}\n\nmodule \"${agentModule}\" {\n source = \"../../common/terraform/src/app/agents/${nameKebabCase}\"\n # ...existing arguments...\n additional_iam_policy_statements = module.${guardrailModule}.iam_policy_statements\n}\n\nmodule \"runtime_config_appconfig_deployment\" {\n # ...existing arguments...\n depends_on = [\n # ...existing modules...\n module.${guardrailModule},\n ]\n}`;\n};\n\n/**\n * How to add the printed Terraform code. `ts#agent` does not add the agent to\n * the root module, so its `module` block may not exist yet.\n */\nconst terraformSteps = (nameKebabCase: string): string => {\n const agentModule = snakeCase(nameKebabCase);\n return `Add the module \"${agentModule}_guardrail\" block as printed below. Add its additional_iam_policy_statements line to your existing module \"${agentModule}\" block, and add module.${agentModule}_guardrail to the depends_on list of your runtime_config_appconfig_deployment module. If your root module does not deploy the agent yet, add the agent's module first, as described in ${TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL}.`;\n};\n\n/**\n * The message the generator prints after it adds the guardrail\n * infrastructure. The generator does not edit the user's stack or root\n * module, so this tells the user what to add there.\n */\nexport const guardrailWiringMessage = (\n options: GuardrailWiringOptions,\n): string => {\n const missing = `Until you add it, the deployed agent fails on its first request, because runtime config has no guardrail at agentcore.guardrails.${options.nameClassName}.`;\n return options.iac === 'cdk'\n ? `Add the guardrail to your agent in your stack, for example packages/infra/src/stacks/application-stack.ts. ${missing}\n\n${cdkGuardrailWiring(options)}`\n : `Add the guardrail to your agent in the root Terraform module where you deploy the agent, for example packages/infra/src/main.tf. ${missing} ${terraformSteps(options.nameKebabCase)}\n\n${terraformGuardrailWiring(options)}`;\n};\n"],"names":["snakeCase","SHARED_CONSTRUCTS_NAME","TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL","cdkGuardrailWiring","nameClassName","npmScope","terraformGuardrailWiring","nameKebabCase","agentModule","guardrailModule","terraformSteps","guardrailWiringMessage","options","missing","iac"],"mappings":"AAAA;;;CAGC,GAED,SAASA,SAAS,QAAQ,cAAc;AACxC,SAASC,sBAAsB,QAAQ,oCAAoC;AAY3E,sFAAsF,GACtF,OAAO,MAAMC,sCACX,yFAAyF;AAE3F;;;;CAIC,GACD,OAAO,MAAMC,qBAAqB,CAAC,EACjCC,aAAa,EACbC,QAAQ,EACmD,GAC3D,CAAC,SAAS,EAAED,cAAc,EAAE,EAAEA,cAAc,mBAAmB,EAAEC,SAAS,CAAC,EAAEJ,uBAAuB;;kBAEpF,EAAEG,cAAc,QAAQ,EAAEA,cAAc;IACtD,EAAEA,cAAc,iBAAiB,EAAEA,cAAc,uBAAuB,CAAC,CAAC;AAE9E;;;;;CAKC,GACD,OAAO,MAAME,2BAA2B,CAAC,EACvCC,aAAa,EACiC;IAC9C,MAAMC,cAAcR,UAAUO;IAC9B,MAAME,kBAAkB,GAAGD,YAAY,UAAU,CAAC;IAClD,OAAO,CAAC,QAAQ,EAAEC,gBAAgB;sDACkB,EAAEF,cAAc;;;QAG9D,EAAEC,YAAY;kDAC4B,EAAED,cAAc;;4CAEtB,EAAEE,gBAAgB;;;;;;;WAOnD,EAAEA,gBAAgB;;CAE5B,CAAC;AACF,EAAE;AAEF;;;CAGC,GACD,MAAMC,iBAAiB,CAACH;IACtB,MAAMC,cAAcR,UAAUO;IAC9B,OAAO,CAAC,gBAAgB,EAAEC,YAAY,2GAA2G,EAAEA,YAAY,wBAAwB,EAAEA,YAAY,uLAAuL,EAAEN,oCAAoC,CAAC,CAAC;AACta;AAEA;;;;CAIC,GACD,OAAO,MAAMS,yBAAyB,CACpCC;IAEA,MAAMC,UAAU,CAAC,iIAAiI,EAAED,QAAQR,aAAa,CAAC,CAAC,CAAC;IAC5K,OAAOQ,QAAQE,GAAG,KAAK,QACnB,CAAC,2GAA2G,EAAED,QAAQ;;AAE5H,EAAEV,mBAAmBS,UAAU,GACzB,CAAC,iIAAiI,EAAEC,QAAQ,CAAC,EAAEH,eAAeE,QAAQL,aAAa,EAAE;;AAE3L,EAAED,yBAAyBM,UAAU;AACrC,EAAE"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/guardrail-constructs/guardrail-wiring.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { Iac } from '../iac.js';\nimport { snakeCase } from '../names.js';\nimport { SHARED_CONSTRUCTS_NAME } from '../shared-constructs-constants.js';\n\nexport interface GuardrailWiringOptions {\n iac: Iac;\n /** Class name of the guarded agent, eg `MyAgent`. */\n nameClassName: string;\n /** Kebab-case name of the guarded agent, eg `my-agent`. */\n nameKebabCase: string;\n /** npm scope of the workspace without the `@`, eg `my-scope`. */\n npmScope: string;\n /**\n * The agent guide section that shows how to deploy the agent's Terraform\n * module. Defaults to the ts#agent guide.\n */\n deployGuideUrl?: string;\n}\n\n/** The ts#agent guide section that shows how to deploy the agent's Terraform module. */\nexport const TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL =\n 'https://awslabs.github.io/nx-plugin-for-aws/en/guides/ts-agent/#infrastructure-as-code';\n\n/** The py#agent guide section that shows how to deploy the agent's Terraform module. */\nexport const PY_AGENT_TERRAFORM_DEPLOY_GUIDE_URL =\n 'https://awslabs.github.io/nx-plugin-for-aws/en/guides/py-agent/#infrastructure-as-code';\n\n/**\n * The code a user adds to their CDK stack to guard the agent.\n * The guide shows the same code for `my-agent` in scope `my-scope`, which\n * generator.spec.ts checks.\n */\nexport const cdkGuardrailWiring = ({\n nameClassName,\n npmScope,\n}: Pick<GuardrailWiringOptions, 'nameClassName' | 'npmScope'>): string =>\n `import { ${nameClassName}, ${nameClassName}Guardrail } from '@${npmScope}/${SHARED_CONSTRUCTS_NAME}';\n\nconst agent = new ${nameClassName}(this, '${nameClassName}');\nnew ${nameClassName}Guardrail(this, '${nameClassName}Guardrail', { agent });`;\n\n/**\n * The code a user adds to their root Terraform module to grant the agent the\n * guardrail and deploy its runtime-config entry, relative to\n * `packages/infra/src`. The guide shows the same code for `my-agent`, which\n * generator.spec.ts checks.\n */\nexport const terraformGuardrailWiring = ({\n nameKebabCase,\n}: Pick<GuardrailWiringOptions, 'nameKebabCase'>): string => {\n const agentModule = snakeCase(nameKebabCase);\n const guardrailModule = `${agentModule}_guardrail`;\n return `module \"${guardrailModule}\" {\n source = \"../../common/terraform/src/app/guardrails/${nameKebabCase}\"\n}\n\nmodule \"${agentModule}\" {\n source = \"../../common/terraform/src/app/agents/${nameKebabCase}\"\n # ...existing arguments...\n additional_iam_policy_statements = module.${guardrailModule}.iam_policy_statements\n}\n\nmodule \"runtime_config_appconfig_deployment\" {\n # ...existing arguments...\n depends_on = [\n # ...existing modules...\n module.${guardrailModule},\n ]\n}`;\n};\n\n/**\n * How to add the printed Terraform code. The agent generators do not add the\n * agent to the root module, so its `module` block may not exist yet.\n */\nconst terraformSteps = (\n nameKebabCase: string,\n deployGuideUrl = TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL,\n): string => {\n const agentModule = snakeCase(nameKebabCase);\n return `Add the module \"${agentModule}_guardrail\" block as printed below. Add its additional_iam_policy_statements line to your existing module \"${agentModule}\" block, and add module.${agentModule}_guardrail to the depends_on list of your runtime_config_appconfig_deployment module. If your root module does not deploy the agent yet, add the agent's module first, as described in ${deployGuideUrl}.`;\n};\n\n/**\n * The message the generator prints after it adds the guardrail\n * infrastructure. The generator does not edit the user's stack or root\n * module, so this tells the user what to add there.\n */\nexport const guardrailWiringMessage = (\n options: GuardrailWiringOptions,\n): string => {\n const missing = `Until you add it, the deployed agent fails on its first request, because runtime config has no guardrail at agentcore.guardrails.${options.nameClassName}.`;\n return options.iac === 'cdk'\n ? `Add the guardrail to your agent in your stack, for example packages/infra/src/stacks/application-stack.ts. ${missing}\n\n${cdkGuardrailWiring(options)}`\n : `Add the guardrail to your agent in the root Terraform module where you deploy the agent, for example packages/infra/src/main.tf. ${missing} ${terraformSteps(options.nameKebabCase, options.deployGuideUrl)}\n\n${terraformGuardrailWiring(options)}`;\n};\n"],"names":["snakeCase","SHARED_CONSTRUCTS_NAME","TS_AGENT_TERRAFORM_DEPLOY_GUIDE_URL","PY_AGENT_TERRAFORM_DEPLOY_GUIDE_URL","cdkGuardrailWiring","nameClassName","npmScope","terraformGuardrailWiring","nameKebabCase","agentModule","guardrailModule","terraformSteps","deployGuideUrl","guardrailWiringMessage","options","missing","iac"],"mappings":"AAAA;;;CAGC,GAED,SAASA,SAAS,QAAQ,cAAc;AACxC,SAASC,sBAAsB,QAAQ,oCAAoC;AAiB3E,sFAAsF,GACtF,OAAO,MAAMC,sCACX,yFAAyF;AAE3F,sFAAsF,GACtF,OAAO,MAAMC,sCACX,yFAAyF;AAE3F;;;;CAIC,GACD,OAAO,MAAMC,qBAAqB,CAAC,EACjCC,aAAa,EACbC,QAAQ,EACmD,GAC3D,CAAC,SAAS,EAAED,cAAc,EAAE,EAAEA,cAAc,mBAAmB,EAAEC,SAAS,CAAC,EAAEL,uBAAuB;;kBAEpF,EAAEI,cAAc,QAAQ,EAAEA,cAAc;IACtD,EAAEA,cAAc,iBAAiB,EAAEA,cAAc,uBAAuB,CAAC,CAAC;AAE9E;;;;;CAKC,GACD,OAAO,MAAME,2BAA2B,CAAC,EACvCC,aAAa,EACiC;IAC9C,MAAMC,cAAcT,UAAUQ;IAC9B,MAAME,kBAAkB,GAAGD,YAAY,UAAU,CAAC;IAClD,OAAO,CAAC,QAAQ,EAAEC,gBAAgB;sDACkB,EAAEF,cAAc;;;QAG9D,EAAEC,YAAY;kDAC4B,EAAED,cAAc;;4CAEtB,EAAEE,gBAAgB;;;;;;;WAOnD,EAAEA,gBAAgB;;CAE5B,CAAC;AACF,EAAE;AAEF;;;CAGC,GACD,MAAMC,iBAAiB,CACrBH,eACAI,iBAAiBV,mCAAmC;IAEpD,MAAMO,cAAcT,UAAUQ;IAC9B,OAAO,CAAC,gBAAgB,EAAEC,YAAY,2GAA2G,EAAEA,YAAY,wBAAwB,EAAEA,YAAY,uLAAuL,EAAEG,eAAe,CAAC,CAAC;AACjZ;AAEA;;;;CAIC,GACD,OAAO,MAAMC,yBAAyB,CACpCC;IAEA,MAAMC,UAAU,CAAC,iIAAiI,EAAED,QAAQT,aAAa,CAAC,CAAC,CAAC;IAC5K,OAAOS,QAAQE,GAAG,KAAK,QACnB,CAAC,2GAA2G,EAAED,QAAQ;;AAE5H,EAAEX,mBAAmBU,UAAU,GACzB,CAAC,iIAAiI,EAAEC,QAAQ,CAAC,EAAEJ,eAAeG,QAAQN,aAAa,EAAEM,QAAQF,cAAc,EAAE;;AAEnN,EAAEL,yBAAyBO,UAAU;AACrC,EAAE"}
@@ -0,0 +1,205 @@
1
+ #
2
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """Local stand-in for AppConfigData and Bedrock Runtime, so tests of generated
6
+ agent code send real SDK requests without AWS. Standard library only.
7
+
8
+ Usage: python fake_aws.py <config.json> <port> <requests.log>
9
+ or, in-process: start_server(config_file, log_file) returns (server, port).
10
+
11
+ - AppConfigData: POST /configurationsessions and GET /configuration serve the
12
+ JSON in <config.json>, re-read on every request, so editing the file changes
13
+ the value. A session whose ApplicationIdentifier is "broken" answers HTTP 400
14
+ BadRequestException, to simulate a read failure.
15
+ - Bedrock Runtime: POST /model/<id>/converse-stream (event stream) and
16
+ /model/<id>/converse (JSON) answer "4". A request with a guardrailConfig gets
17
+ BLOCKED_ANSWER with stop reason guardrail_intervened and a BLOCKED trace when
18
+ the text the guardrail checks contains DENIED_TERM. As in Bedrock, the
19
+ guardrail checks only the guardContent blocks if the messages hold any, and
20
+ every message otherwise.
21
+
22
+ Every request appends one JSON line to <requests.log>: AppConfigData requests
23
+ as {"path": "StartConfigurationSession" | "GetLatestConfiguration"}, model
24
+ requests as {"path", "modelId", "guardrailConfig", "userTexts", "intervened"}.
25
+ """
26
+
27
+ import binascii
28
+ import json
29
+ import struct
30
+ import sys
31
+ import threading
32
+ import urllib.parse
33
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
34
+
35
+ DENIED_TERM = "stocks"
36
+ BLOCKED_ANSWER = "Sorry, I can't help with that request."
37
+
38
+
39
+ def _header(name: str, value: str) -> bytes:
40
+ n, v = name.encode(), value.encode()
41
+ return struct.pack("B", len(n)) + n + b"\x07" + struct.pack(">H", len(v)) + v
42
+
43
+
44
+ def _event(event_type: str, payload: dict) -> bytes:
45
+ headers = (
46
+ _header(":event-type", event_type)
47
+ + _header(":content-type", "application/json")
48
+ + _header(":message-type", "event")
49
+ )
50
+ body = json.dumps(payload).encode()
51
+ total = 12 + len(headers) + len(body) + 4
52
+ prelude = struct.pack(">II", total, len(headers))
53
+ prelude += struct.pack(">I", binascii.crc32(prelude) & 0xFFFFFFFF)
54
+ message = prelude + headers + body
55
+ return message + struct.pack(">I", binascii.crc32(message) & 0xFFFFFFFF)
56
+
57
+
58
+ def _texts(blocks: list) -> list[str]:
59
+ texts = []
60
+ for block in blocks:
61
+ if "text" in block:
62
+ texts.append(block["text"])
63
+ elif "guardContent" in block:
64
+ texts.append(block["guardContent"].get("text", {}).get("text", ""))
65
+ elif "toolResult" in block:
66
+ texts.extend(_texts(block["toolResult"].get("content", [])))
67
+ return texts
68
+
69
+
70
+ def _intervenes(request: dict) -> bool:
71
+ """Whether the guardrail blocks the request's input, by Bedrock's rule for guardContent."""
72
+ if not request.get("guardrailConfig"):
73
+ return False
74
+ messages = request.get("messages", [])
75
+ guarded = [
76
+ block["guardContent"].get("text", {}).get("text", "")
77
+ for message in messages
78
+ for block in message.get("content", [])
79
+ if "guardContent" in block
80
+ ]
81
+ checked = guarded or [text for message in messages for text in _texts(message.get("content", []))]
82
+ return any(DENIED_TERM in text.lower() for text in checked)
83
+
84
+
85
+ _BLOCKED_TRACE = {
86
+ "guardrail": {
87
+ "inputAssessment": {
88
+ "grtest": {
89
+ "topicPolicy": {
90
+ "topics": [{"name": "Investment advice", "type": "DENY", "action": "BLOCKED", "detected": True}]
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+
98
+ def _handler(config_file: str, log_file: str) -> type[BaseHTTPRequestHandler]:
99
+ lock = threading.Lock()
100
+
101
+ def log(entry: dict) -> None:
102
+ with lock, open(log_file, "a") as f:
103
+ f.write(json.dumps(entry) + "\n")
104
+
105
+ class Handler(BaseHTTPRequestHandler):
106
+ protocol_version = "HTTP/1.1"
107
+
108
+ def log_message(self, *_args) -> None:
109
+ pass
110
+
111
+ def _send(self, status: int, body: bytes, content_type: str, extra=None) -> None:
112
+ self.send_response(status)
113
+ self.send_header("Content-Type", content_type)
114
+ self.send_header("Content-Length", str(len(body)))
115
+ for k, v in (extra or {}).items():
116
+ self.send_header(k, v)
117
+ self.end_headers()
118
+ self.wfile.write(body)
119
+
120
+ def do_GET(self) -> None:
121
+ if self.path.startswith("/configuration"):
122
+ log({"path": "GetLatestConfiguration"})
123
+ with open(config_file, "rb") as f:
124
+ body = f.read()
125
+ self._send(
126
+ 200,
127
+ body,
128
+ "application/json",
129
+ {"Next-Poll-Configuration-Token": "token", "Next-Poll-Interval-In-Seconds": "60"},
130
+ )
131
+ return
132
+ self._send(404, b"{}", "application/json")
133
+
134
+ def do_POST(self) -> None:
135
+ raw = self.rfile.read(int(self.headers.get("Content-Length", "0")))
136
+ if self.path.startswith("/configurationsessions"):
137
+ log({"path": "StartConfigurationSession"})
138
+ if json.loads(raw or b"{}").get("ApplicationIdentifier") == "broken":
139
+ self._send(
140
+ 400,
141
+ json.dumps({"Message": "Application broken does not exist."}).encode(),
142
+ "application/json",
143
+ {"x-amzn-ErrorType": "BadRequestException"},
144
+ )
145
+ return
146
+ self._send(201, json.dumps({"InitialConfigurationToken": "token"}).encode(), "application/json")
147
+ return
148
+ if self.path.startswith("/model/"):
149
+ request = json.loads(raw or b"{}")
150
+ model_id = urllib.parse.unquote(self.path.split("/")[2])
151
+ intervened = _intervenes(request)
152
+ log(
153
+ {
154
+ "path": self.path.split("/")[-1],
155
+ "modelId": model_id,
156
+ "guardrailConfig": request.get("guardrailConfig"),
157
+ "userTexts": [
158
+ text
159
+ for message in request.get("messages", [])
160
+ if message.get("role") == "user"
161
+ for text in _texts(message.get("content", []))
162
+ ],
163
+ "intervened": intervened,
164
+ }
165
+ )
166
+ text = BLOCKED_ANSWER if intervened else "4"
167
+ stop_reason = "guardrail_intervened" if intervened else "end_turn"
168
+ metadata = {
169
+ "usage": {"inputTokens": 1, "outputTokens": 1, "totalTokens": 2},
170
+ "metrics": {"latencyMs": 1},
171
+ **({"trace": _BLOCKED_TRACE} if intervened else {}),
172
+ }
173
+ if self.path.endswith("/converse-stream"):
174
+ body = b"".join(
175
+ [
176
+ _event("messageStart", {"role": "assistant"}),
177
+ _event("contentBlockDelta", {"contentBlockIndex": 0, "delta": {"text": text}}),
178
+ _event("contentBlockStop", {"contentBlockIndex": 0}),
179
+ _event("messageStop", {"stopReason": stop_reason}),
180
+ _event("metadata", metadata),
181
+ ]
182
+ )
183
+ self._send(200, body, "application/vnd.amazon.eventstream")
184
+ return
185
+ answer = {
186
+ "output": {"message": {"role": "assistant", "content": [{"text": text}]}},
187
+ "stopReason": stop_reason,
188
+ **metadata,
189
+ }
190
+ self._send(200, json.dumps(answer).encode(), "application/json")
191
+ return
192
+ self._send(404, b"{}", "application/json")
193
+
194
+ return Handler
195
+
196
+
197
+ def start_server(config_file: str, log_file: str, port: int = 0) -> tuple[ThreadingHTTPServer, int]:
198
+ """Serve on 127.0.0.1 in a daemon thread; port 0 picks a free port."""
199
+ server = ThreadingHTTPServer(("127.0.0.1", port), _handler(config_file, log_file))
200
+ threading.Thread(target=server.serve_forever, daemon=True).start()
201
+ return server, server.server_address[1]
202
+
203
+
204
+ if __name__ == "__main__":
205
+ ThreadingHTTPServer(("127.0.0.1", int(sys.argv[2])), _handler(sys.argv[1], sys.argv[3])).serve_forever()
@@ -12,3 +12,9 @@ export declare const PY_VERIFIER_TYPE_CHECKER: string;
12
12
  * they are included too.
13
13
  */
14
14
  export declare const PY_DYNAMODB_VERIFIER_DEPENDENCIES: string[];
15
+ /**
16
+ * The packages a Strands or LangChain agent generated by `py#agent` imports
17
+ * from agent.py and guardrail.py, at the versions the generator vends, plus
18
+ * the ruff the vended lint and format targets run.
19
+ */
20
+ export declare const PY_AGENT_GUARDRAIL_VERIFIER_DEPENDENCIES: string[];
@@ -2,6 +2,7 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */ import { OPEN_API_PY_CLIENT_DEPENDENCIES } from "../../open-api/py-client/generator.js";
5
+ import { DEPENDENCIES as PY_AGENT_DEPENDENCIES } from "../../py/agent/generator.js";
5
6
  import { DEPENDENCIES as PY_DYNAMODB_DEPENDENCIES } from "../../py/dynamodb/generator.js";
6
7
  import { declaredNames } from "../declared-dependencies.js";
7
8
  import { PY_VERSIONS, withPyVersions } from "../versions.js";
@@ -19,5 +20,23 @@ import { PY_VERSIONS, withPyVersions } from "../versions.js";
19
20
  ...withPyVersions(PY_DYNAMODB_DEPENDENCIES, declaredNames(PY_DYNAMODB_DEPENDENCIES.py)),
20
21
  ...PY_CLIENT_VERIFIER_DEPENDENCIES
21
22
  ];
23
+ /**
24
+ * The packages a Strands or LangChain agent generated by `py#agent` imports
25
+ * from agent.py and guardrail.py, at the versions the generator vends, plus
26
+ * the ruff the vended lint and format targets run.
27
+ */ export const PY_AGENT_GUARDRAIL_VERIFIER_DEPENDENCIES = [
28
+ ...withPyVersions(PY_AGENT_DEPENDENCIES, [
29
+ 'aws-lambda-powertools',
30
+ 'boto3',
31
+ 'strands-agents',
32
+ 'strands-agents-tools',
33
+ 'langchain',
34
+ 'langchain-aws',
35
+ 'langgraph',
36
+ 'langgraph-checkpoint-sqlite',
37
+ 'aiosqlite'
38
+ ]),
39
+ `ruff${PY_VERSIONS.ruff}`
40
+ ];
22
41
 
23
42
  //# sourceMappingURL=python-dependencies.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/test/python-dependencies.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { OPEN_API_PY_CLIENT_DEPENDENCIES } from '../../open-api/py-client/generator.js';\nimport { DEPENDENCIES as PY_DYNAMODB_DEPENDENCIES } from '../../py/dynamodb/generator.js';\nimport { declaredNames } from '../declared-dependencies.js';\nimport { PY_VERSIONS, withPyVersions } from '../versions.js';\n\n/**\n * The packages a client generated by `open-api#py-client` imports, at the\n * versions the generator vends — so a worker runs the code under test against\n * exactly what a consumer gets.\n */\nexport const PY_CLIENT_VERIFIER_DEPENDENCIES: string[] = withPyVersions(\n OPEN_API_PY_CLIENT_DEPENDENCIES,\n declaredNames(OPEN_API_PY_CLIENT_DEPENDENCIES.py),\n);\n\n/** The type checker every Python worker installs alongside its dependencies. */\nexport const PY_VERIFIER_TYPE_CHECKER = `ty${PY_VERSIONS.ty}`;\n\n/**\n * The packages a project generated by `py#dynamodb` imports, at the versions\n * the generator vends. The worker itself imports the py-client packages, so\n * they are included too.\n */\nexport const PY_DYNAMODB_VERIFIER_DEPENDENCIES: string[] = [\n ...withPyVersions(\n PY_DYNAMODB_DEPENDENCIES,\n declaredNames(PY_DYNAMODB_DEPENDENCIES.py),\n ),\n ...PY_CLIENT_VERIFIER_DEPENDENCIES,\n];\n"],"names":["OPEN_API_PY_CLIENT_DEPENDENCIES","DEPENDENCIES","PY_DYNAMODB_DEPENDENCIES","declaredNames","PY_VERSIONS","withPyVersions","PY_CLIENT_VERIFIER_DEPENDENCIES","py","PY_VERIFIER_TYPE_CHECKER","ty","PY_DYNAMODB_VERIFIER_DEPENDENCIES"],"mappings":"AAAA;;;CAGC,GACD,SAASA,+BAA+B,QAAQ,wCAAwC;AACxF,SAASC,gBAAgBC,wBAAwB,QAAQ,iCAAiC;AAC1F,SAASC,aAAa,QAAQ,8BAA8B;AAC5D,SAASC,WAAW,EAAEC,cAAc,QAAQ,iBAAiB;AAE7D;;;;CAIC,GACD,OAAO,MAAMC,kCAA4CD,eACvDL,iCACAG,cAAcH,gCAAgCO,EAAE,GAChD;AAEF,8EAA8E,GAC9E,OAAO,MAAMC,2BAA2B,CAAC,EAAE,EAAEJ,YAAYK,EAAE,EAAE,CAAC;AAE9D;;;;CAIC,GACD,OAAO,MAAMC,oCAA8C;OACtDL,eACDH,0BACAC,cAAcD,yBAAyBK,EAAE;OAExCD;CACJ,CAAC"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/test/python-dependencies.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { OPEN_API_PY_CLIENT_DEPENDENCIES } from '../../open-api/py-client/generator.js';\nimport { DEPENDENCIES as PY_AGENT_DEPENDENCIES } from '../../py/agent/generator.js';\nimport { DEPENDENCIES as PY_DYNAMODB_DEPENDENCIES } from '../../py/dynamodb/generator.js';\nimport { declaredNames } from '../declared-dependencies.js';\nimport { PY_VERSIONS, withPyVersions } from '../versions.js';\n\n/**\n * The packages a client generated by `open-api#py-client` imports, at the\n * versions the generator vends — so a worker runs the code under test against\n * exactly what a consumer gets.\n */\nexport const PY_CLIENT_VERIFIER_DEPENDENCIES: string[] = withPyVersions(\n OPEN_API_PY_CLIENT_DEPENDENCIES,\n declaredNames(OPEN_API_PY_CLIENT_DEPENDENCIES.py),\n);\n\n/** The type checker every Python worker installs alongside its dependencies. */\nexport const PY_VERIFIER_TYPE_CHECKER = `ty${PY_VERSIONS.ty}`;\n\n/**\n * The packages a project generated by `py#dynamodb` imports, at the versions\n * the generator vends. The worker itself imports the py-client packages, so\n * they are included too.\n */\nexport const PY_DYNAMODB_VERIFIER_DEPENDENCIES: string[] = [\n ...withPyVersions(\n PY_DYNAMODB_DEPENDENCIES,\n declaredNames(PY_DYNAMODB_DEPENDENCIES.py),\n ),\n ...PY_CLIENT_VERIFIER_DEPENDENCIES,\n];\n\n/**\n * The packages a Strands or LangChain agent generated by `py#agent` imports\n * from agent.py and guardrail.py, at the versions the generator vends, plus\n * the ruff the vended lint and format targets run.\n */\nexport const PY_AGENT_GUARDRAIL_VERIFIER_DEPENDENCIES: string[] = [\n ...withPyVersions(PY_AGENT_DEPENDENCIES, [\n 'aws-lambda-powertools',\n 'boto3',\n 'strands-agents',\n 'strands-agents-tools',\n 'langchain',\n 'langchain-aws',\n 'langgraph',\n 'langgraph-checkpoint-sqlite',\n 'aiosqlite',\n ]),\n `ruff${PY_VERSIONS.ruff}`,\n];\n"],"names":["OPEN_API_PY_CLIENT_DEPENDENCIES","DEPENDENCIES","PY_AGENT_DEPENDENCIES","PY_DYNAMODB_DEPENDENCIES","declaredNames","PY_VERSIONS","withPyVersions","PY_CLIENT_VERIFIER_DEPENDENCIES","py","PY_VERIFIER_TYPE_CHECKER","ty","PY_DYNAMODB_VERIFIER_DEPENDENCIES","PY_AGENT_GUARDRAIL_VERIFIER_DEPENDENCIES","ruff"],"mappings":"AAAA;;;CAGC,GACD,SAASA,+BAA+B,QAAQ,wCAAwC;AACxF,SAASC,gBAAgBC,qBAAqB,QAAQ,8BAA8B;AACpF,SAASD,gBAAgBE,wBAAwB,QAAQ,iCAAiC;AAC1F,SAASC,aAAa,QAAQ,8BAA8B;AAC5D,SAASC,WAAW,EAAEC,cAAc,QAAQ,iBAAiB;AAE7D;;;;CAIC,GACD,OAAO,MAAMC,kCAA4CD,eACvDN,iCACAI,cAAcJ,gCAAgCQ,EAAE,GAChD;AAEF,8EAA8E,GAC9E,OAAO,MAAMC,2BAA2B,CAAC,EAAE,EAAEJ,YAAYK,EAAE,EAAE,CAAC;AAE9D;;;;CAIC,GACD,OAAO,MAAMC,oCAA8C;OACtDL,eACDH,0BACAC,cAAcD,yBAAyBK,EAAE;OAExCD;CACJ,CAAC;AAEF;;;;CAIC,GACD,OAAO,MAAMK,2CAAqD;OAC7DN,eAAeJ,uBAAuB;QACvC;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACD;IACD,CAAC,IAAI,EAAEG,YAAYQ,IAAI,EAAE;CAC1B,CAAC"}