@aws/nx-plugin 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -29
- package/generators.json +16 -0
- package/migrations.json +6 -1
- package/package.json +1 -1
- package/src/agentcore-harness/generator.d.ts +1 -1
- package/src/greengrass/component/common.d.ts +60 -0
- package/src/greengrass/component/common.js +191 -0
- package/src/greengrass/component/common.js.map +1 -0
- package/src/greengrass/component/dependencies.d.ts +101 -0
- package/src/greengrass/component/dependencies.js +37 -0
- package/src/greengrass/component/dependencies.js.map +1 -0
- package/src/greengrass/component/generator.d.ts +174 -0
- package/src/greengrass/component/generator.js +32 -0
- package/src/greengrass/component/generator.js.map +1 -0
- package/src/greengrass/component/metadata.d.ts +2 -0
- package/src/greengrass/component/metadata.js +14 -0
- package/src/greengrass/component/metadata.js.map +1 -0
- package/src/greengrass/component/python/__snapshots__/generator.spec.ts.snap +450 -0
- package/src/greengrass/component/python/files/component/main.py.template +60 -0
- package/src/greengrass/component/python/files/component/recipe.yaml.template +30 -0
- package/src/greengrass/component/python/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/python/files/tests/test___componentSnakeCase__.py.template +37 -0
- package/src/greengrass/component/python/generator.d.ts +15 -0
- package/src/greengrass/component/python/generator.js +192 -0
- package/src/greengrass/component/python/generator.js.map +1 -0
- package/src/greengrass/component/schema.d.js +6 -0
- package/src/greengrass/component/schema.d.js.map +1 -0
- package/src/greengrass/component/schema.d.ts +18 -0
- package/src/greengrass/component/schema.json +82 -0
- package/src/greengrass/component/typescript/__snapshots__/generator.spec.ts.snap +275 -0
- package/src/greengrass/component/typescript/files/component/recipe.yaml.template +41 -0
- package/src/greengrass/component/typescript/files/entrypoint/main.ts.template +58 -0
- package/src/greengrass/component/typescript/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/typescript/files/scripts/vendor-crt-addon.ts.template +115 -0
- package/src/greengrass/component/typescript/generator.d.ts +16 -0
- package/src/greengrass/component/typescript/generator.js +194 -0
- package/src/greengrass/component/typescript/generator.js.map +1 -0
- package/src/infra/app/generator.d.ts +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/internal/test-matrix/generator.js +48 -1
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/license/dependency-check/pre-approved.js +2 -1
- package/src/license/dependency-check/pre-approved.js.map +1 -1
- package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/metadata.json +3 -0
- package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.d.ts +6 -0
- package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.js +118 -0
- package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.js.map +1 -0
- package/src/open-api/py-client/generator.d.ts +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/preset/generator.d.ts +1 -1
- package/src/py/agent/a2a-connection/generator.d.ts +1 -1
- package/src/py/agent/gateway-connection/generator.d.ts +1 -1
- package/src/py/agent/guardrails/__snapshots__/generator.spec.ts.snap +3813 -0
- package/src/py/agent/guardrails/files/langchain/guardrail.py.template +135 -0
- package/src/py/agent/guardrails/files/strands/guardrail.py.template +93 -0
- package/src/py/agent/guardrails/generator.d.ts +10 -0
- package/src/py/agent/guardrails/generator.js +175 -0
- package/src/py/agent/guardrails/generator.js.map +1 -0
- package/src/py/agent/guardrails/runtime-check/run_checks.py +250 -0
- package/src/py/agent/guardrails/schema.d.js +6 -0
- package/src/py/agent/guardrails/schema.d.js.map +1 -0
- package/src/py/agent/guardrails/schema.d.ts +9 -0
- package/src/py/agent/guardrails/schema.json +25 -0
- package/src/py/agent/mcp-connection/generator.d.ts +1 -1
- package/src/py/agent/react-connection/generator.d.ts +1 -1
- package/src/py/fast-api/react/generator.d.ts +1 -1
- package/src/py/project/generator.js +2 -0
- package/src/py/project/generator.js.map +1 -1
- package/src/sdk/greengrass.d.ts +6 -0
- package/src/sdk/greengrass.js +6 -0
- package/src/sdk/greengrass.js.map +1 -0
- package/src/sdk/py.d.ts +2 -0
- package/src/sdk/py.js +1 -0
- package/src/sdk/py.js.map +1 -1
- package/src/smithy/project/generator.d.ts +1 -1
- package/src/smithy/react-connection/generator.d.ts +1 -1
- package/src/terraform/project/generator.d.ts +1 -1
- package/src/trpc/react/generator.d.ts +1 -1
- package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
- package/src/ts/agent/guardrails/__snapshots__/generator.spec.ts.snap +54 -3
- package/src/ts/agent/guardrails/generator.js +2 -1
- package/src/ts/agent/guardrails/generator.js.map +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +1 -1
- package/src/ts/astro-docs/generator.d.ts +1 -1
- package/src/ts/dcr-proxy/generator.d.ts +1 -1
- package/src/ts/dynamodb/generator.d.ts +1 -1
- package/src/ts/lambda-function/generator.d.ts +1 -1
- package/src/ts/lib/generator.d.ts +1 -1
- package/src/ts/nx-generator/generator.d.ts +1 -1
- package/src/ts/nx-migration/generator.d.ts +1 -1
- package/src/ts/nx-plugin/generator.d.ts +1 -1
- package/src/ts/rdb/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/utils/__snapshots__/shared-greengrass-scripts.spec.ts.snap +1279 -0
- package/src/utils/files/common/scripts/src/greengrass/build-artifact.ts.template +236 -0
- package/src/utils/files/common/scripts/src/greengrass/component-logs.ts.template +48 -0
- package/src/utils/files/common/scripts/src/greengrass/deploy-local.ts.template +497 -0
- package/src/utils/files/common/scripts/src/greengrass/recipe-utils.ts.template +356 -0
- package/src/utils/files/common/scripts/src/greengrass/zip-writer.ts.template +121 -0
- package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +3 -3
- package/src/utils/files/terraform/src/metrics/metrics.tf.template +1 -1
- package/src/utils/greengrass/constants.d.ts +58 -0
- package/src/utils/greengrass/constants.js +63 -0
- package/src/utils/greengrass/constants.js.map +1 -0
- package/src/utils/greengrass/naming.d.ts +46 -0
- package/src/utils/greengrass/naming.js +67 -0
- package/src/utils/greengrass/naming.js.map +1 -0
- package/src/utils/greengrass/recipe.d.ts +143 -0
- package/src/utils/greengrass/recipe.js +119 -0
- package/src/utils/greengrass/recipe.js.map +1 -0
- package/src/utils/greengrass-constructs/files/cdk/app/greengrass-component/__componentDirName__.ts.template +42 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/artifact-bucket.ts.template +82 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/component-version.ts.template +370 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/deployment.ts.template +276 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/index.ts.template +5 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/next-version.ts.template +119 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/recipe.ts.template +90 -0
- package/src/utils/greengrass-constructs/files/next-version/__nextVersionFileName__.template +195 -0
- package/src/utils/greengrass-constructs/files/terraform/app/greengrass-component/__componentDirName__/__componentDirName__.tf.template +26 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/artifact-bucket/main.tf.template +204 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/component-version/main.tf.template +156 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/deployment/main.tf.template +186 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.d.ts +57 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js +154 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js.map +1 -0
- package/src/utils/guardrail-constructs/files/terraform/app/guardrails/__nameKebabCase__/__nameKebabCase__-guardrail.tf.template +18 -1
- package/src/utils/guardrail-constructs/guardrail-constructs.d.ts +5 -0
- package/src/utils/guardrail-constructs/guardrail-constructs.js +8 -0
- package/src/utils/guardrail-constructs/guardrail-constructs.js.map +1 -1
- package/src/utils/guardrail-constructs/guardrail-wiring.d.ts +7 -0
- package/src/utils/guardrail-constructs/guardrail-wiring.js +6 -5
- package/src/utils/guardrail-constructs/guardrail-wiring.js.map +1 -1
- package/src/utils/nx.d.ts +15 -0
- package/src/utils/nx.js +32 -0
- package/src/utils/nx.js.map +1 -1
- package/src/utils/shared-greengrass-scripts.d.ts +21 -0
- package/src/utils/shared-greengrass-scripts.js +43 -0
- package/src/utils/shared-greengrass-scripts.js.map +1 -0
- package/src/utils/test/fake-aws/fake_aws.py +205 -0
- package/src/utils/test/python-dependencies.d.ts +6 -0
- package/src/utils/test/python-dependencies.js +19 -0
- package/src/utils/test/python-dependencies.js.map +1 -1
- package/src/utils/test/warm-python-cache.js +5 -1
- package/src/utils/test/warm-python-cache.js.map +1 -1
- package/src/utils/versions.d.ts +7 -0
- package/src/utils/versions.js +7 -0
- package/src/utils/versions.js.map +1 -1
- /package/src/{ts/agent/guardrails/files/agent → utils/guardrail-constructs/files/policy}/guardrail.json.template +0 -0
|
@@ -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","
|
|
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.
|
|
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 ${
|
|
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.
|
|
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"}
|
package/src/utils/nx.d.ts
CHANGED
|
@@ -86,6 +86,21 @@ export declare const addComponentGeneratorMetadata: (tree: Tree, projectName: st
|
|
|
86
86
|
}, componentPath: string, componentName?: string, additionalMetadata?: {
|
|
87
87
|
[key: string]: any;
|
|
88
88
|
}) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Merge `patch` into the component metadata entry matched on generator id and
|
|
91
|
+
* name, leaving every other recorded value alone. A no-op when no entry matches
|
|
92
|
+
* or when the patch changes nothing.
|
|
93
|
+
*
|
|
94
|
+
* `addComponentGeneratorMetadata` leaves an existing entry untouched, so a
|
|
95
|
+
* generator whose options may legitimately change between runs pairs the two:
|
|
96
|
+
* the add records a first run, this converges the values a later run resolved
|
|
97
|
+
* differently.
|
|
98
|
+
*/
|
|
99
|
+
export declare const updateComponentGeneratorMetadata: (tree: Tree, projectName: string, info: {
|
|
100
|
+
id: string;
|
|
101
|
+
}, componentName: string | undefined, patch: {
|
|
102
|
+
[key: string]: any;
|
|
103
|
+
}) => void;
|
|
89
104
|
/**
|
|
90
105
|
* Merge a generator's own config into an `nx.json` `targetDefaults` value,
|
|
91
106
|
* preserving whatever the workspace already had. `apply` receives the config to
|
package/src/utils/nx.js
CHANGED
|
@@ -139,6 +139,38 @@ export const getPackageVersion = ()=>{
|
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Merge `patch` into the component metadata entry matched on generator id and
|
|
144
|
+
* name, leaving every other recorded value alone. A no-op when no entry matches
|
|
145
|
+
* or when the patch changes nothing.
|
|
146
|
+
*
|
|
147
|
+
* `addComponentGeneratorMetadata` leaves an existing entry untouched, so a
|
|
148
|
+
* generator whose options may legitimately change between runs pairs the two:
|
|
149
|
+
* the add records a first run, this converges the values a later run resolved
|
|
150
|
+
* differently.
|
|
151
|
+
*/ export const updateComponentGeneratorMetadata = (tree, projectName, info, componentName, patch)=>{
|
|
152
|
+
const config = readProjectConfigurationUnqualified(tree, projectName);
|
|
153
|
+
const existingComponents = config?.metadata?.components ?? [];
|
|
154
|
+
const updated = existingComponents.map((component)=>component.generator === info.id && component.name === componentName ? {
|
|
155
|
+
...component,
|
|
156
|
+
...patch
|
|
157
|
+
} : component);
|
|
158
|
+
if (JSON.stringify(updated) === JSON.stringify(existingComponents)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Place metadata before targets for a stable serialized key order.
|
|
162
|
+
const { targets, ...rest } = config;
|
|
163
|
+
updateProjectConfiguration(tree, config.name, {
|
|
164
|
+
...rest,
|
|
165
|
+
metadata: {
|
|
166
|
+
...config?.metadata,
|
|
167
|
+
components: updated
|
|
168
|
+
},
|
|
169
|
+
...targets ? {
|
|
170
|
+
targets
|
|
171
|
+
} : {}
|
|
172
|
+
});
|
|
173
|
+
};
|
|
142
174
|
/**
|
|
143
175
|
* Merge a generator's own config into an `nx.json` `targetDefaults` value,
|
|
144
176
|
* preserving whatever the workspace already had. `apply` receives the config to
|
package/src/utils/nx.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/nx.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type ProjectConfiguration,\n readJson,\n readProjectConfiguration,\n type TargetConfiguration,\n type TargetDefaultValue,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport * as path from 'path';\nimport PackageJson from '../../package.json' with { type: 'json' };\nimport { NX_PLUGIN_MCP_PACKAGE_NAME } from './mcp.js';\nimport { toSnakeCase } from './names.js';\nimport { getNpmScope, getNpmScopePrefix } from './npm-scope.js';\n\nexport type { GeneratorInfo, NxGeneratorInfo } from './generators.js';\nexport { buildGeneratorInfoList } from './generators.js';\n\nimport { buildGeneratorInfoList, type GeneratorInfo } from './generators.js';\n\nconst GENERATORS = buildGeneratorInfoList(\n path.resolve(import.meta.dirname, '..', '..'),\n);\n\n/**\n * List Nx Plugin for AWS generators\n * @param includeHidden include hidden generators (default false)\n */\nexport const listGenerators = (includeHidden = false) =>\n GENERATORS.filter((g) => includeHidden || !g.hidden);\n\n/**\n * Return generator information. Call this from a generator method with import.meta.filename\n */\nexport const getGeneratorInfo = (generatorFileName: string): GeneratorInfo => {\n const { dir, name } = path.parse(path.resolve(generatorFileName));\n const resolvedFactoryPath = path.join(dir, name);\n return GENERATORS.find(\n (generatorInfo) =>\n generatorInfo.resolvedFactoryPath === resolvedFactoryPath,\n );\n};\n\nexport const getPackageVersion = () => {\n return PackageJson.version;\n};\n\n/**\n * True inside the plugin's own monorepo, where the plugin is the source rather\n * than a dependency.\n */\nexport const isNxPluginForAwsWorkspace = (tree: Tree): boolean => {\n const rootPackageJson = tree.exists('package.json')\n ? readJson(tree, 'package.json')\n : undefined;\n return rootPackageJson?.name === '@aws/nx-plugin-source';\n};\n\n/**\n * The `@aws/nx-plugin` devDependency entry for a workspace the plugin generates\n * into, keyed to the version the running generators come from. Empty inside the\n * plugin's own monorepo.\n */\nexport const nxPluginSelfDependency = (tree: Tree): Record<string, string> =>\n isNxPluginForAwsWorkspace(tree)\n ? {}\n : { [PackageJson.name]: `^${PackageJson.version}` };\n\n/**\n * The `@aws/nx-plugin-mcp` devDependency entry, keyed to the running generators'\n * version — the two packages are released in lockstep. Declaring it pins the MCP\n * server the vended config runs in the workspace's lockfile, so upgrading it\n * upgrades the server. Empty inside the plugin's own monorepo.\n */\nexport const nxPluginMcpDependency = (tree: Tree): Record<string, string> =>\n isNxPluginForAwsWorkspace(tree)\n ? {}\n : { [NX_PLUGIN_MCP_PACKAGE_NAME]: `^${PackageJson.version}` };\n\n/**\n * Read a project configuration where the project name may not be fully qualified (ie may omit the scope prefix)\n */\nexport const readProjectConfigurationUnqualified = (\n tree: Tree,\n projectName: string,\n) => {\n try {\n return readProjectConfiguration(tree, projectName);\n } catch (e) {\n // Attempt to find the project without the scope\n const project = [...getProjects(tree).values()].find(\n (p) =>\n p.name &&\n (p.name === `${getNpmScopePrefix(tree)}${projectName}` || // TypeScript fully-qualified name\n p.name === `${toSnakeCase(getNpmScope(tree))}.${projectName}`), // Python fully-qualified name\n );\n if (project) {\n return project;\n }\n throw e;\n }\n};\n\n/**\n * Returns whether a project with the given name already exists in the workspace.\n * The project name may be unqualified (ie may omit the scope prefix).\n */\nexport const projectExists = (tree: Tree, projectName: string): boolean => {\n try {\n readProjectConfigurationUnqualified(tree, projectName);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Add metadata about the generator that generated the project to the project.json\n */\nexport const addGeneratorMetadata = (\n tree: Tree,\n projectName: string,\n info: { id: string },\n additionalMetadata?: { [key: string]: any },\n) => {\n const config = readProjectConfigurationUnqualified(tree, projectName);\n const metadata = {\n ...config?.metadata,\n generator: info.id,\n ...additionalMetadata,\n };\n // Skip the write when nothing changed so re-running doesn't reserialize\n // project.json. metadata is plain JSON built in a fixed key order, so a\n // stringify comparison is exact here.\n if (JSON.stringify(config?.metadata) === JSON.stringify(metadata)) {\n return;\n }\n // Place metadata immediately before targets so the serialized key order is\n // identical whether or not metadata already existed (matching the order Nx\n // normalizes a read config to), keeping the change a pure metadata insert.\n const { targets, ...rest } = config;\n updateProjectConfiguration(tree, config.name, {\n ...rest,\n metadata: metadata as any,\n ...(targets ? { targets } : {}),\n });\n};\n\n/**\n * Represents a component entry in project metadata\n */\nexport interface ComponentMetadata {\n readonly generator: string;\n readonly name?: string;\n /**\n * What this entry wires up. For a connection that is the target — the thing\n * being connected to — paired with `sourcePath` below.\n */\n readonly path?: string;\n /**\n * The component the connection is made *from*, when both ends are components\n * of a project rather than whole projects.\n *\n * A connection records the project it lives on and the target it reaches, which\n * alone can't tell which of several components on that project is wired up.\n * Recorded as a path for the same reason `path` is: it identifies a component\n * within its project without depending on a name that may repeat.\n */\n readonly sourcePath?: string;\n [key: string]: any;\n}\n\n/**\n * Add metadata about the generator that generated the component to the project.json\n *\n * A connection between two components passes the source's path as\n * `additionalMetadata.sourcePath`, so the pair is identifiable rather than just\n * the two projects.\n */\nexport const addComponentGeneratorMetadata = (\n tree: Tree,\n projectName: string,\n info: { id: string },\n componentPath: string,\n componentName?: string,\n additionalMetadata?: { [key: string]: any },\n) => {\n const config = readProjectConfigurationUnqualified(tree, projectName);\n\n const existingComponents = (config?.metadata as any)?.components ?? [];\n const alreadyAdded = existingComponents.filter(\n (c: any) => c.generator === info.id && c.name === componentName,\n );\n\n if (alreadyAdded.length === 0) {\n const componentMetadata: ComponentMetadata = {\n generator: info.id,\n path: componentPath,\n ...(componentName ? { name: componentName } : {}),\n ...additionalMetadata,\n };\n // Place metadata before targets for a stable serialized key order.\n const { targets, ...rest } = config;\n updateProjectConfiguration(tree, config.name, {\n ...rest,\n metadata: {\n ...config?.metadata,\n components: [...existingComponents, componentMetadata],\n } as any,\n ...(targets ? { targets } : {}),\n });\n }\n};\n\n/**\n * Merge a generator's own config into an `nx.json` `targetDefaults` value,\n * preserving whatever the workspace already had. `apply` receives the config to\n * layer onto and returns the merged config (e.g. `(base) => ({ cache: true,\n * ...base })`); it must be idempotent so re-running the generator is a no-op.\n *\n * Nx 23.1 widened each value to either a config object or an ordered array of\n * filtered entries. The plugin only contributes unfiltered (catch-all) config,\n * so the input's shape is preserved: an object (or unset) stays an object;\n * an array keeps the user's filtered entries and `apply` merges into its first\n * unfiltered entry, prepending one as the base if none exists (Nx layers later\n * matches on top, so our catch-all must stay first to not override a filter).\n */\nexport const mergeTargetDefault = (\n value: TargetDefaultValue | undefined,\n apply: (base: Partial<TargetConfiguration>) => Partial<TargetConfiguration>,\n): TargetDefaultValue => {\n if (!Array.isArray(value)) {\n return apply(value ?? {});\n }\n const catchAllIndex = value.findIndex((entry) => entry.filter === undefined);\n if (catchAllIndex === -1) {\n return [apply({}), ...value];\n }\n return value.map((entry, index) =>\n index === catchAllIndex ? apply(entry) : entry,\n );\n};\n\n/**\n * A single entry in an Nx `dependsOn` array.\n * Matches the shape accepted by Nx without requiring a devkit type import.\n */\nexport type TargetDependency =\n | string\n | {\n projects?: string | string[];\n target: string;\n params?: 'ignore' | 'forward';\n };\n\nconst targetDependencyEquals = (a: TargetDependency, b: TargetDependency) => {\n if (typeof a === 'string' || typeof b === 'string') {\n return a === b;\n }\n if (a.target !== b.target || a.params !== b.params) {\n return false;\n }\n const aProjects = Array.isArray(a.projects)\n ? a.projects\n : a.projects !== undefined\n ? [a.projects]\n : [];\n const bProjects = Array.isArray(b.projects)\n ? b.projects\n : b.projects !== undefined\n ? [b.projects]\n : [];\n if (aProjects.length !== bProjects.length) {\n return false;\n }\n return aProjects.every((p, i) => p === bProjects[i]);\n};\n\n// The order target properties are authored in. Nx preserves a target's\n// authored key order when it reads then rewrites an existing project.json,\n// except `options` and `configurations`, which it always moves to the end (in\n// that order). Keeping `options`/`configurations` last here therefore makes a\n// generator's first-run output byte-identical to subsequent runs (idempotency);\n// the remaining keys are ordered to read intuitively.\nconst NX_TARGET_KEY_ORDER = [\n 'executor',\n 'dependsOn',\n 'continuous',\n 'cache',\n 'inputs',\n 'outputs',\n 'metadata',\n 'defaultConfiguration',\n 'options',\n 'configurations',\n];\n\n/**\n * Reorder a target's keys to match Nx's own serialization order. Nx reorders\n * target properties when it rewrites an existing project.json, so applying the\n * same order when first authoring a target keeps generators idempotent.\n */\nexport const normalizeTargetKeyOrder = <T extends object>(target: T): T =>\n Object.fromEntries(\n Object.entries(target as Record<string, unknown>).sort(([a], [b]) => {\n const ai = NX_TARGET_KEY_ORDER.indexOf(a);\n const bi = NX_TARGET_KEY_ORDER.indexOf(b);\n return (\n (ai === -1 ? NX_TARGET_KEY_ORDER.length : ai) -\n (bi === -1 ? NX_TARGET_KEY_ORDER.length : bi)\n );\n }),\n ) as T;\n\n/**\n * Merge the configuration a generator owns into a project's existing target, so\n * re-running converges that configuration without discarding what other\n * generators (or the user) contributed to the same target.\n *\n * `dependsOn` is unioned: existing entries keep their order and the desired ones\n * are appended only when absent, so a re-run neither drops an entry (e.g. the\n * `bundle` a lambda function adds to `build`) nor duplicates one. Every other\n * key the caller passes wins, since those are the ones the generator owns.\n *\n * The result carries Nx's own key order, so a target authored on the first run\n * serializes identically to the same target merged into on a re-run.\n */\nexport const mergeTarget = (\n existing: TargetConfiguration | undefined,\n desired: TargetConfiguration,\n): TargetConfiguration => {\n const merged: TargetConfiguration = { ...existing, ...desired };\n const dependsOn = [...(existing?.dependsOn ?? [])];\n for (const dependency of desired.dependsOn ?? []) {\n if (!dependsOn.some((d) => targetDependencyEquals(d, dependency))) {\n dependsOn.push(dependency);\n }\n }\n if (dependsOn.length > 0) {\n merged.dependsOn = dependsOn;\n }\n return normalizeTargetKeyOrder(merged);\n};\n\n/**\n * Mutate the project to add the dependency to the target if not already present\n * Adds the target if not present.\n *\n * Accepts either a string target name (e.g. `'build'`) or a dependency config\n * object (e.g. `{ projects: ['foo'], target: 'dev' }`). Deduplicates\n * using deep equality so re-running a generator does not grow `dependsOn`.\n */\nexport const addDependencyToTargetIfNotPresent = (\n project: ProjectConfiguration,\n target: string,\n dependency: TargetDependency,\n) => {\n project.targets ??= {};\n project.targets[target] ??= {};\n project.targets[target].dependsOn ??= [];\n const dependsOn = project.targets[target].dependsOn;\n // Leave existing dependencies in place so re-running does not reorder them;\n // only append when the dependency is genuinely absent.\n if (!dependsOn.some((d) => targetDependencyEquals(d, dependency))) {\n dependsOn.push(dependency);\n }\n // Keep key order aligned with Nx's so first run matches subsequent runs.\n project.targets[target] = normalizeTargetKeyOrder(project.targets[target]);\n};\n\n/**\n * Register a dependency that produces a deployable artifact on both `build` and\n * `assemble`.\n *\n * `build` is the everything target: artifacts plus the quality gates (lint,\n * test, typecheck). `assemble` is its artifact-only sibling, which the deploy\n * targets depend on so a deploy builds what it deploys and nothing else.\n */\nexport const addArtifactDependencyToTargets = (\n project: ProjectConfiguration,\n dependency: TargetDependency,\n) => {\n addDependencyToTargetIfNotPresent(project, 'build', dependency);\n addDependencyToTargetIfNotPresent(project, 'assemble', dependency);\n};\n\n/**\n * Register another project whose artifacts this one consumes, on both `build`\n * and `assemble`.\n *\n * The shared infrastructure project reads the artifacts of every project it\n * deploys, and those cross-project edges are explicit because Nx's `^` operator\n * only follows package dependencies. `assemble` mirrors the list against the\n * consumed projects' own `assemble`, keeping the artifact-only chain closed.\n */\nexport const addArtifactProjectToTargets = (\n project: ProjectConfiguration,\n consumedProjectName: string,\n) => {\n addDependencyToTargetIfNotPresent(\n project,\n 'build',\n `${consumedProjectName}:build`,\n );\n addDependencyToTargetIfNotPresent(\n project,\n 'assemble',\n `${consumedProjectName}:assemble`,\n );\n};\n\n/**\n * Register a component's `<name>-dev` target under the project-level `dev`\n * target, so `nx run <project>:dev` starts every component in the project.\n *\n * Component generators author their own continuous `<name>-dev` runner and call\n * this to aggregate it. Every component added to the project accumulates its\n * `<name>-dev` under `dev`. Nx keys tasks by `project:target`, so any\n * dependencies shared between components run exactly once.\n */\nexport const addComponentDevTarget = (\n targets: Record<string, ProjectConfiguration['targets'][string]>,\n componentDevTargetName: string,\n) => {\n targets['dev'] ??= { continuous: true, dependsOn: [] };\n const projectDev = targets['dev'];\n projectDev.continuous = true;\n projectDev.dependsOn ??= [];\n // Append in component-creation order; dedupe keeps re-runs idempotent.\n if (!projectDev.dependsOn.includes(componentDevTargetName)) {\n projectDev.dependsOn.push(componentDevTargetName);\n }\n targets['dev'] = normalizeTargetKeyOrder(projectDev);\n};\n"],"names":["getProjects","readJson","readProjectConfiguration","updateProjectConfiguration","path","PackageJson","type","NX_PLUGIN_MCP_PACKAGE_NAME","toSnakeCase","getNpmScope","getNpmScopePrefix","buildGeneratorInfoList","GENERATORS","resolve","dirname","listGenerators","includeHidden","filter","g","hidden","getGeneratorInfo","generatorFileName","dir","name","parse","resolvedFactoryPath","join","find","generatorInfo","getPackageVersion","version","isNxPluginForAwsWorkspace","tree","rootPackageJson","exists","undefined","nxPluginSelfDependency","nxPluginMcpDependency","readProjectConfigurationUnqualified","projectName","e","project","values","p","projectExists","addGeneratorMetadata","info","additionalMetadata","config","metadata","generator","id","JSON","stringify","targets","rest","addComponentGeneratorMetadata","componentPath","componentName","existingComponents","components","alreadyAdded","c","length","componentMetadata","mergeTargetDefault","value","apply","Array","isArray","catchAllIndex","findIndex","entry","map","index","targetDependencyEquals","a","b","target","params","aProjects","projects","bProjects","every","i","NX_TARGET_KEY_ORDER","normalizeTargetKeyOrder","Object","fromEntries","entries","sort","ai","indexOf","bi","mergeTarget","existing","desired","merged","dependsOn","dependency","some","d","push","addDependencyToTargetIfNotPresent","addArtifactDependencyToTargets","addArtifactProjectToTargets","consumedProjectName","addComponentDevTarget","componentDevTargetName","continuous","projectDev","includes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/nx.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type ProjectConfiguration,\n readJson,\n readProjectConfiguration,\n type TargetConfiguration,\n type TargetDefaultValue,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport * as path from 'path';\nimport PackageJson from '../../package.json' with { type: 'json' };\nimport { NX_PLUGIN_MCP_PACKAGE_NAME } from './mcp.js';\nimport { toSnakeCase } from './names.js';\nimport { getNpmScope, getNpmScopePrefix } from './npm-scope.js';\n\nexport type { GeneratorInfo, NxGeneratorInfo } from './generators.js';\nexport { buildGeneratorInfoList } from './generators.js';\n\nimport { buildGeneratorInfoList, type GeneratorInfo } from './generators.js';\n\nconst GENERATORS = buildGeneratorInfoList(\n path.resolve(import.meta.dirname, '..', '..'),\n);\n\n/**\n * List Nx Plugin for AWS generators\n * @param includeHidden include hidden generators (default false)\n */\nexport const listGenerators = (includeHidden = false) =>\n GENERATORS.filter((g) => includeHidden || !g.hidden);\n\n/**\n * Return generator information. Call this from a generator method with import.meta.filename\n */\nexport const getGeneratorInfo = (generatorFileName: string): GeneratorInfo => {\n const { dir, name } = path.parse(path.resolve(generatorFileName));\n const resolvedFactoryPath = path.join(dir, name);\n return GENERATORS.find(\n (generatorInfo) =>\n generatorInfo.resolvedFactoryPath === resolvedFactoryPath,\n );\n};\n\nexport const getPackageVersion = () => {\n return PackageJson.version;\n};\n\n/**\n * True inside the plugin's own monorepo, where the plugin is the source rather\n * than a dependency.\n */\nexport const isNxPluginForAwsWorkspace = (tree: Tree): boolean => {\n const rootPackageJson = tree.exists('package.json')\n ? readJson(tree, 'package.json')\n : undefined;\n return rootPackageJson?.name === '@aws/nx-plugin-source';\n};\n\n/**\n * The `@aws/nx-plugin` devDependency entry for a workspace the plugin generates\n * into, keyed to the version the running generators come from. Empty inside the\n * plugin's own monorepo.\n */\nexport const nxPluginSelfDependency = (tree: Tree): Record<string, string> =>\n isNxPluginForAwsWorkspace(tree)\n ? {}\n : { [PackageJson.name]: `^${PackageJson.version}` };\n\n/**\n * The `@aws/nx-plugin-mcp` devDependency entry, keyed to the running generators'\n * version — the two packages are released in lockstep. Declaring it pins the MCP\n * server the vended config runs in the workspace's lockfile, so upgrading it\n * upgrades the server. Empty inside the plugin's own monorepo.\n */\nexport const nxPluginMcpDependency = (tree: Tree): Record<string, string> =>\n isNxPluginForAwsWorkspace(tree)\n ? {}\n : { [NX_PLUGIN_MCP_PACKAGE_NAME]: `^${PackageJson.version}` };\n\n/**\n * Read a project configuration where the project name may not be fully qualified (ie may omit the scope prefix)\n */\nexport const readProjectConfigurationUnqualified = (\n tree: Tree,\n projectName: string,\n) => {\n try {\n return readProjectConfiguration(tree, projectName);\n } catch (e) {\n // Attempt to find the project without the scope\n const project = [...getProjects(tree).values()].find(\n (p) =>\n p.name &&\n (p.name === `${getNpmScopePrefix(tree)}${projectName}` || // TypeScript fully-qualified name\n p.name === `${toSnakeCase(getNpmScope(tree))}.${projectName}`), // Python fully-qualified name\n );\n if (project) {\n return project;\n }\n throw e;\n }\n};\n\n/**\n * Returns whether a project with the given name already exists in the workspace.\n * The project name may be unqualified (ie may omit the scope prefix).\n */\nexport const projectExists = (tree: Tree, projectName: string): boolean => {\n try {\n readProjectConfigurationUnqualified(tree, projectName);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Add metadata about the generator that generated the project to the project.json\n */\nexport const addGeneratorMetadata = (\n tree: Tree,\n projectName: string,\n info: { id: string },\n additionalMetadata?: { [key: string]: any },\n) => {\n const config = readProjectConfigurationUnqualified(tree, projectName);\n const metadata = {\n ...config?.metadata,\n generator: info.id,\n ...additionalMetadata,\n };\n // Skip the write when nothing changed so re-running doesn't reserialize\n // project.json. metadata is plain JSON built in a fixed key order, so a\n // stringify comparison is exact here.\n if (JSON.stringify(config?.metadata) === JSON.stringify(metadata)) {\n return;\n }\n // Place metadata immediately before targets so the serialized key order is\n // identical whether or not metadata already existed (matching the order Nx\n // normalizes a read config to), keeping the change a pure metadata insert.\n const { targets, ...rest } = config;\n updateProjectConfiguration(tree, config.name, {\n ...rest,\n metadata: metadata as any,\n ...(targets ? { targets } : {}),\n });\n};\n\n/**\n * Represents a component entry in project metadata\n */\nexport interface ComponentMetadata {\n readonly generator: string;\n readonly name?: string;\n /**\n * What this entry wires up. For a connection that is the target — the thing\n * being connected to — paired with `sourcePath` below.\n */\n readonly path?: string;\n /**\n * The component the connection is made *from*, when both ends are components\n * of a project rather than whole projects.\n *\n * A connection records the project it lives on and the target it reaches, which\n * alone can't tell which of several components on that project is wired up.\n * Recorded as a path for the same reason `path` is: it identifies a component\n * within its project without depending on a name that may repeat.\n */\n readonly sourcePath?: string;\n [key: string]: any;\n}\n\n/**\n * Add metadata about the generator that generated the component to the project.json\n *\n * A connection between two components passes the source's path as\n * `additionalMetadata.sourcePath`, so the pair is identifiable rather than just\n * the two projects.\n */\nexport const addComponentGeneratorMetadata = (\n tree: Tree,\n projectName: string,\n info: { id: string },\n componentPath: string,\n componentName?: string,\n additionalMetadata?: { [key: string]: any },\n) => {\n const config = readProjectConfigurationUnqualified(tree, projectName);\n\n const existingComponents = (config?.metadata as any)?.components ?? [];\n const alreadyAdded = existingComponents.filter(\n (c: any) => c.generator === info.id && c.name === componentName,\n );\n\n if (alreadyAdded.length === 0) {\n const componentMetadata: ComponentMetadata = {\n generator: info.id,\n path: componentPath,\n ...(componentName ? { name: componentName } : {}),\n ...additionalMetadata,\n };\n // Place metadata before targets for a stable serialized key order.\n const { targets, ...rest } = config;\n updateProjectConfiguration(tree, config.name, {\n ...rest,\n metadata: {\n ...config?.metadata,\n components: [...existingComponents, componentMetadata],\n } as any,\n ...(targets ? { targets } : {}),\n });\n }\n};\n\n/**\n * Merge `patch` into the component metadata entry matched on generator id and\n * name, leaving every other recorded value alone. A no-op when no entry matches\n * or when the patch changes nothing.\n *\n * `addComponentGeneratorMetadata` leaves an existing entry untouched, so a\n * generator whose options may legitimately change between runs pairs the two:\n * the add records a first run, this converges the values a later run resolved\n * differently.\n */\nexport const updateComponentGeneratorMetadata = (\n tree: Tree,\n projectName: string,\n info: { id: string },\n componentName: string | undefined,\n patch: { [key: string]: any },\n) => {\n const config = readProjectConfigurationUnqualified(tree, projectName);\n const existingComponents: ComponentMetadata[] =\n (config?.metadata as any)?.components ?? [];\n\n const updated = existingComponents.map((component) =>\n component.generator === info.id && component.name === componentName\n ? { ...component, ...patch }\n : component,\n );\n if (JSON.stringify(updated) === JSON.stringify(existingComponents)) {\n return;\n }\n\n // Place metadata before targets for a stable serialized key order.\n const { targets, ...rest } = config;\n updateProjectConfiguration(tree, config.name, {\n ...rest,\n metadata: {\n ...config?.metadata,\n components: updated,\n } as any,\n ...(targets ? { targets } : {}),\n });\n};\n\n/**\n * Merge a generator's own config into an `nx.json` `targetDefaults` value,\n * preserving whatever the workspace already had. `apply` receives the config to\n * layer onto and returns the merged config (e.g. `(base) => ({ cache: true,\n * ...base })`); it must be idempotent so re-running the generator is a no-op.\n *\n * Nx 23.1 widened each value to either a config object or an ordered array of\n * filtered entries. The plugin only contributes unfiltered (catch-all) config,\n * so the input's shape is preserved: an object (or unset) stays an object;\n * an array keeps the user's filtered entries and `apply` merges into its first\n * unfiltered entry, prepending one as the base if none exists (Nx layers later\n * matches on top, so our catch-all must stay first to not override a filter).\n */\nexport const mergeTargetDefault = (\n value: TargetDefaultValue | undefined,\n apply: (base: Partial<TargetConfiguration>) => Partial<TargetConfiguration>,\n): TargetDefaultValue => {\n if (!Array.isArray(value)) {\n return apply(value ?? {});\n }\n const catchAllIndex = value.findIndex((entry) => entry.filter === undefined);\n if (catchAllIndex === -1) {\n return [apply({}), ...value];\n }\n return value.map((entry, index) =>\n index === catchAllIndex ? apply(entry) : entry,\n );\n};\n\n/**\n * A single entry in an Nx `dependsOn` array.\n * Matches the shape accepted by Nx without requiring a devkit type import.\n */\nexport type TargetDependency =\n | string\n | {\n projects?: string | string[];\n target: string;\n params?: 'ignore' | 'forward';\n };\n\nconst targetDependencyEquals = (a: TargetDependency, b: TargetDependency) => {\n if (typeof a === 'string' || typeof b === 'string') {\n return a === b;\n }\n if (a.target !== b.target || a.params !== b.params) {\n return false;\n }\n const aProjects = Array.isArray(a.projects)\n ? a.projects\n : a.projects !== undefined\n ? [a.projects]\n : [];\n const bProjects = Array.isArray(b.projects)\n ? b.projects\n : b.projects !== undefined\n ? [b.projects]\n : [];\n if (aProjects.length !== bProjects.length) {\n return false;\n }\n return aProjects.every((p, i) => p === bProjects[i]);\n};\n\n// The order target properties are authored in. Nx preserves a target's\n// authored key order when it reads then rewrites an existing project.json,\n// except `options` and `configurations`, which it always moves to the end (in\n// that order). Keeping `options`/`configurations` last here therefore makes a\n// generator's first-run output byte-identical to subsequent runs (idempotency);\n// the remaining keys are ordered to read intuitively.\nconst NX_TARGET_KEY_ORDER = [\n 'executor',\n 'dependsOn',\n 'continuous',\n 'cache',\n 'inputs',\n 'outputs',\n 'metadata',\n 'defaultConfiguration',\n 'options',\n 'configurations',\n];\n\n/**\n * Reorder a target's keys to match Nx's own serialization order. Nx reorders\n * target properties when it rewrites an existing project.json, so applying the\n * same order when first authoring a target keeps generators idempotent.\n */\nexport const normalizeTargetKeyOrder = <T extends object>(target: T): T =>\n Object.fromEntries(\n Object.entries(target as Record<string, unknown>).sort(([a], [b]) => {\n const ai = NX_TARGET_KEY_ORDER.indexOf(a);\n const bi = NX_TARGET_KEY_ORDER.indexOf(b);\n return (\n (ai === -1 ? NX_TARGET_KEY_ORDER.length : ai) -\n (bi === -1 ? NX_TARGET_KEY_ORDER.length : bi)\n );\n }),\n ) as T;\n\n/**\n * Merge the configuration a generator owns into a project's existing target, so\n * re-running converges that configuration without discarding what other\n * generators (or the user) contributed to the same target.\n *\n * `dependsOn` is unioned: existing entries keep their order and the desired ones\n * are appended only when absent, so a re-run neither drops an entry (e.g. the\n * `bundle` a lambda function adds to `build`) nor duplicates one. Every other\n * key the caller passes wins, since those are the ones the generator owns.\n *\n * The result carries Nx's own key order, so a target authored on the first run\n * serializes identically to the same target merged into on a re-run.\n */\nexport const mergeTarget = (\n existing: TargetConfiguration | undefined,\n desired: TargetConfiguration,\n): TargetConfiguration => {\n const merged: TargetConfiguration = { ...existing, ...desired };\n const dependsOn = [...(existing?.dependsOn ?? [])];\n for (const dependency of desired.dependsOn ?? []) {\n if (!dependsOn.some((d) => targetDependencyEquals(d, dependency))) {\n dependsOn.push(dependency);\n }\n }\n if (dependsOn.length > 0) {\n merged.dependsOn = dependsOn;\n }\n return normalizeTargetKeyOrder(merged);\n};\n\n/**\n * Mutate the project to add the dependency to the target if not already present\n * Adds the target if not present.\n *\n * Accepts either a string target name (e.g. `'build'`) or a dependency config\n * object (e.g. `{ projects: ['foo'], target: 'dev' }`). Deduplicates\n * using deep equality so re-running a generator does not grow `dependsOn`.\n */\nexport const addDependencyToTargetIfNotPresent = (\n project: ProjectConfiguration,\n target: string,\n dependency: TargetDependency,\n) => {\n project.targets ??= {};\n project.targets[target] ??= {};\n project.targets[target].dependsOn ??= [];\n const dependsOn = project.targets[target].dependsOn;\n // Leave existing dependencies in place so re-running does not reorder them;\n // only append when the dependency is genuinely absent.\n if (!dependsOn.some((d) => targetDependencyEquals(d, dependency))) {\n dependsOn.push(dependency);\n }\n // Keep key order aligned with Nx's so first run matches subsequent runs.\n project.targets[target] = normalizeTargetKeyOrder(project.targets[target]);\n};\n\n/**\n * Register a dependency that produces a deployable artifact on both `build` and\n * `assemble`.\n *\n * `build` is the everything target: artifacts plus the quality gates (lint,\n * test, typecheck). `assemble` is its artifact-only sibling, which the deploy\n * targets depend on so a deploy builds what it deploys and nothing else.\n */\nexport const addArtifactDependencyToTargets = (\n project: ProjectConfiguration,\n dependency: TargetDependency,\n) => {\n addDependencyToTargetIfNotPresent(project, 'build', dependency);\n addDependencyToTargetIfNotPresent(project, 'assemble', dependency);\n};\n\n/**\n * Register another project whose artifacts this one consumes, on both `build`\n * and `assemble`.\n *\n * The shared infrastructure project reads the artifacts of every project it\n * deploys, and those cross-project edges are explicit because Nx's `^` operator\n * only follows package dependencies. `assemble` mirrors the list against the\n * consumed projects' own `assemble`, keeping the artifact-only chain closed.\n */\nexport const addArtifactProjectToTargets = (\n project: ProjectConfiguration,\n consumedProjectName: string,\n) => {\n addDependencyToTargetIfNotPresent(\n project,\n 'build',\n `${consumedProjectName}:build`,\n );\n addDependencyToTargetIfNotPresent(\n project,\n 'assemble',\n `${consumedProjectName}:assemble`,\n );\n};\n\n/**\n * Register a component's `<name>-dev` target under the project-level `dev`\n * target, so `nx run <project>:dev` starts every component in the project.\n *\n * Component generators author their own continuous `<name>-dev` runner and call\n * this to aggregate it. Every component added to the project accumulates its\n * `<name>-dev` under `dev`. Nx keys tasks by `project:target`, so any\n * dependencies shared between components run exactly once.\n */\nexport const addComponentDevTarget = (\n targets: Record<string, ProjectConfiguration['targets'][string]>,\n componentDevTargetName: string,\n) => {\n targets['dev'] ??= { continuous: true, dependsOn: [] };\n const projectDev = targets['dev'];\n projectDev.continuous = true;\n projectDev.dependsOn ??= [];\n // Append in component-creation order; dedupe keeps re-runs idempotent.\n if (!projectDev.dependsOn.includes(componentDevTargetName)) {\n projectDev.dependsOn.push(componentDevTargetName);\n }\n targets['dev'] = normalizeTargetKeyOrder(projectDev);\n};\n"],"names":["getProjects","readJson","readProjectConfiguration","updateProjectConfiguration","path","PackageJson","type","NX_PLUGIN_MCP_PACKAGE_NAME","toSnakeCase","getNpmScope","getNpmScopePrefix","buildGeneratorInfoList","GENERATORS","resolve","dirname","listGenerators","includeHidden","filter","g","hidden","getGeneratorInfo","generatorFileName","dir","name","parse","resolvedFactoryPath","join","find","generatorInfo","getPackageVersion","version","isNxPluginForAwsWorkspace","tree","rootPackageJson","exists","undefined","nxPluginSelfDependency","nxPluginMcpDependency","readProjectConfigurationUnqualified","projectName","e","project","values","p","projectExists","addGeneratorMetadata","info","additionalMetadata","config","metadata","generator","id","JSON","stringify","targets","rest","addComponentGeneratorMetadata","componentPath","componentName","existingComponents","components","alreadyAdded","c","length","componentMetadata","updateComponentGeneratorMetadata","patch","updated","map","component","mergeTargetDefault","value","apply","Array","isArray","catchAllIndex","findIndex","entry","index","targetDependencyEquals","a","b","target","params","aProjects","projects","bProjects","every","i","NX_TARGET_KEY_ORDER","normalizeTargetKeyOrder","Object","fromEntries","entries","sort","ai","indexOf","bi","mergeTarget","existing","desired","merged","dependsOn","dependency","some","d","push","addDependencyToTargetIfNotPresent","addArtifactDependencyToTargets","addArtifactProjectToTargets","consumedProjectName","addComponentDevTarget","componentDevTargetName","continuous","projectDev","includes"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
import type { DependencyDeclaration, MustDeclare } from './declared-dependencies.js';
|
|
7
|
+
/** Dependencies a caller must declare to use the shared Greengrass scripts. */
|
|
8
|
+
export declare const SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES: readonly [{
|
|
9
|
+
readonly name: "js-yaml";
|
|
10
|
+
}, {
|
|
11
|
+
readonly name: "@types/js-yaml";
|
|
12
|
+
}, {
|
|
13
|
+
readonly name: "tsx";
|
|
14
|
+
}];
|
|
15
|
+
/**
|
|
16
|
+
* Ensures the shared scripts package exists and adds Greengrass local-build
|
|
17
|
+
* and local-deploy scripts to packages/common/scripts/src/greengrass/. Used
|
|
18
|
+
* by greengrass#component for Python and TypeScript hosts so a
|
|
19
|
+
* single set of TypeScript scripts serves both.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sharedGreengrassScriptsGenerator<const D extends DependencyDeclaration>(tree: Tree, declaration: D & MustDeclare<typeof SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES, D>): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { generateFiles, joinPathFragments, OverwriteStrategy } from "@nx/devkit";
|
|
5
|
+
import { addDependenciesToPackageJson } from "./dependencies.js";
|
|
6
|
+
import { esmVars } from "./module-format.js";
|
|
7
|
+
import { PACKAGES_DIR, SHARED_SCRIPTS_DIR } from "./shared-constructs-constants.js";
|
|
8
|
+
import { ensureSharedScriptsProject } from "./shared-scripts.js";
|
|
9
|
+
import { withVersions } from "./versions.js";
|
|
10
|
+
/** Dependencies a caller must declare to use the shared Greengrass scripts. */ export const SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES = [
|
|
11
|
+
{
|
|
12
|
+
name: 'js-yaml'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: '@types/js-yaml'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'tsx'
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Ensures the shared scripts package exists and adds Greengrass local-build
|
|
23
|
+
* and local-deploy scripts to packages/common/scripts/src/greengrass/. Used
|
|
24
|
+
* by greengrass#component for Python and TypeScript hosts so a
|
|
25
|
+
* single set of TypeScript scripts serves both.
|
|
26
|
+
*/ export async function sharedGreengrassScriptsGenerator(tree, declaration) {
|
|
27
|
+
const scriptsDir = joinPathFragments(PACKAGES_DIR, SHARED_SCRIPTS_DIR);
|
|
28
|
+
const greengrassScriptsDir = joinPathFragments(scriptsDir, 'src', 'greengrass');
|
|
29
|
+
await ensureSharedScriptsProject(tree);
|
|
30
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', SHARED_SCRIPTS_DIR, 'src', 'greengrass'), greengrassScriptsDir, esmVars(tree), {
|
|
31
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
32
|
+
});
|
|
33
|
+
addDependenciesToPackageJson(tree, withVersions(declaration, [
|
|
34
|
+
'js-yaml'
|
|
35
|
+
]), withVersions(declaration, [
|
|
36
|
+
'@types/js-yaml'
|
|
37
|
+
]), joinPathFragments(PACKAGES_DIR, SHARED_SCRIPTS_DIR, 'package.json'));
|
|
38
|
+
addDependenciesToPackageJson(tree, {}, withVersions(declaration, [
|
|
39
|
+
'tsx'
|
|
40
|
+
]));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=shared-greengrass-scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/shared-greengrass-scripts.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 type {\n DependencyDeclaration,\n MustDeclare,\n} from './declared-dependencies.js';\nimport { addDependenciesToPackageJson } from './dependencies.js';\nimport { esmVars } from './module-format.js';\nimport {\n PACKAGES_DIR,\n SHARED_SCRIPTS_DIR,\n} from './shared-constructs-constants.js';\nimport { ensureSharedScriptsProject } from './shared-scripts.js';\nimport { type ITsDepVersion, withVersions } from './versions.js';\n\n/** Dependencies a caller must declare to use the shared Greengrass scripts. */\nexport const SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES = [\n { name: 'js-yaml' },\n { name: '@types/js-yaml' },\n { name: 'tsx' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Ensures the shared scripts package exists and adds Greengrass local-build\n * and local-deploy scripts to packages/common/scripts/src/greengrass/. Used\n * by greengrass#component for Python and TypeScript hosts so a\n * single set of TypeScript scripts serves both.\n */\nexport async function sharedGreengrassScriptsGenerator<\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n declaration: D &\n MustDeclare<typeof SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES, D>,\n): Promise<void> {\n const scriptsDir = joinPathFragments(PACKAGES_DIR, SHARED_SCRIPTS_DIR);\n const greengrassScriptsDir = joinPathFragments(\n scriptsDir,\n 'src',\n 'greengrass',\n );\n\n await ensureSharedScriptsProject(tree);\n\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n SHARED_SCRIPTS_DIR,\n 'src',\n 'greengrass',\n ),\n greengrassScriptsDir,\n esmVars(tree),\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n\n addDependenciesToPackageJson(\n tree,\n withVersions(\n declaration as DependencyDeclaration<\n typeof SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES\n >,\n ['js-yaml'],\n ),\n withVersions(\n declaration as DependencyDeclaration<\n typeof SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES\n >,\n ['@types/js-yaml'],\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_SCRIPTS_DIR, 'package.json'),\n );\n addDependenciesToPackageJson(\n tree,\n {},\n withVersions(\n declaration as DependencyDeclaration<\n typeof SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES\n >,\n ['tsx'],\n ),\n );\n}\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","addDependenciesToPackageJson","esmVars","PACKAGES_DIR","SHARED_SCRIPTS_DIR","ensureSharedScriptsProject","withVersions","SHARED_GREENGRASS_SCRIPTS_DEPENDENCIES","name","sharedGreengrassScriptsGenerator","tree","declaration","scriptsDir","greengrassScriptsDir","dirname","overwriteStrategy","KeepExisting"],"mappings":"AAAA;;;CAGC,GACD,SACEA,aAAa,EACbC,iBAAiB,EACjBC,iBAAiB,QAEZ,aAAa;AAKpB,SAASC,4BAA4B,QAAQ,oBAAoB;AACjE,SAASC,OAAO,QAAQ,qBAAqB;AAC7C,SACEC,YAAY,EACZC,kBAAkB,QACb,mCAAmC;AAC1C,SAASC,0BAA0B,QAAQ,sBAAsB;AACjE,SAA6BC,YAAY,QAAQ,gBAAgB;AAEjE,6EAA6E,GAC7E,OAAO,MAAMC,yCAAyC;IACpD;QAAEC,MAAM;IAAU;IAClB;QAAEA,MAAM;IAAiB;IACzB;QAAEA,MAAM;IAAM;CACf,CAAuD;AAExD;;;;;CAKC,GACD,OAAO,eAAeC,iCAGpBC,IAAU,EACVC,WAC+D;IAE/D,MAAMC,aAAab,kBAAkBI,cAAcC;IACnD,MAAMS,uBAAuBd,kBAC3Ba,YACA,OACA;IAGF,MAAMP,2BAA2BK;IAEjCZ,cACEY,MACAX,kBACE,YAAYe,OAAO,EACnB,SACAV,oBACA,OACA,eAEFS,sBACAX,QAAQQ,OACR;QAAEK,mBAAmBf,kBAAkBgB,YAAY;IAAC;IAGtDf,6BACES,MACAJ,aACEK,aAGA;QAAC;KAAU,GAEbL,aACEK,aAGA;QAAC;KAAiB,GAEpBZ,kBAAkBI,cAAcC,oBAAoB;IAEtDH,6BACES,MACA,CAAC,GACDJ,aACEK,aAGA;QAAC;KAAM;AAGb"}
|
|
@@ -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[];
|