@aws/nx-plugin 1.0.0-rc.91 → 1.0.0-rc.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/migrations.json +6 -1
  2. package/package.json +1 -1
  3. package/src/internal/test-matrix/generator.js +28 -0
  4. package/src/internal/test-matrix/generator.js.map +1 -1
  5. package/src/migrations/latest/react-website-unique-dev-ports/metadata.json +3 -0
  6. package/src/migrations/latest/react-website-unique-dev-ports/migration.d.ts +6 -0
  7. package/src/migrations/latest/react-website-unique-dev-ports/migration.js +79 -0
  8. package/src/migrations/latest/react-website-unique-dev-ports/migration.js.map +1 -0
  9. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +90 -115
  10. package/src/py/agent/files/package/main.py.template +13 -0
  11. package/src/py/agent/generator.d.ts +6 -1
  12. package/src/py/agent/generator.js +82 -45
  13. package/src/py/agent/generator.js.map +1 -1
  14. package/src/py/agent/schema.d.js.map +1 -1
  15. package/src/py/agent/schema.d.ts +2 -1
  16. package/src/py/agent/schema.json +2 -2
  17. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +64 -113
  18. package/src/py/mcp-server/files/package/main.py.template +13 -0
  19. package/src/py/mcp-server/generator.d.ts +6 -1
  20. package/src/py/mcp-server/generator.js +79 -43
  21. package/src/py/mcp-server/generator.js.map +1 -1
  22. package/src/py/mcp-server/schema.d.js.map +1 -1
  23. package/src/py/mcp-server/schema.d.ts +2 -1
  24. package/src/py/mcp-server/schema.json +2 -2
  25. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +12 -59
  26. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +77 -114
  27. package/src/ts/agent/generator.d.ts +26 -1
  28. package/src/ts/agent/generator.js +71 -45
  29. package/src/ts/agent/generator.js.map +1 -1
  30. package/src/ts/agent/schema.d.js.map +1 -1
  31. package/src/ts/agent/schema.d.ts +2 -1
  32. package/src/ts/agent/schema.json +2 -2
  33. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +64 -113
  34. package/src/ts/mcp-server/generator.d.ts +26 -1
  35. package/src/ts/mcp-server/generator.js +66 -38
  36. package/src/ts/mcp-server/generator.js.map +1 -1
  37. package/src/ts/mcp-server/schema.d.js.map +1 -1
  38. package/src/ts/mcp-server/schema.d.ts +2 -1
  39. package/src/ts/mcp-server/schema.json +2 -2
  40. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +16 -110
  41. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +2 -0
  42. package/src/ts/react-website/app/generator.js +16 -0
  43. package/src/ts/react-website/app/generator.js.map +1 -1
  44. package/src/ts/react-website/cognito-auth/generator.js +14 -2
  45. package/src/ts/react-website/cognito-auth/generator.js.map +1 -1
  46. package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +25 -6
  47. package/src/utils/agent-core-constructs/agent-core-constructs.js +36 -7
  48. package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
  49. package/src/utils/agent-core-constructs/files/cdk/app/agent-core/__nameKebabCase__/__nameKebabCase__.ts.template +20 -4
  50. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +34 -1
  51. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +51 -112
  52. package/src/utils/agent-core-constructs/files/terraform/core/agent-core-code/runtime.tf.template +204 -0
  53. package/src/utils/agent-core-constructs/files/terraform/core/agent-core-container/runtime.tf.template +235 -0
  54. package/src/utils/agent-core-packaging.d.ts +119 -0
  55. package/src/utils/agent-core-packaging.js +131 -0
  56. package/src/utils/agent-core-packaging.js.map +1 -0
  57. package/src/utils/agent-core-runtime-resolution.d.ts +35 -0
  58. package/src/utils/agent-core-runtime-resolution.js +68 -0
  59. package/src/utils/agent-core-runtime-resolution.js.map +1 -0
  60. package/src/utils/files/terraform/src/core/asset-ecr/asset-ecr.tf.template +137 -0
  61. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +1 -1
  62. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +1 -4
  63. package/src/utils/identity-constructs/identity-constructs.d.ts +22 -2
  64. package/src/utils/identity-constructs/identity-constructs.js +75 -5
  65. package/src/utils/identity-constructs/identity-constructs.js.map +1 -1
  66. package/src/utils/port.d.ts +5 -0
  67. package/src/utils/port.js +1 -1
  68. package/src/utils/port.js.map +1 -1
  69. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +12 -59
  70. package/src/utils/version-upgrade-migration/sync-embedded-versions.js +30 -3
  71. package/src/utils/version-upgrade-migration/sync-embedded-versions.js.map +1 -1
  72. package/src/utils/versions.d.ts +20 -0
  73. package/src/utils/versions.js +17 -0
  74. package/src/utils/versions.js.map +1 -1
  75. /package/src/ts/mcp-server/files/{http.ts.template → app/http.ts.template} +0 -0
  76. /package/src/ts/mcp-server/files/{index.ts.template → app/index.ts.template} +0 -0
  77. /package/src/ts/mcp-server/files/{resources → app/resources}/sample-guidance.ts.template +0 -0
  78. /package/src/ts/mcp-server/files/{server.ts.template → app/server.ts.template} +0 -0
  79. /package/src/ts/mcp-server/files/{stdio.ts.template → app/stdio.ts.template} +0 -0
  80. /package/src/ts/mcp-server/files/{tools → app/tools}/divide.ts.template +0 -0
  81. /package/src/ts/mcp-server/files/{Dockerfile.template → deploy/Dockerfile.template} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/agent-core-packaging.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport { addArtifactDependencyToTargets } from './nx.js';\nimport {\n agentCoreRuntime,\n type ITsDepVersion,\n TS_VERSIONS,\n} from './versions.js';\n\n/**\n * How an AgentCore Runtime's artifact is packaged and hosted.\n *\n * `agentcore` uploads a `.zip` of the built code to S3 and runs it on an\n * AgentCore managed language runtime, which skips the container build, ECR\n * repository and image push entirely — the fastest build and deploy cycle.\n * `agentcore-ecr` builds an arm64 container image and hosts it from ECR, for\n * workloads needing OS-level control (e.g. native system libraries) or an\n * established container pipeline.\n */\nexport const AGENT_CORE_INFRA = ['agentcore', 'agentcore-ecr', 'none'] as const;\n\nexport type AgentCoreInfra = (typeof AGENT_CORE_INFRA)[number];\n\n/** Whether the given infra option hosts on AgentCore Runtime at all. */\nexport const isAgentCoreHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore' || infra === 'agentcore-ecr';\n\n/** Whether the given infra option builds and hosts a container image. */\nexport const isContainerHosted = (infra: AgentCoreInfra): boolean =>\n infra === 'agentcore-ecr';\n\n/** The AgentCore managed runtime a TypeScript component targets. */\nexport const agentCoreNodeRuntime = (): string => agentCoreRuntime('node');\n\n/** The AgentCore managed runtime a Python component targets. */\nexport const agentCorePythonRuntime = (): string => agentCoreRuntime('python');\n\n/**\n * Dependencies a caller must declare to add a code (`.zip`) packaging target.\n *\n * The TypeScript package target vendors the AWS Distro for OpenTelemetry into\n * the package directory, so its version is declared here (spread through\n * `ownedElsewhere` by callers, since the install targets the built package\n * rather than any workspace manifest).\n */\nexport const CODE_PACKAGE_DEPENDENCIES = [\n ...FS_DEPENDENCIES,\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface AddTypeScriptCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the target producing the bundled `index.js`, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the bundled `index.js`, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n}\n\n/**\n * Add a target assembling the deployable code package for a TypeScript\n * AgentCore Runtime.\n *\n * The package is the rolldown bundle plus a vendored AWS Distro for\n * OpenTelemetry install. AgentCore rejects a code package with no OpenTelemetry\n * dependencies present (`CREATE_FAILED`: \"OpenTelemetry instrumentation\n * executable not found\"), so ADOT must ship inside the package rather than\n * being installed at runtime — the `opentelemetry-instrument` entry point\n * prefix resolves it from there.\n *\n * The install is scoped to the package directory with `--prefix` and\n * `--no-save`, so it never touches the project's own manifest. `--omit=dev`\n * keeps the package to what the runtime loads. ADOT is pure JavaScript with no\n * native binaries, so the package is architecture independent even though\n * AgentCore only runs arm64.\n */\nexport const addTypeScriptCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddTypeScriptCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof CODE_PACKAGE_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n const adotVersion =\n TS_VERSIONS['@aws/aws-distro-opentelemetry-node-autoinstrumentation'];\n\n project.targets ??= {};\n project.targets[targetName] = {\n cache: true,\n inputs: ['default'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cp(\n joinPathFragments(bundleOutputDir, 'index.js'),\n joinPathFragments(packageOutputDir, 'index.js'),\n ),\n `npm install --prefix ${packageOutputDir} --no-save --no-audit --no-fund --omit=dev @aws/aws-distro-opentelemetry-node-autoinstrumentation@${adotVersion}`,\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName],\n };\n\n addArtifactDependencyToTargets(project, targetName);\n};\n\nexport interface AddPythonCodePackageTargetOptions {\n /** Project configuration to add the target to (mutated in place). */\n readonly project: ProjectConfiguration;\n /** Name of the packaging target to add, e.g. `agent-package`. */\n readonly targetName: string;\n /** Name of the arm64 bundle target, depended on here. */\n readonly bundleTargetName: string;\n /** Directory holding the arm64 bundle, relative to the workspace root. */\n readonly bundleOutputDir: string;\n /** Directory to assemble the deployable package in, relative to the workspace root. */\n readonly packageOutputDir: string;\n /**\n * The project's source root, which is itself the module directory — copied\n * into the package under {@link moduleName} so the agent's imports resolve.\n */\n readonly sourceRoot: string;\n /** Python module name (the source root's last segment). */\n readonly moduleName: string;\n /**\n * Path to the entry point script, relative to the workspace root. Copied to\n * the package root, where AgentCore's `entryPoint` runs it.\n */\n readonly entryPointPath: string;\n /** File name the entry point takes at the package root. */\n readonly entryPointFileName: string;\n}\n\n/**\n * Add a target assembling the deployable code package for a Python AgentCore\n * Runtime.\n *\n * The package is the arm64 dependency bundle (which already carries\n * `bin/opentelemetry-instrument` from `aws-opentelemetry-distro`, satisfying\n * AgentCore's requirement that OpenTelemetry be present), the project's own\n * module tree, and a root entry point script.\n *\n * The entry point sits at the package root rather than inside the module:\n * AgentCore runs it as a file path, and Python gives a script run that way no\n * parent package, so the agent's own `from .init import app` would raise\n * `ImportError: attempted relative import with no known parent package`. The\n * root script imports the module absolutely instead, which resolves those\n * relative imports normally.\n */\nexport const addPythonCodePackageTarget = <\n const D extends DependencyDeclaration,\n>(\n tree: Tree,\n options: AddPythonCodePackageTargetOptions,\n declaration: D & MustDeclare<typeof FS_DEPENDENCIES, D>,\n): void => {\n const {\n project,\n targetName,\n bundleTargetName,\n bundleOutputDir,\n packageOutputDir,\n sourceRoot,\n moduleName,\n entryPointPath,\n entryPointFileName,\n } = options;\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof FS_DEPENDENCIES>(declaration),\n );\n\n project.targets ??= {};\n project.targets[targetName] = {\n cache: true,\n inputs: ['production', '^production'],\n outputs: [`{workspaceRoot}/${packageOutputDir}`],\n executor: 'nx:run-commands',\n options: {\n commands: [\n fs.rm(packageOutputDir),\n fs.mkdir(packageOutputDir),\n fs.cp(bundleOutputDir, packageOutputDir),\n fs.cp(sourceRoot, joinPathFragments(packageOutputDir, moduleName)),\n fs.cp(\n entryPointPath,\n joinPathFragments(packageOutputDir, entryPointFileName),\n ),\n ],\n parallel: false,\n },\n dependsOn: [bundleTargetName],\n };\n\n addArtifactDependencyToTargets(project, targetName);\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addArtifactDependencyToTargets","agentCoreRuntime","TS_VERSIONS","AGENT_CORE_INFRA","isAgentCoreHosted","infra","isContainerHosted","agentCoreNodeRuntime","agentCorePythonRuntime","CODE_PACKAGE_DEPENDENCIES","name","addTypeScriptCodePackageTarget","tree","options","declaration","project","targetName","bundleTargetName","bundleOutputDir","packageOutputDir","fs","adotVersion","targets","cache","inputs","outputs","executor","commands","rm","mkdir","cp","parallel","dependsOn","addPythonCodePackageTarget","sourceRoot","moduleName","entryPointPath","entryPointFileName"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SAEEC,eAAe,QAEV,6BAA6B;AACpC,SAASC,eAAe,EAAEC,UAAU,QAAQ,UAAU;AACtD,SAASC,8BAA8B,QAAQ,UAAU;AACzD,SACEC,gBAAgB,EAEhBC,WAAW,QACN,gBAAgB;AAEvB;;;;;;;;;CASC,GACD,OAAO,MAAMC,mBAAmB;IAAC;IAAa;IAAiB;CAAO,CAAU;AAIhF,sEAAsE,GACtE,OAAO,MAAMC,oBAAoB,CAACC,QAChCA,UAAU,eAAeA,UAAU,gBAAgB;AAErD,uEAAuE,GACvE,OAAO,MAAMC,oBAAoB,CAACD,QAChCA,UAAU,gBAAgB;AAE5B,kEAAkE,GAClE,OAAO,MAAME,uBAAuB,IAAcN,iBAAiB,QAAQ;AAE3E,8DAA8D,GAC9D,OAAO,MAAMO,yBAAyB,IAAcP,iBAAiB,UAAU;AAE/E;;;;;;;CAOC,GACD,OAAO,MAAMQ,4BAA4B;OACpCX;IACH;QAAEY,MAAM;IAAyD;CAClE,CAAuD;AAexD;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,iCAAiC,CAG5CC,MACAC,SACAC;IAEA,MAAM,EACJC,OAAO,EACPC,UAAU,EACVC,gBAAgB,EAChBC,eAAe,EACfC,gBAAgB,EACjB,GAAGN;IAEJ,MAAMO,KAAK,IAAIrB,WACba,MACAf,gBAAwCiB;IAE1C,MAAMO,cACJnB,WAAW,CAAC,yDAAyD;IAEvEa,QAAQO,OAAO,KAAK,CAAC;IACrBP,QAAQO,OAAO,CAACN,WAAW,GAAG;QAC5BO,OAAO;QACPC,QAAQ;YAAC;SAAU;QACnBC,SAAS;YAAC,CAAC,gBAAgB,EAAEN,kBAAkB;SAAC;QAChDO,UAAU;QACVb,SAAS;YACPc,UAAU;gBACRP,GAAGQ,EAAE,CAACT;gBACNC,GAAGS,KAAK,CAACV;gBACTC,GAAGU,EAAE,CACHlC,kBAAkBsB,iBAAiB,aACnCtB,kBAAkBuB,kBAAkB;gBAEtC,CAAC,qBAAqB,EAAEA,iBAAiB,kGAAkG,EAAEE,aAAa;aAC3J;YACDU,UAAU;QACZ;QACAC,WAAW;YAACf;SAAiB;IAC/B;IAEAjB,+BAA+Be,SAASC;AAC1C,EAAE;AA6BF;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMiB,6BAA6B,CAGxCrB,MACAC,SACAC;IAEA,MAAM,EACJC,OAAO,EACPC,UAAU,EACVC,gBAAgB,EAChBC,eAAe,EACfC,gBAAgB,EAChBe,UAAU,EACVC,UAAU,EACVC,cAAc,EACdC,kBAAkB,EACnB,GAAGxB;IAEJ,MAAMO,KAAK,IAAIrB,WACba,MACAf,gBAAwCiB;IAG1CC,QAAQO,OAAO,KAAK,CAAC;IACrBP,QAAQO,OAAO,CAACN,WAAW,GAAG;QAC5BO,OAAO;QACPC,QAAQ;YAAC;YAAc;SAAc;QACrCC,SAAS;YAAC,CAAC,gBAAgB,EAAEN,kBAAkB;SAAC;QAChDO,UAAU;QACVb,SAAS;YACPc,UAAU;gBACRP,GAAGQ,EAAE,CAACT;gBACNC,GAAGS,KAAK,CAACV;gBACTC,GAAGU,EAAE,CAACZ,iBAAiBC;gBACvBC,GAAGU,EAAE,CAACI,YAAYtC,kBAAkBuB,kBAAkBgB;gBACtDf,GAAGU,EAAE,CACHM,gBACAxC,kBAAkBuB,kBAAkBkB;aAEvC;YACDN,UAAU;QACZ;QACAC,WAAW;YAACf;SAAiB;IAC/B;IAEAjB,+BAA+Be,SAASC;AAC1C,EAAE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { type RuntimeResolution } from './lambda-runtime-resolution.js';
6
+ import { type IAgentCoreRuntime } from './versions.js';
7
+ /**
8
+ * Resolution of the managed AgentCore Runtime runtimes the version update pins.
9
+ *
10
+ * Lives here rather than beside the script so it is covered by the plugin's test
11
+ * suite; the script supplies the runtime list read from `aws-cdk-lib`.
12
+ */
13
+ /**
14
+ * The version an AgentCore runtime identifier names, or undefined for one that
15
+ * isn't a versioned `NODE`/`PYTHON` runtime.
16
+ *
17
+ * `NODE_22` -> `22`, `PYTHON_3_14` -> `3.14`.
18
+ */
19
+ export declare const agentCoreRuntimeIdentifierVersion: (identifier: string) => {
20
+ language: IAgentCoreRuntime;
21
+ version: string;
22
+ } | undefined;
23
+ /**
24
+ * The latest managed runtime for each language in
25
+ * {@link AGENT_CORE_RUNTIME_VERSIONS}, from the runtime identifiers
26
+ * `aws-cdk-lib` publishes as `AgentCoreRuntime` members.
27
+ *
28
+ * Mirrors the Lambda resolution: the member list is curated, so a runtime still
29
+ * in preview does not appear. Never moves a runtime backwards, and a language
30
+ * the list says nothing about keeps its current pin and is reported through
31
+ * `unresolved`, so a failure costs this bump alone.
32
+ */
33
+ export declare const resolveAgentCoreRuntimes: (identifiers: readonly string[]) => RuntimeResolution;
34
+ /** The warning a language's unresolved AgentCore runtime reports. */
35
+ export declare const unresolvedAgentCoreRuntimeWarning: (entry: RuntimeResolution["unresolved"][number]) => string;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { compareRuntimeVersions } from "./lambda-runtime-resolution.js";
5
+ import { AGENT_CORE_RUNTIME_VERSIONS } from "./versions.js";
6
+ /**
7
+ * Resolution of the managed AgentCore Runtime runtimes the version update pins.
8
+ *
9
+ * Lives here rather than beside the script so it is covered by the plugin's test
10
+ * suite; the script supplies the runtime list read from `aws-cdk-lib`.
11
+ */ /**
12
+ * The version an AgentCore runtime identifier names, or undefined for one that
13
+ * isn't a versioned `NODE`/`PYTHON` runtime.
14
+ *
15
+ * `NODE_22` -> `22`, `PYTHON_3_14` -> `3.14`.
16
+ */ export const agentCoreRuntimeIdentifierVersion = (identifier)=>{
17
+ const node = /^NODE_(\d+)$/.exec(identifier);
18
+ if (node) {
19
+ return {
20
+ language: 'node',
21
+ version: node[1]
22
+ };
23
+ }
24
+ const python = /^PYTHON_(\d+)_(\d+)$/.exec(identifier);
25
+ return python ? {
26
+ language: 'python',
27
+ version: `${python[1]}.${python[2]}`
28
+ } : undefined;
29
+ };
30
+ /**
31
+ * The latest managed runtime for each language in
32
+ * {@link AGENT_CORE_RUNTIME_VERSIONS}, from the runtime identifiers
33
+ * `aws-cdk-lib` publishes as `AgentCoreRuntime` members.
34
+ *
35
+ * Mirrors the Lambda resolution: the member list is curated, so a runtime still
36
+ * in preview does not appear. Never moves a runtime backwards, and a language
37
+ * the list says nothing about keeps its current pin and is reported through
38
+ * `unresolved`, so a failure costs this bump alone.
39
+ */ export const resolveAgentCoreRuntimes = (identifiers)=>{
40
+ const unresolved = [];
41
+ const versions = Object.fromEntries(Object.keys(AGENT_CORE_RUNTIME_VERSIONS).map((language)=>{
42
+ const current = AGENT_CORE_RUNTIME_VERSIONS[language];
43
+ const available = identifiers.map(agentCoreRuntimeIdentifierVersion).filter((parsed)=>parsed?.language === language).map((parsed)=>parsed.version);
44
+ if (available.length === 0) {
45
+ unresolved.push({
46
+ language,
47
+ kept: current,
48
+ reason: 'no-runtimes-listed'
49
+ });
50
+ return [
51
+ language,
52
+ current
53
+ ];
54
+ }
55
+ const latest = available.reduce((best, version)=>compareRuntimeVersions(version, best) > 0 ? version : best);
56
+ return [
57
+ language,
58
+ compareRuntimeVersions(latest, current) > 0 ? latest : current
59
+ ];
60
+ }));
61
+ return {
62
+ versions,
63
+ unresolved
64
+ };
65
+ };
66
+ /** The warning a language's unresolved AgentCore runtime reports. */ export const unresolvedAgentCoreRuntimeWarning = (entry)=>`Found no ${entry.language} runtime in the aws-cdk-lib AgentCoreRuntime list, so it keeps its pinned runtime (${entry.kept})`;
67
+
68
+ //# sourceMappingURL=agent-core-runtime-resolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/agent-core-runtime-resolution.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n compareRuntimeVersions,\n type RuntimeResolution,\n} from './lambda-runtime-resolution.js';\nimport {\n AGENT_CORE_RUNTIME_VERSIONS,\n type IAgentCoreRuntime,\n} from './versions.js';\n\n/**\n * Resolution of the managed AgentCore Runtime runtimes the version update pins.\n *\n * Lives here rather than beside the script so it is covered by the plugin's test\n * suite; the script supplies the runtime list read from `aws-cdk-lib`.\n */\n\n/**\n * The version an AgentCore runtime identifier names, or undefined for one that\n * isn't a versioned `NODE`/`PYTHON` runtime.\n *\n * `NODE_22` -> `22`, `PYTHON_3_14` -> `3.14`.\n */\nexport const agentCoreRuntimeIdentifierVersion = (\n identifier: string,\n): { language: IAgentCoreRuntime; version: string } | undefined => {\n const node = /^NODE_(\\d+)$/.exec(identifier);\n if (node) {\n return { language: 'node', version: node[1] };\n }\n const python = /^PYTHON_(\\d+)_(\\d+)$/.exec(identifier);\n return python\n ? { language: 'python', version: `${python[1]}.${python[2]}` }\n : undefined;\n};\n\n/**\n * The latest managed runtime for each language in\n * {@link AGENT_CORE_RUNTIME_VERSIONS}, from the runtime identifiers\n * `aws-cdk-lib` publishes as `AgentCoreRuntime` members.\n *\n * Mirrors the Lambda resolution: the member list is curated, so a runtime still\n * in preview does not appear. Never moves a runtime backwards, and a language\n * the list says nothing about keeps its current pin and is reported through\n * `unresolved`, so a failure costs this bump alone.\n */\nexport const resolveAgentCoreRuntimes = (\n identifiers: readonly string[],\n): RuntimeResolution => {\n const unresolved: RuntimeResolution['unresolved'][number][] = [];\n\n const versions = Object.fromEntries(\n (Object.keys(AGENT_CORE_RUNTIME_VERSIONS) as IAgentCoreRuntime[]).map(\n (language) => {\n const current = AGENT_CORE_RUNTIME_VERSIONS[language];\n const available = identifiers\n .map(agentCoreRuntimeIdentifierVersion)\n .filter((parsed) => parsed?.language === language)\n .map((parsed) => parsed!.version);\n\n if (available.length === 0) {\n unresolved.push({\n language,\n kept: current,\n reason: 'no-runtimes-listed',\n });\n return [language, current];\n }\n\n const latest = available.reduce((best, version) =>\n compareRuntimeVersions(version, best) > 0 ? version : best,\n );\n\n return [\n language,\n compareRuntimeVersions(latest, current) > 0 ? latest : current,\n ];\n },\n ),\n );\n\n return { versions, unresolved };\n};\n\n/** The warning a language's unresolved AgentCore runtime reports. */\nexport const unresolvedAgentCoreRuntimeWarning = (\n entry: RuntimeResolution['unresolved'][number],\n): string =>\n `Found no ${entry.language} runtime in the aws-cdk-lib AgentCoreRuntime list, so it keeps its pinned runtime (${entry.kept})`;\n"],"names":["compareRuntimeVersions","AGENT_CORE_RUNTIME_VERSIONS","agentCoreRuntimeIdentifierVersion","identifier","node","exec","language","version","python","undefined","resolveAgentCoreRuntimes","identifiers","unresolved","versions","Object","fromEntries","keys","map","current","available","filter","parsed","length","push","kept","reason","latest","reduce","best","unresolvedAgentCoreRuntimeWarning","entry"],"mappings":"AAAA;;;CAGC,GACD,SACEA,sBAAsB,QAEjB,iCAAiC;AACxC,SACEC,2BAA2B,QAEtB,gBAAgB;AAEvB;;;;;CAKC,GAED;;;;;CAKC,GACD,OAAO,MAAMC,oCAAoC,CAC/CC;IAEA,MAAMC,OAAO,eAAeC,IAAI,CAACF;IACjC,IAAIC,MAAM;QACR,OAAO;YAAEE,UAAU;YAAQC,SAASH,IAAI,CAAC,EAAE;QAAC;IAC9C;IACA,MAAMI,SAAS,uBAAuBH,IAAI,CAACF;IAC3C,OAAOK,SACH;QAAEF,UAAU;QAAUC,SAAS,GAAGC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAEA,MAAM,CAAC,EAAE,EAAE;IAAC,IAC3DC;AACN,EAAE;AAEF;;;;;;;;;CASC,GACD,OAAO,MAAMC,2BAA2B,CACtCC;IAEA,MAAMC,aAAwD,EAAE;IAEhE,MAAMC,WAAWC,OAAOC,WAAW,CACjC,AAACD,OAAOE,IAAI,CAACf,6BAAqDgB,GAAG,CACnE,CAACX;QACC,MAAMY,UAAUjB,2BAA2B,CAACK,SAAS;QACrD,MAAMa,YAAYR,YACfM,GAAG,CAACf,mCACJkB,MAAM,CAAC,CAACC,SAAWA,QAAQf,aAAaA,UACxCW,GAAG,CAAC,CAACI,SAAWA,OAAQd,OAAO;QAElC,IAAIY,UAAUG,MAAM,KAAK,GAAG;YAC1BV,WAAWW,IAAI,CAAC;gBACdjB;gBACAkB,MAAMN;gBACNO,QAAQ;YACV;YACA,OAAO;gBAACnB;gBAAUY;aAAQ;QAC5B;QAEA,MAAMQ,SAASP,UAAUQ,MAAM,CAAC,CAACC,MAAMrB,UACrCP,uBAAuBO,SAASqB,QAAQ,IAAIrB,UAAUqB;QAGxD,OAAO;YACLtB;YACAN,uBAAuB0B,QAAQR,WAAW,IAAIQ,SAASR;SACxD;IACH;IAIJ,OAAO;QAAEL;QAAUD;IAAW;AAChC,EAAE;AAEF,mEAAmE,GACnE,OAAO,MAAMiB,oCAAoC,CAC/CC,QAEA,CAAC,SAAS,EAAEA,MAAMxB,QAAQ,CAAC,mFAAmF,EAAEwB,MAAMN,IAAI,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,137 @@
1
+ terraform {
2
+ required_version = ">= 1.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "<%- awsProviderVersion %>"
8
+ }
9
+ random = {
10
+ source = "hashicorp/random"
11
+ version = "<%- randomProviderVersion %>"
12
+ }
13
+ }
14
+ }
15
+
16
+ variable "repository_name_prefix" {
17
+ description = "Optional prefix to apply to the generated repository name. Useful when you want a stable, human-recognisable identifier in addition to the random suffix."
18
+ type = string
19
+ default = "assets"
20
+ }
21
+
22
+ variable "enable_key_rotation" {
23
+ description = "Whether to enable automatic key rotation on the KMS key used to encrypt the images in the repository."
24
+ type = bool
25
+ default = true
26
+ }
27
+
28
+ variable "tags" {
29
+ description = "Tags to apply to all resources"
30
+ type = map(string)
31
+ default = {}
32
+ }
33
+
34
+ data "aws_caller_identity" "current" {}
35
+
36
+ resource "random_string" "suffix" {
37
+ length = 8
38
+ special = false
39
+ upper = false
40
+ }
41
+
42
+ # KMS key encrypting the images in the shared asset repository.
43
+ resource "aws_kms_key" "assets" {
44
+ description = "KMS key for ${var.repository_name_prefix} asset repository images"
45
+ enable_key_rotation = var.enable_key_rotation
46
+ deletion_window_in_days = 30
47
+
48
+ policy = jsonencode({
49
+ Version = "2012-10-17"
50
+ Statement = [
51
+ {
52
+ Sid = "EnableIAMUserPermissions"
53
+ Effect = "Allow"
54
+ Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
55
+ Action = "kms:*"
56
+ Resource = "*"
57
+ }
58
+ ]
59
+ })
60
+
61
+ tags = var.tags
62
+ }
63
+
64
+ # Shared asset container registry. Every container image the workspace deploys is
65
+ # pushed here under a tag namespaced by the image's owner.
66
+ resource "aws_ecr_repository" "assets" {
67
+ name = "${var.repository_name_prefix}-${random_string.suffix.result}"
68
+
69
+ image_tag_mutability = "IMMUTABLE"
70
+ force_delete = true
71
+
72
+ encryption_configuration {
73
+ encryption_type = "KMS"
74
+ kms_key = aws_kms_key.assets.arn
75
+ }
76
+
77
+ image_scanning_configuration {
78
+ scan_on_push = true
79
+ }
80
+
81
+ tags = var.tags
82
+ }
83
+
84
+ resource "aws_ecr_repository_policy" "assets" {
85
+ repository = aws_ecr_repository.assets.name
86
+
87
+ policy = jsonencode({
88
+ Version = "2012-10-17"
89
+ Statement = [
90
+ {
91
+ Sid = "AllowPushPull"
92
+ Effect = "Allow"
93
+ Principal = {
94
+ AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
95
+ }
96
+ Action = [
97
+ "ecr:BatchCheckLayerAvailability",
98
+ "ecr:BatchGetImage",
99
+ "ecr:CompleteLayerUpload",
100
+ "ecr:GetDownloadUrlForLayer",
101
+ "ecr:InitiateLayerUpload",
102
+ "ecr:PutImage",
103
+ "ecr:UploadLayerPart"
104
+ ]
105
+ },
106
+ {
107
+ Sid = "AllowLambdaPull"
108
+ Effect = "Allow"
109
+ Principal = {
110
+ Service = "lambda.amazonaws.com"
111
+ }
112
+ Action = [
113
+ "ecr:BatchCheckLayerAvailability",
114
+ "ecr:BatchGetImage",
115
+ "ecr:GetDownloadUrlForLayer"
116
+ ]
117
+ }
118
+ ]
119
+ })
120
+ }
121
+
122
+ output "repository_name" {
123
+ description = "Name of the shared asset repository"
124
+ value = aws_ecr_repository.assets.name
125
+ }
126
+
127
+ output "repository_url" {
128
+ description = "URL of the shared asset repository — pass to app modules' `asset_ecr_repository_url` input."
129
+ value = aws_ecr_repository.assets.repository_url
130
+
131
+ depends_on = [aws_ecr_repository_policy.assets]
132
+ }
133
+
134
+ output "repository_arn" {
135
+ description = "ARN of the shared asset repository — pass to app modules' `asset_ecr_repository_arn` input, which scopes their image pull grants to it."
136
+ value = aws_ecr_repository.assets.arn
137
+ }
@@ -40,7 +40,7 @@ import { suppressRules } from './checkov<% if (esm) { %>.js<% } %>';
40
40
  const WEB_CLIENT_ID = 'WebClient';
41
41
 
42
42
  /** Local dev server origins permitted to complete the sign-in redirect */
43
- const LOCAL_CALLBACK_URLS = ['http://localhost:4200', 'http://localhost:4300'];
43
+ const LOCAL_CALLBACK_URLS = [];
44
44
 
45
45
  export interface UserIdentityProps {
46
46
  /**
@@ -69,10 +69,7 @@ data "aws_region" "current" {}
69
69
 
70
70
  locals {
71
71
  # Local dev server origins permitted to complete the sign-in redirect
72
- local_callback_urls = [
73
- "http://localhost:4200",
74
- "http://localhost:4300"
75
- ]
72
+ local_callback_urls = []
76
73
 
77
74
  user_pool_waf_log_group_name = var.enable_waf ? "aws-waf-logs-UserPool-${random_id.unique_suffix.hex}" : null
78
75
  }
@@ -17,10 +17,30 @@ export declare const IDENTITY_CONSTRUCTS_PY_DEPENDENCIES: readonly [{
17
17
  export interface AddIdentityInfraOptions {
18
18
  cognitoDomain: string;
19
19
  allowSignup: boolean;
20
+ /**
21
+ * Local ports (dev server, preview, ...) of the website adding auth,
22
+ * allow-listed as Cognito callback/logout URLs so its sign-in redirects succeed.
23
+ */
24
+ localCallbackPorts: number[];
20
25
  }
21
26
  /**
22
- * Add infrastructure for a static website
27
+ * Idempotently allow-list a local port as a Cognito callback/logout URL, on
28
+ * whichever shared identity construct/module the given `iac` provider vends.
29
+ * Shared between the auth generator (adding a new website's ports) and the
30
+ * migration that backfills ports for websites that predate per-project port
31
+ * assignment.
32
+ *
33
+ * Returns whether the port ended up allow-listed (already was, or now is) —
34
+ * `false` means the file doesn't exist or has diverged from the shape this
35
+ * can recognise, which the migration reports via `nextSteps` rather than
36
+ * clobbering.
37
+ */
38
+ export declare const addLocalCallbackUrl: (tree: Tree, iac: Iac, port: number) => Promise<boolean>;
39
+ /**
40
+ * Add infrastructure for a static website. Returns the local ports that could
41
+ * not be allow-listed as Cognito callback/logout URLs, because the shared
42
+ * construct/module no longer matches the generated shape.
23
43
  */
24
44
  export declare const addIdentityInfra: (tree: Tree, options: AddIdentityInfraOptions & {
25
45
  iac: Iac;
26
- }) => Promise<void>;
46
+ }) => Promise<number[]>;
@@ -2,7 +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 { generateFiles, joinPathFragments, OverwriteStrategy } from "@nx/devkit";
5
- import { addStarExport } from "../ast.js";
5
+ import { addStarExport, appendToArrayViaGritQL, applyGritQL, matchGritQL } from "../ast.js";
6
6
  import { esmVars } from "../module-format.js";
7
7
  import { generatedTerraform, PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../shared-constructs-constants.js";
8
8
  import { PY_VERSIONS, terraformProviderVersions } from "../versions.js";
@@ -17,12 +17,64 @@ import { PY_VERSIONS, terraformProviderVersions } from "../versions.js";
17
17
  }
18
18
  ];
19
19
  /**
20
- * Add infrastructure for a static website
20
+ * Idempotently allow-list a local port as a Cognito callback/logout URL, on
21
+ * whichever shared identity construct/module the given `iac` provider vends.
22
+ * Shared between the auth generator (adding a new website's ports) and the
23
+ * migration that backfills ports for websites that predate per-project port
24
+ * assignment.
25
+ *
26
+ * Returns whether the port ended up allow-listed (already was, or now is) —
27
+ * `false` means the file doesn't exist or has diverged from the shape this
28
+ * can recognise, which the migration reports via `nextSteps` rather than
29
+ * clobbering.
30
+ */ export const addLocalCallbackUrl = async (tree, iac, port)=>{
31
+ if (iac === 'cdk') {
32
+ return addLocalCallbackUrlToCdk(tree, port);
33
+ } else if (iac === 'terraform') {
34
+ return addLocalCallbackUrlToTerraform(tree, port);
35
+ } else {
36
+ throw new Error(`Unsupported iac ${iac}`);
37
+ }
38
+ };
39
+ const addLocalCallbackUrlToCdk = async (tree, port)=>{
40
+ const filePath = joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'core', 'user-identity.ts');
41
+ const url = `'http://localhost:${port}'`;
42
+ if (!tree.exists(filePath)) {
43
+ return false;
44
+ }
45
+ // Scoped to the array, so a URL mentioned elsewhere in the file (e.g. a
46
+ // comment, or a different array) doesn't read as allow-listed.
47
+ if (await matchGritQL(tree, filePath, `\`LOCAL_CALLBACK_URLS = [$items]\` where { $items <: contains \`${url}\` }`)) {
48
+ return true;
49
+ }
50
+ return appendToArrayViaGritQL(tree, filePath, 'LOCAL_CALLBACK_URLS = ', url);
51
+ };
52
+ // Rewrites the last array element directly (rather than inserting after a
53
+ // placeholder) so the indentation is exact — nothing reformats generated
54
+ // `.tf` files afterward.
55
+ const addLocalCallbackUrlToTerraform = async (tree, port)=>{
56
+ const filePath = joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'user-identity', 'identity', 'identity.tf');
57
+ const url = `"http://localhost:${port}"`;
58
+ if (!tree.exists(filePath)) {
59
+ return false;
60
+ }
61
+ // Scoped to the array, so a URL mentioned elsewhere in the file doesn't
62
+ // read as allow-listed.
63
+ if (await matchGritQL(tree, filePath, `language hcl\n\`local_callback_urls = [$items]\` where { $items <: contains \`${url}\` }`)) {
64
+ return true;
65
+ }
66
+ // `[$items]` doesn't match `[]`, so an empty list needs its own branch.
67
+ return applyGritQL(tree, filePath, `language hcl\n` + `or {` + ` \`local_callback_urls = []\` => \`local_callback_urls = [\n ${url}\n ]\`,` + ` \`local_callback_urls = [$items]\` where { $items <: not contains \`${url}\`, $items <: [$..., $last], $last => \`$last,\n ${url}\` }` + ` }`);
68
+ };
69
+ /**
70
+ * Add infrastructure for a static website. Returns the local ports that could
71
+ * not be allow-listed as Cognito callback/logout URLs, because the shared
72
+ * construct/module no longer matches the generated shape.
21
73
  */ export const addIdentityInfra = async (tree, options)=>{
22
74
  if (options.iac === 'cdk') {
23
- await addIdentityCdkConstructs(tree, options);
75
+ return addIdentityCdkConstructs(tree, options);
24
76
  } else if (options.iac === 'terraform') {
25
- addIdentityTerraformModules(tree, options);
77
+ return addIdentityTerraformModules(tree, options);
26
78
  } else {
27
79
  throw new Error(`Unsupported iac ${options.iac}`);
28
80
  }
@@ -35,8 +87,17 @@ const addIdentityCdkConstructs = async (tree, options)=>{
35
87
  overwriteStrategy: OverwriteStrategy.KeepExisting
36
88
  });
37
89
  await addStarExport(tree, joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'core', 'index.ts'), './user-identity.js');
90
+ // The construct is shared by every website in the workspace, so each one adding
91
+ // auth allow-lists its own local ports rather than overwriting the others'.
92
+ const portsNotAllowListed = [];
93
+ for (const port of options.localCallbackPorts){
94
+ if (!await addLocalCallbackUrlToCdk(tree, port)) {
95
+ portsNotAllowListed.push(port);
96
+ }
97
+ }
98
+ return portsNotAllowListed;
38
99
  };
39
- const addIdentityTerraformModules = (tree, options)=>{
100
+ const addIdentityTerraformModules = async (tree, options)=>{
40
101
  generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'terraform', 'core'), joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core'), {
41
102
  ...options,
42
103
  boto3Version: PY_VERSIONS.boto3,
@@ -44,6 +105,14 @@ const addIdentityTerraformModules = (tree, options)=>{
44
105
  }, {
45
106
  overwriteStrategy: OverwriteStrategy.KeepExisting
46
107
  });
108
+ // The module is shared by every website in the workspace, so each one adding
109
+ // auth allow-lists its own local ports rather than overwriting the others'.
110
+ const portsNotAllowListed = [];
111
+ for (const port of options.localCallbackPorts){
112
+ if (!await addLocalCallbackUrlToTerraform(tree, port)) {
113
+ portsNotAllowListed.push(port);
114
+ }
115
+ }
47
116
  // Update the static website module to add the callback url
48
117
  const staticWebsiteModule = tree.read(joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'static-website', 'static-website.tf'), 'utf-8');
49
118
  if (staticWebsiteModule && !staticWebsiteModule.includes('source = "../user-identity/add-callback-url"')) {
@@ -100,6 +169,7 @@ module "add_callback_url" {
100
169
  }
101
170
  }
102
171
  }
172
+ return portsNotAllowListed;
103
173
  };
104
174
 
105
175
  //# sourceMappingURL=identity-constructs.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/identity-constructs/identity-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 { DeclaredPyDependency } from '../declared-dependencies.js';\nimport type { Iac } from '../iac.js';\nimport { esmVars } from '../module-format.js';\nimport {\n generatedTerraform,\n type IacMetadata,\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport {\n type IPyDepVersion,\n PY_VERSIONS,\n terraformProviderVersions,\n} from '../versions.js';\n\n/**\n * Python version the generated Terraform pins in the callback URL module's inline\n * `uv run --with` script. Nothing installs it, so it is declared for its version\n * alone, and only on the Terraform branch that writes the script.\n */\nexport const IDENTITY_CONSTRUCTS_PY_DEPENDENCIES = [\n { name: 'boto3', when: generatedTerraform },\n] as const satisfies readonly DeclaredPyDependency<\n IPyDepVersion,\n IacMetadata\n>[];\n\nexport interface AddIdentityInfraOptions {\n cognitoDomain: string;\n allowSignup: boolean;\n}\n\n/**\n * Add infrastructure for a static website\n */\nexport const addIdentityInfra = async (\n tree: Tree,\n options: AddIdentityInfraOptions & { iac: Iac },\n) => {\n if (options.iac === 'cdk') {\n await addIdentityCdkConstructs(tree, options);\n } else if (options.iac === 'terraform') {\n addIdentityTerraformModules(tree, options);\n } else {\n throw new Error(`Unsupported iac ${options.iac}`);\n }\n};\n\nconst addIdentityCdkConstructs = async (\n tree: Tree,\n options: AddIdentityInfraOptions,\n) => {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'cdk', 'core'),\n joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'core'),\n { ...options, ...esmVars(tree) },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'index.ts',\n ),\n './user-identity.js',\n );\n};\n\nconst addIdentityTerraformModules = (\n tree: Tree,\n options: AddIdentityInfraOptions,\n) => {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'terraform', 'core'),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core'),\n {\n ...options,\n boto3Version: PY_VERSIONS.boto3,\n ...terraformProviderVersions(),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Update the static website module to add the callback url\n const staticWebsiteModule = tree.read(\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'core',\n 'static-website',\n 'static-website.tf',\n ),\n 'utf-8',\n );\n if (\n staticWebsiteModule &&\n !staticWebsiteModule.includes(\n 'source = \"../user-identity/add-callback-url\"',\n )\n ) {\n // Find the aws_cloudfront_distribution.website resource and add the callback URL module after it.\n // Includes the CloudFront domain plus any custom domain names (aliases) configured via var.custom_domain_names.\n const callbackUrlModule = `\n\n# Add CloudFront domain and any custom domain names to user pool client callback URLs.\n# Keyed by statically-known values so for_each keys are resolvable at plan time; the\n# CloudFront domain is only known at apply time, so it appears in the value, not the key.\nlocals {\n callback_urls = merge(\n { cloudfront = \"https://\\${aws_cloudfront_distribution.website.domain_name}\" },\n { for domain in var.custom_domain_names : domain => \"https://\\${domain}\" },\n )\n}\n\nmodule \"add_callback_url\" {\n source = \"../user-identity/add-callback-url\"\n for_each = local.callback_urls\n\n callback_url = each.value\n\n depends_on = [aws_cloudfront_distribution.website]\n}`;\n\n // Find the CloudFront distribution resource using proper brace counting\n // This handles deeply nested structures correctly\n const resourceStartPattern =\n /resource\\s+\"aws_cloudfront_distribution\"\\s+\"website\"\\s*\\{/;\n const resourceStartMatch = staticWebsiteModule.match(resourceStartPattern);\n\n if (resourceStartMatch) {\n const startIndex =\n resourceStartMatch.index! + resourceStartMatch[0].length - 1; // Position at opening brace\n let braceCount = 0;\n let insertionPoint = -1;\n\n // Count braces to find the end of the resource block\n for (let i = startIndex; i < staticWebsiteModule.length; i++) {\n const char = staticWebsiteModule[i];\n if (char === '{') {\n braceCount++;\n } else if (char === '}') {\n braceCount--;\n if (braceCount === 0) {\n // Found the closing brace of the CloudFront resource\n insertionPoint = i + 1;\n break;\n }\n }\n }\n\n if (insertionPoint !== -1) {\n // Insert the callback URL module right after the CloudFront distribution\n const beforeInsertion = staticWebsiteModule.substring(\n 0,\n insertionPoint,\n );\n const afterInsertion = staticWebsiteModule.substring(insertionPoint);\n const updatedContent =\n beforeInsertion + callbackUrlModule + afterInsertion;\n\n tree.write(\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'core',\n 'static-website',\n 'static-website.tf',\n ),\n updatedContent,\n );\n }\n }\n }\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","addStarExport","esmVars","generatedTerraform","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","PY_VERSIONS","terraformProviderVersions","IDENTITY_CONSTRUCTS_PY_DEPENDENCIES","name","when","addIdentityInfra","tree","options","iac","addIdentityCdkConstructs","addIdentityTerraformModules","Error","dirname","overwriteStrategy","KeepExisting","boto3Version","boto3","staticWebsiteModule","read","includes","callbackUrlModule","resourceStartPattern","resourceStartMatch","match","startIndex","index","length","braceCount","insertionPoint","i","char","beforeInsertion","substring","afterInsertion","updatedContent","write"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/identity-constructs/identity-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 {\n addStarExport,\n appendToArrayViaGritQL,\n applyGritQL,\n matchGritQL,\n} from '../ast.js';\nimport type { DeclaredPyDependency } from '../declared-dependencies.js';\nimport type { Iac } from '../iac.js';\nimport { esmVars } from '../module-format.js';\nimport {\n generatedTerraform,\n type IacMetadata,\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants.js';\nimport {\n type IPyDepVersion,\n PY_VERSIONS,\n terraformProviderVersions,\n} from '../versions.js';\n\n/**\n * Python version the generated Terraform pins in the callback URL module's inline\n * `uv run --with` script. Nothing installs it, so it is declared for its version\n * alone, and only on the Terraform branch that writes the script.\n */\nexport const IDENTITY_CONSTRUCTS_PY_DEPENDENCIES = [\n { name: 'boto3', when: generatedTerraform },\n] as const satisfies readonly DeclaredPyDependency<\n IPyDepVersion,\n IacMetadata\n>[];\n\nexport interface AddIdentityInfraOptions {\n cognitoDomain: string;\n allowSignup: boolean;\n /**\n * Local ports (dev server, preview, ...) of the website adding auth,\n * allow-listed as Cognito callback/logout URLs so its sign-in redirects succeed.\n */\n localCallbackPorts: number[];\n}\n\n/**\n * Idempotently allow-list a local port as a Cognito callback/logout URL, on\n * whichever shared identity construct/module the given `iac` provider vends.\n * Shared between the auth generator (adding a new website's ports) and the\n * migration that backfills ports for websites that predate per-project port\n * assignment.\n *\n * Returns whether the port ended up allow-listed (already was, or now is) —\n * `false` means the file doesn't exist or has diverged from the shape this\n * can recognise, which the migration reports via `nextSteps` rather than\n * clobbering.\n */\nexport const addLocalCallbackUrl = async (\n tree: Tree,\n iac: Iac,\n port: number,\n): Promise<boolean> => {\n if (iac === 'cdk') {\n return addLocalCallbackUrlToCdk(tree, port);\n } else if (iac === 'terraform') {\n return addLocalCallbackUrlToTerraform(tree, port);\n } else {\n throw new Error(`Unsupported iac ${iac}`);\n }\n};\n\nconst addLocalCallbackUrlToCdk = async (\n tree: Tree,\n port: number,\n): Promise<boolean> => {\n const filePath = joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'user-identity.ts',\n );\n const url = `'http://localhost:${port}'`;\n if (!tree.exists(filePath)) {\n return false;\n }\n // Scoped to the array, so a URL mentioned elsewhere in the file (e.g. a\n // comment, or a different array) doesn't read as allow-listed.\n if (\n await matchGritQL(\n tree,\n filePath,\n `\\`LOCAL_CALLBACK_URLS = [$items]\\` where { $items <: contains \\`${url}\\` }`,\n )\n ) {\n return true;\n }\n return appendToArrayViaGritQL(tree, filePath, 'LOCAL_CALLBACK_URLS = ', url);\n};\n\n// Rewrites the last array element directly (rather than inserting after a\n// placeholder) so the indentation is exact — nothing reformats generated\n// `.tf` files afterward.\nconst addLocalCallbackUrlToTerraform = async (\n tree: Tree,\n port: number,\n): Promise<boolean> => {\n const filePath = joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'core',\n 'user-identity',\n 'identity',\n 'identity.tf',\n );\n const url = `\"http://localhost:${port}\"`;\n if (!tree.exists(filePath)) {\n return false;\n }\n // Scoped to the array, so a URL mentioned elsewhere in the file doesn't\n // read as allow-listed.\n if (\n await matchGritQL(\n tree,\n filePath,\n `language hcl\\n\\`local_callback_urls = [$items]\\` where { $items <: contains \\`${url}\\` }`,\n )\n ) {\n return true;\n }\n // `[$items]` doesn't match `[]`, so an empty list needs its own branch.\n return applyGritQL(\n tree,\n filePath,\n `language hcl\\n` +\n `or {` +\n ` \\`local_callback_urls = []\\` => \\`local_callback_urls = [\\n ${url}\\n ]\\`,` +\n ` \\`local_callback_urls = [$items]\\` where { $items <: not contains \\`${url}\\`, $items <: [$..., $last], $last => \\`$last,\\n ${url}\\` }` +\n ` }`,\n );\n};\n\n/**\n * Add infrastructure for a static website. Returns the local ports that could\n * not be allow-listed as Cognito callback/logout URLs, because the shared\n * construct/module no longer matches the generated shape.\n */\nexport const addIdentityInfra = async (\n tree: Tree,\n options: AddIdentityInfraOptions & { iac: Iac },\n): Promise<number[]> => {\n if (options.iac === 'cdk') {\n return addIdentityCdkConstructs(tree, options);\n } else if (options.iac === 'terraform') {\n return addIdentityTerraformModules(tree, options);\n } else {\n throw new Error(`Unsupported iac ${options.iac}`);\n }\n};\n\nconst addIdentityCdkConstructs = async (\n tree: Tree,\n options: AddIdentityInfraOptions,\n): Promise<number[]> => {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'cdk', 'core'),\n joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'core'),\n { ...options, ...esmVars(tree) },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'index.ts',\n ),\n './user-identity.js',\n );\n\n // The construct is shared by every website in the workspace, so each one adding\n // auth allow-lists its own local ports rather than overwriting the others'.\n const portsNotAllowListed: number[] = [];\n for (const port of options.localCallbackPorts) {\n if (!(await addLocalCallbackUrlToCdk(tree, port))) {\n portsNotAllowListed.push(port);\n }\n }\n return portsNotAllowListed;\n};\n\nconst addIdentityTerraformModules = async (\n tree: Tree,\n options: AddIdentityInfraOptions,\n): Promise<number[]> => {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'terraform', 'core'),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core'),\n {\n ...options,\n boto3Version: PY_VERSIONS.boto3,\n ...terraformProviderVersions(),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // The module is shared by every website in the workspace, so each one adding\n // auth allow-lists its own local ports rather than overwriting the others'.\n const portsNotAllowListed: number[] = [];\n for (const port of options.localCallbackPorts) {\n if (!(await addLocalCallbackUrlToTerraform(tree, port))) {\n portsNotAllowListed.push(port);\n }\n }\n\n // Update the static website module to add the callback url\n const staticWebsiteModule = tree.read(\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'core',\n 'static-website',\n 'static-website.tf',\n ),\n 'utf-8',\n );\n if (\n staticWebsiteModule &&\n !staticWebsiteModule.includes(\n 'source = \"../user-identity/add-callback-url\"',\n )\n ) {\n // Find the aws_cloudfront_distribution.website resource and add the callback URL module after it.\n // Includes the CloudFront domain plus any custom domain names (aliases) configured via var.custom_domain_names.\n const callbackUrlModule = `\n\n# Add CloudFront domain and any custom domain names to user pool client callback URLs.\n# Keyed by statically-known values so for_each keys are resolvable at plan time; the\n# CloudFront domain is only known at apply time, so it appears in the value, not the key.\nlocals {\n callback_urls = merge(\n { cloudfront = \"https://\\${aws_cloudfront_distribution.website.domain_name}\" },\n { for domain in var.custom_domain_names : domain => \"https://\\${domain}\" },\n )\n}\n\nmodule \"add_callback_url\" {\n source = \"../user-identity/add-callback-url\"\n for_each = local.callback_urls\n\n callback_url = each.value\n\n depends_on = [aws_cloudfront_distribution.website]\n}`;\n\n // Find the CloudFront distribution resource using proper brace counting\n // This handles deeply nested structures correctly\n const resourceStartPattern =\n /resource\\s+\"aws_cloudfront_distribution\"\\s+\"website\"\\s*\\{/;\n const resourceStartMatch = staticWebsiteModule.match(resourceStartPattern);\n\n if (resourceStartMatch) {\n const startIndex =\n resourceStartMatch.index! + resourceStartMatch[0].length - 1; // Position at opening brace\n let braceCount = 0;\n let insertionPoint = -1;\n\n // Count braces to find the end of the resource block\n for (let i = startIndex; i < staticWebsiteModule.length; i++) {\n const char = staticWebsiteModule[i];\n if (char === '{') {\n braceCount++;\n } else if (char === '}') {\n braceCount--;\n if (braceCount === 0) {\n // Found the closing brace of the CloudFront resource\n insertionPoint = i + 1;\n break;\n }\n }\n }\n\n if (insertionPoint !== -1) {\n // Insert the callback URL module right after the CloudFront distribution\n const beforeInsertion = staticWebsiteModule.substring(\n 0,\n insertionPoint,\n );\n const afterInsertion = staticWebsiteModule.substring(insertionPoint);\n const updatedContent =\n beforeInsertion + callbackUrlModule + afterInsertion;\n\n tree.write(\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'core',\n 'static-website',\n 'static-website.tf',\n ),\n updatedContent,\n );\n }\n }\n }\n\n return portsNotAllowListed;\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","addStarExport","appendToArrayViaGritQL","applyGritQL","matchGritQL","esmVars","generatedTerraform","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","PY_VERSIONS","terraformProviderVersions","IDENTITY_CONSTRUCTS_PY_DEPENDENCIES","name","when","addLocalCallbackUrl","tree","iac","port","addLocalCallbackUrlToCdk","addLocalCallbackUrlToTerraform","Error","filePath","url","exists","addIdentityInfra","options","addIdentityCdkConstructs","addIdentityTerraformModules","dirname","overwriteStrategy","KeepExisting","portsNotAllowListed","localCallbackPorts","push","boto3Version","boto3","staticWebsiteModule","read","includes","callbackUrlModule","resourceStartPattern","resourceStartMatch","match","startIndex","index","length","braceCount","insertionPoint","i","char","beforeInsertion","substring","afterInsertion","updatedContent","write"],"mappings":"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"}
@@ -3,6 +3,11 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { type ProjectConfiguration, type Tree } from '@nx/devkit';
6
+ /**
7
+ * Return the first port already registered in a project's own metadata, if any.
8
+ * Supports both the metadata.ports array and the legacy metadata.port.
9
+ */
10
+ export declare const getExistingProjectPort: (project: ProjectConfiguration) => number | undefined;
6
11
  /**
7
12
  * Options for assigning a port to a target within a project.
8
13
  */
package/src/utils/port.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Return the first port already registered in a project's own metadata, if any.
7
7
  * Supports both the metadata.ports array and the legacy metadata.port.
8
- */ const getExistingProjectPort = (project)=>{
8
+ */ export const getExistingProjectPort = (project)=>{
9
9
  const metadata = project.metadata;
10
10
  if (Array.isArray(metadata?.ports) && typeof metadata.ports[0] === 'number') {
11
11
  return metadata.ports[0];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/port.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { getProjects, type ProjectConfiguration, type Tree } from '@nx/devkit';\n\n/**\n * Return the first port already registered in a project's own metadata, if any.\n * Supports both the metadata.ports array and the legacy metadata.port.\n */\nconst getExistingProjectPort = (\n project: ProjectConfiguration,\n): number | undefined => {\n const metadata = project.metadata as any;\n if (Array.isArray(metadata?.ports) && typeof metadata.ports[0] === 'number') {\n return metadata.ports[0];\n }\n if (typeof metadata?.port === 'number') {\n return metadata.port;\n }\n return undefined;\n};\n\n/**\n * Return the port previously assigned to a particular component within a\n * project, if any. Projects which host multiple components (e.g. one agent or\n * MCP server per component) record a port per component in metadata.components.\n */\nconst getExistingComponentPort = (\n project: ProjectConfiguration,\n component: { info: { id: string }; name?: string },\n): number | undefined => {\n const components = (project.metadata as any)?.components ?? [];\n const match = components.find(\n (c: any) => c.generator === component.info.id && c.name === component.name,\n );\n return typeof match?.port === 'number' ? match.port : undefined;\n};\n\n/**\n * Options for assigning a port to a target within a project.\n */\nexport interface AssignPortOptions {\n /**\n * Identifies the component the port is being assigned for. When provided, a\n * port already assigned to this component is reused so re-runs are\n * idempotent. Use this for projects which host a port per component.\n */\n component?: { info: { id: string }; name?: string };\n}\n\n/**\n * Assign a port shared across all projects created by the same generator (or\n * family of generators). If this project already has a port, or a sibling\n * project created by any of the given generator IDs has one, reuse it;\n * otherwise assign a fresh one. Mutates the project to register the port in\n * its metadata.ports.\n */\nexport const assignSharedPort = (\n tree: Tree,\n project: ProjectConfiguration,\n generatorId: string | string[],\n startPort: number,\n): number => {\n const generatorIds = Array.isArray(generatorId) ? generatorId : [generatorId];\n\n // Reuse this project's own port if it already has one (idempotent on re-run)\n const ownPort = getExistingProjectPort(project);\n if (ownPort !== undefined) {\n return ownPort;\n }\n\n // Otherwise reuse a sibling project's shared port if one exists\n const siblingPort = [...getProjects(tree).values()]\n .filter((p) => p.name !== project.name)\n .map((p) => ({\n generator: (p.metadata as any)?.generator,\n port: getExistingProjectPort(p),\n }))\n .find(\n (p) => generatorIds.includes(p.generator) && p.port !== undefined,\n )?.port;\n\n if (siblingPort !== undefined) {\n project.metadata ??= {};\n (project.metadata as any).ports = [\n ...((project.metadata as any).ports ?? []),\n siblingPort,\n ];\n return siblingPort;\n }\n\n return assignPort(tree, project, startPort);\n};\n\n/**\n * Assign a port for a particular target within a project.\n * Mutates the project to add/update its metadata.ports with the newly assigned port.\n *\n * Re-runs are idempotent: a port previously assigned to this project (or, when\n * `options.component` is provided, to this component) is returned as-is without\n * registering a duplicate.\n */\nexport const assignPort = (\n tree: Tree,\n project: ProjectConfiguration,\n startPort: number,\n options?: AssignPortOptions,\n) => {\n // Reuse a port already assigned to this project/component on re-run\n const existingPort = options?.component\n ? getExistingComponentPort(project, options.component)\n : getExistingProjectPort(project);\n if (existingPort !== undefined) {\n return existingPort;\n }\n\n // Find all existing ports\n const allPorts = new Set(\n [...getProjects(tree).values()].flatMap((p) => {\n const metadata = p.metadata as any;\n const ports: number[] = [];\n // Support old port assignment where each project registered a single port\n if (metadata?.port && typeof metadata.port === 'number') {\n ports.push(metadata.port);\n }\n if (\n metadata?.ports &&\n typeof metadata.ports === 'object' &&\n Array.isArray(metadata.ports)\n ) {\n ports.push(...metadata.ports);\n }\n return ports;\n }),\n );\n\n // Find the next available port higher than the start port\n let candidatePort = startPort;\n while (allPorts.has(candidatePort)) {\n candidatePort++;\n }\n\n project.metadata ??= {};\n (project.metadata as any).ports ??= [];\n (project.metadata as any).ports = [\n ...(project.metadata as any).ports,\n candidatePort,\n ];\n\n return candidatePort;\n};\n"],"names":["getProjects","getExistingProjectPort","project","metadata","Array","isArray","ports","port","undefined","getExistingComponentPort","component","components","match","find","c","generator","info","id","name","assignSharedPort","tree","generatorId","startPort","generatorIds","ownPort","siblingPort","values","filter","p","map","includes","assignPort","options","existingPort","allPorts","Set","flatMap","push","candidatePort","has"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/port.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { getProjects, type ProjectConfiguration, type Tree } from '@nx/devkit';\n\n/**\n * Return the first port already registered in a project's own metadata, if any.\n * Supports both the metadata.ports array and the legacy metadata.port.\n */\nexport const getExistingProjectPort = (\n project: ProjectConfiguration,\n): number | undefined => {\n const metadata = project.metadata as any;\n if (Array.isArray(metadata?.ports) && typeof metadata.ports[0] === 'number') {\n return metadata.ports[0];\n }\n if (typeof metadata?.port === 'number') {\n return metadata.port;\n }\n return undefined;\n};\n\n/**\n * Return the port previously assigned to a particular component within a\n * project, if any. Projects which host multiple components (e.g. one agent or\n * MCP server per component) record a port per component in metadata.components.\n */\nconst getExistingComponentPort = (\n project: ProjectConfiguration,\n component: { info: { id: string }; name?: string },\n): number | undefined => {\n const components = (project.metadata as any)?.components ?? [];\n const match = components.find(\n (c: any) => c.generator === component.info.id && c.name === component.name,\n );\n return typeof match?.port === 'number' ? match.port : undefined;\n};\n\n/**\n * Options for assigning a port to a target within a project.\n */\nexport interface AssignPortOptions {\n /**\n * Identifies the component the port is being assigned for. When provided, a\n * port already assigned to this component is reused so re-runs are\n * idempotent. Use this for projects which host a port per component.\n */\n component?: { info: { id: string }; name?: string };\n}\n\n/**\n * Assign a port shared across all projects created by the same generator (or\n * family of generators). If this project already has a port, or a sibling\n * project created by any of the given generator IDs has one, reuse it;\n * otherwise assign a fresh one. Mutates the project to register the port in\n * its metadata.ports.\n */\nexport const assignSharedPort = (\n tree: Tree,\n project: ProjectConfiguration,\n generatorId: string | string[],\n startPort: number,\n): number => {\n const generatorIds = Array.isArray(generatorId) ? generatorId : [generatorId];\n\n // Reuse this project's own port if it already has one (idempotent on re-run)\n const ownPort = getExistingProjectPort(project);\n if (ownPort !== undefined) {\n return ownPort;\n }\n\n // Otherwise reuse a sibling project's shared port if one exists\n const siblingPort = [...getProjects(tree).values()]\n .filter((p) => p.name !== project.name)\n .map((p) => ({\n generator: (p.metadata as any)?.generator,\n port: getExistingProjectPort(p),\n }))\n .find(\n (p) => generatorIds.includes(p.generator) && p.port !== undefined,\n )?.port;\n\n if (siblingPort !== undefined) {\n project.metadata ??= {};\n (project.metadata as any).ports = [\n ...((project.metadata as any).ports ?? []),\n siblingPort,\n ];\n return siblingPort;\n }\n\n return assignPort(tree, project, startPort);\n};\n\n/**\n * Assign a port for a particular target within a project.\n * Mutates the project to add/update its metadata.ports with the newly assigned port.\n *\n * Re-runs are idempotent: a port previously assigned to this project (or, when\n * `options.component` is provided, to this component) is returned as-is without\n * registering a duplicate.\n */\nexport const assignPort = (\n tree: Tree,\n project: ProjectConfiguration,\n startPort: number,\n options?: AssignPortOptions,\n) => {\n // Reuse a port already assigned to this project/component on re-run\n const existingPort = options?.component\n ? getExistingComponentPort(project, options.component)\n : getExistingProjectPort(project);\n if (existingPort !== undefined) {\n return existingPort;\n }\n\n // Find all existing ports\n const allPorts = new Set(\n [...getProjects(tree).values()].flatMap((p) => {\n const metadata = p.metadata as any;\n const ports: number[] = [];\n // Support old port assignment where each project registered a single port\n if (metadata?.port && typeof metadata.port === 'number') {\n ports.push(metadata.port);\n }\n if (\n metadata?.ports &&\n typeof metadata.ports === 'object' &&\n Array.isArray(metadata.ports)\n ) {\n ports.push(...metadata.ports);\n }\n return ports;\n }),\n );\n\n // Find the next available port higher than the start port\n let candidatePort = startPort;\n while (allPorts.has(candidatePort)) {\n candidatePort++;\n }\n\n project.metadata ??= {};\n (project.metadata as any).ports ??= [];\n (project.metadata as any).ports = [\n ...(project.metadata as any).ports,\n candidatePort,\n ];\n\n return candidatePort;\n};\n"],"names":["getProjects","getExistingProjectPort","project","metadata","Array","isArray","ports","port","undefined","getExistingComponentPort","component","components","match","find","c","generator","info","id","name","assignSharedPort","tree","generatorId","startPort","generatorIds","ownPort","siblingPort","values","filter","p","map","includes","assignPort","options","existingPort","allPorts","Set","flatMap","push","candidatePort","has"],"mappings":"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"}