From 371db56edbad62dad843801c03fbd2cc11547205 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:07:14 -0700 Subject: [PATCH 01/93] adding output schemas --- package-lock.json | 3 +++ packages/backend-output-schemas/src/geo/index.ts | 8 ++++++++ packages/backend-output-schemas/src/geo/v1.ts | 10 ++++++++++ packages/backend-output-schemas/src/index.ts | 16 ++++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 packages/backend-output-schemas/src/geo/index.ts create mode 100644 packages/backend-output-schemas/src/geo/v1.ts diff --git a/package-lock.json b/package-lock.json index c5f1a65ff49..ac339ab1df3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50170,6 +50170,9 @@ "packages/form-generator": { "name": "@aws-amplify/form-generator", "version": "1.2.3", + "bundleDependencies": [ + "@graphql-codegen/core" + ], "license": "Apache-2.0", "dependencies": { "@aws-amplify/appsync-modelgen-plugin": "^2.11.0", diff --git a/packages/backend-output-schemas/src/geo/index.ts b/packages/backend-output-schemas/src/geo/index.ts new file mode 100644 index 00000000000..4c0fddf1e22 --- /dev/null +++ b/packages/backend-output-schemas/src/geo/index.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { geoOutputSchema as geoOutputSchemaV1 } from './v1'; + +export const versionedGeoOutputSchema = z.discriminatedUnion('version', [ + geoOutputSchemaV1, +]); + +export type GeoOutput = z.infer; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts new file mode 100644 index 00000000000..2c74fc45cab --- /dev/null +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const geoOutputSchema = z.object({ + version: z.literal('1'), + payload: z.object({ + defaultCollection: z.string(), + geoRegion: z.string(), + collections: z.string(z.array(z.string()).optional()), // JSON serialized array of collection names + }), +}); diff --git a/packages/backend-output-schemas/src/index.ts b/packages/backend-output-schemas/src/index.ts index 11bfb14cd2d..0f4348fddee 100644 --- a/packages/backend-output-schemas/src/index.ts +++ b/packages/backend-output-schemas/src/index.ts @@ -6,6 +6,7 @@ import { versionedStackOutputSchema } from './stack/index.js'; import { versionedCustomOutputSchema } from './custom'; import { versionedFunctionOutputSchema } from './function/index.js'; import { versionedAIConversationOutputSchema } from './ai/conversation/index.js'; +import { versionedGeoOutputSchema } from './geo/index.js'; /** * The auth, graphql and storage exports here are duplicated from the submodule exports in the package.json file @@ -99,6 +100,20 @@ export * from './ai/conversation/index.js'; */ export const aiConversationOutputKey = 'AWS::Amplify::AI::Conversation'; +/** + * ---------- Geo exports ---------- + */ + +/** + * re-export the AI conversation output schema + */ +export * from './geo/index.js'; + +/** + * Expected key that AI conversation output is stored under + */ +export const geoOutputKey = 'AWS::Amplify::Geo'; + /** * ---------- Unified exports ---------- */ @@ -115,6 +130,7 @@ export const unifiedBackendOutputSchema = z.object({ [customOutputKey]: versionedCustomOutputSchema.optional(), [functionOutputKey]: versionedFunctionOutputSchema.optional(), [aiConversationOutputKey]: versionedAIConversationOutputSchema.optional(), + [geoOutputKey]: versionedGeoOutputSchema.optional(), }); /** * This type is a subset of the BackendOutput type that is exposed by the platform. From 3f1f95a3921df86d71139964aa002237b10f99ed Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:09:45 -0700 Subject: [PATCH 02/93] creating changeset --- .changeset/empty-trains-cut.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/empty-trains-cut.md diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct From ea9202d9b477d504938a79bc2edee8fc0cb10397 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:10:58 -0700 Subject: [PATCH 03/93] API changes --- packages/backend-output-schemas/API.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 23c5a41d89f..706b7ab13dd 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -66,6 +66,12 @@ export type FunctionOutput = z.infer; // @public export const functionOutputKey = "AWS::Amplify::Function"; +// @public (undocumented) +export type GeoOutput = z.infer; + +// @public +export const geoOutputKey = "AWS::Amplify::Geo"; + // @public (undocumented) export type GraphqlOutput = z.infer; @@ -371,6 +377,36 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; }>]>>; + "AWS::Amplify::Geo": z.ZodOptional; + payload: z.ZodObject<{ + defaultCollection: z.ZodString; + geoRegion: z.ZodString; + collections: z.ZodString; + }, "strip", z.ZodTypeAny, { + defaultCollection: string; + geoRegion: string; + collections: string; + }, { + defaultCollection: string; + geoRegion: string; + collections: string; + }>; + }, "strip", z.ZodTypeAny, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + }, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + }>]>>; }, "strip", z.ZodTypeAny, { "AWS::Amplify::Platform"?: { version: "1"; @@ -443,6 +479,14 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; } | undefined; + "AWS::Amplify::Geo"?: { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + } | undefined; }, { "AWS::Amplify::Platform"?: { version: "1"; @@ -515,6 +559,14 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; } | undefined; + "AWS::Amplify::Geo"?: { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + } | undefined; }>; // @public (undocumented) @@ -700,6 +752,38 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ }; }>]>; +// @public (undocumented) +export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ + version: z.ZodLiteral<"1">; + payload: z.ZodObject<{ + defaultCollection: z.ZodString; + geoRegion: z.ZodString; + collections: z.ZodString; + }, "strip", z.ZodTypeAny, { + defaultCollection: string; + geoRegion: string; + collections: string; + }, { + defaultCollection: string; + geoRegion: string; + collections: string; + }>; +}, "strip", z.ZodTypeAny, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; +}, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; +}>]>; + // @public (undocumented) export const versionedGraphqlOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; From a6916e4a51b8d1d5c77b52dd91aec30822010946 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:32:56 -0700 Subject: [PATCH 04/93] adding package and required files with partial implementation --- package-lock.json | 35 ++++++++++++++++++++++++++++++++--- package.json | 3 +++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac339ab1df3..dd890f7dc61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "workspaces": [ "packages/*" ], + "dependencies": { + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + }, "devDependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", @@ -578,6 +581,10 @@ "resolved": "packages/backend-function", "link": true }, + "node_modules/@aws-amplify/backend-geo": { + "resolved": "packages/backend-geo", + "link": true + }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12810,6 +12817,19 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, + "node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.206.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", + "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.206.0", + "constructs": "^10.0.0" + } + }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -35408,9 +35428,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.204.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.204.0.tgz", - "integrity": "sha512-mY3nYu+QvPhO+fz+LCFKbc0PFhTHbHzDLnbcA2fPcQBKciYnTixpBd2ccRlKYWbG4y6NTc6ju6DudZ3HIS4OlA==", + "version": "2.206.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.206.0.tgz", + "integrity": "sha512-WQGSSzSX+CvIG3j4GICxCAARGaB2dbB2ZiAn8dqqWdUkF6G9pedlSd3bjB0NHOqrxJMu3jYQCYf3gLYTaJuR8A==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -49744,6 +49764,15 @@ "constructs": "^10.0.0" } }, + "packages/backend-geo": { + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.158.0", + "constructs": "^10.0.0" + } + }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", diff --git a/package.json b/package.json index 29cc9161332..fe62027d13d 100644 --- a/package.json +++ b/package.json @@ -117,5 +117,8 @@ "overrides": { "minimatch": "10.0.1", "@graphql-tools/merge": "9.0.22" + }, + "dependencies": { + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } From b11dc0927be14d6e178d89e129283fb3a5d02b90 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:34:30 -0700 Subject: [PATCH 05/93] adding API changes with package --- packages/create-amplify/src/default_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-amplify/src/default_packages.json b/packages/create-amplify/src/default_packages.json index 4502bc76d41..77a6695ae7a 100644 --- a/packages/create-amplify/src/default_packages.json +++ b/packages/create-amplify/src/default_packages.json @@ -2,7 +2,7 @@ "defaultDevPackages": [ "@aws-amplify/backend", "@aws-amplify/backend-cli", - "aws-cdk-lib@2.204.0", + "aws-cdk-lib@2.206.0", "constructs@^10.0.0", "typescript@^5.0.0", "tsx", From 8af5a590cc2b7289b68f5532913e1e54dfe8d1a0 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 15:46:00 -0700 Subject: [PATCH 06/93] adding initial version of construct and API --- .changeset/warm-garlics-flow.md | 5 + package-lock.json | 7 + package.json | 2 + packages/backend-geo/.npmignore | 14 ++ packages/backend-geo/API.md | 115 ++++++++++++ packages/backend-geo/README.md | 3 + packages/backend-geo/api-extractor.json | 3 + packages/backend-geo/package.json | 29 +++ packages/backend-geo/src/access_builder.ts | 105 +++++++++++ packages/backend-geo/src/construct.ts | 56 ++++++ packages/backend-geo/src/factory.ts | 166 ++++++++++++++++++ .../src/geo_access_orchestrator.ts | 90 ++++++++++ .../src/geo_access_policy_factory.ts | 62 +++++++ packages/backend-geo/src/index.ts | 7 + packages/backend-geo/src/types.ts | 80 +++++++++ packages/backend-geo/tsconfig.json | 5 + packages/backend-geo/typedoc.json | 3 + 17 files changed, 752 insertions(+) create mode 100644 .changeset/warm-garlics-flow.md create mode 100644 packages/backend-geo/.npmignore create mode 100644 packages/backend-geo/API.md create mode 100644 packages/backend-geo/README.md create mode 100644 packages/backend-geo/api-extractor.json create mode 100644 packages/backend-geo/package.json create mode 100644 packages/backend-geo/src/access_builder.ts create mode 100644 packages/backend-geo/src/construct.ts create mode 100644 packages/backend-geo/src/factory.ts create mode 100644 packages/backend-geo/src/geo_access_orchestrator.ts create mode 100644 packages/backend-geo/src/geo_access_policy_factory.ts create mode 100644 packages/backend-geo/src/index.ts create mode 100644 packages/backend-geo/src/types.ts create mode 100644 packages/backend-geo/tsconfig.json create mode 100644 packages/backend-geo/typedoc.json diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. diff --git a/package-lock.json b/package-lock.json index dd890f7dc61..79d21f9f0f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "packages/*" ], "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" }, "devDependencies": { @@ -49768,6 +49770,11 @@ "name": "@aws-amplify/backend-geo", "version": "0.1.0", "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + }, "peerDependencies": { "aws-cdk-lib": "^2.158.0", "constructs": "^10.0.0" diff --git a/package.json b/package.json index fe62027d13d..99dca9415b0 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,8 @@ "@graphql-tools/merge": "9.0.22" }, "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } diff --git a/packages/backend-geo/.npmignore b/packages/backend-geo/.npmignore new file mode 100644 index 00000000000..dbde1fb5dbc --- /dev/null +++ b/packages/backend-geo/.npmignore @@ -0,0 +1,14 @@ +# Be very careful editing this file. It is crafted to work around [this issue](https://github.com/npm/npm/issues/4479) + +# First ignore everything +**/* + +# Then add back in transpiled js and ts declaration files +!lib/**/*.js +!lib/**/*.d.ts + +# Then ignore test js and ts declaration files +*.test.js +*.test.d.ts + +# This leaves us with including only js and ts declaration files of functional code diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md new file mode 100644 index 00000000000..5c6feff41d0 --- /dev/null +++ b/packages/backend-geo/API.md @@ -0,0 +1,115 @@ +## API Report File for "@aws-amplify/backend-geo" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam'; +import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { Construct } from 'constructs'; +import { ConstructFactory } from '@aws-amplify/plugin-types'; +import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; +import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { ResourceProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { StackProvider } from '@aws-amplify/plugin-types'; + +// @public +export class AmplifyGeoFactory implements ConstructFactory> { + // Warning: (ae-forgotten-export) The symbol "GeoAccessOrchestratorFactory" needs to be exported by the entry point index.d.ts + constructor(props: AmplifyGeoFactoryProps, geoAccessOrchestratorFactory?: GeoAccessOrchestratorFactory); + // Warning: (ae-forgotten-export) The symbol "AmplifyGeo" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getInstance: (getInstanceProps: ConstructFactoryGetInstanceProps) => AmplifyGeo; +} + +// @public (undocumented) +export type AmplifyGeoFactoryProps = Omit & { + region: string; + access: GeoAccessGenerator; + resourceIdentifier?: GeoResourceType; +}; + +// @public (undocumented) +export type AmplifyGeoProps = { + name: string; + collectionProps?: GeofenceCollectionProps; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +// @public (undocumented) +export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; + +// @public +export const defineCollection: (// returns resources and any stack errors +props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const defineMap: (// doesn't return anything because it only configures access +props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; + +// @public +export const definePlace: (// doesn't return anything because it only configures access +props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; + +// @public (undocumented) +export type GeoAccessBuilder = { + authenticated: GeoActionBuilder; + guest: GeoActionBuilder; + groups: (groupNames: string[]) => GeoActionBuilder; +}; + +// @public (undocumented) +export type GeoAccessDefinition = { + userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; + actions: GeoAction[]; + uniqueDefinitionValidators: { + uniqueRoleToken: string; + validationErrorOptions: AmplifyUserErrorOptions; + }[]; +}; + +// @public (undocumented) +export type GeoAccessGenerator = (allow: GeoAccessBuilder) => GeoAccessDefinition[]; + +// @public (undocumented) +export type GeoAction = MapAction | IndexAction | CollectionAction; + +// @public (undocumented) +export type GeoActionBuilder = { + to: (actions: GeoAction[]) => GeoAccessDefinition; +}; + +// @public (undocumented) +export const geoCfnResourceTypes: string[]; + +// @public (undocumented) +export const geoManagedResourceTypes: string[]; + +// @public (undocumented) +export type GeoResources = { + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; + +// @public (undocumented) +export type GeoResourceType = 'map' | 'place' | 'collection'; + +// @public (undocumented) +export type IndexAction = 'autocomplete' | 'geocode' | 'search'; + +// @public (undocumented) +export type MapAction = 'get'; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/backend-geo/README.md b/packages/backend-geo/README.md new file mode 100644 index 00000000000..793417be040 --- /dev/null +++ b/packages/backend-geo/README.md @@ -0,0 +1,3 @@ +# Description + +Replace with a description of this package diff --git a/packages/backend-geo/api-extractor.json b/packages/backend-geo/api-extractor.json new file mode 100644 index 00000000000..0f56de03f66 --- /dev/null +++ b/packages/backend-geo/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.base.json" +} diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json new file mode 100644 index 00000000000..06c6879d8af --- /dev/null +++ b/packages/backend-geo/package.json @@ -0,0 +1,29 @@ +{ + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "type": "module", + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js", + "require": "./lib/index.js" + } + }, + "types": "lib/index.d.ts", + "scripts": { + "update:api": "api-extractor run --local" + }, + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.158.0", + "constructs": "^10.0.0" + }, + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + } +} diff --git a/packages/backend-geo/src/access_builder.ts b/packages/backend-geo/src/access_builder.ts new file mode 100644 index 00000000000..a07d0b95b94 --- /dev/null +++ b/packages/backend-geo/src/access_builder.ts @@ -0,0 +1,105 @@ +import { + AuthResources, + AuthRoleName, + ConstructFactoryGetInstanceProps, + ResourceAccessAcceptorFactory, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { GeoAccessBuilder } from './types.js'; + +export type Roles = IRole; + +export const roleAccessBuilder: GeoAccessBuilder = { + authenticated: { + // access for authenticated users + to: (actions) => ({ + userRoles: [getAuthRole], + actions, + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: `Access definition for authenticated users specified multiple times.`, + resolution: `Combine all access definitions for authenticated users into one access rule.`, + }, + }, + ], + }), + }, + guest: { + // access for guest users + to: (actions) => ({ + userRoles: [getUnauthRole], + actions, + uniqueDefinitionValidators: [ + { + uniqueRoleToken: `guest`, + validationErrorOptions: { + message: `Access definition for guest users specified multiple times.`, + resolution: `Combine all access definitions for guest users into one access rule.`, + }, + }, + ], + }), + }, + groups: (groupNames) => ({ + // access for user groups + to: (actions) => ({ + userRoles: groupNames.map( + // for each group in the user groups + (groupName) => (getInstanceProps) => + getUserRole(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) + ), + actions, + uniqueDefinitionValidators: groupNames.map((groupName) => ({ + uniqueRoleToken: `${groupName}`, + validationErrorOptions: { + message: `Access definition for the group ${groupName} specified multiple times.`, + resolution: `Combine all access definitions for the group ${groupName} into one access rule.`, + }, + })), + }), + }), +}; + +const getAuthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => + getUserRole(getInstanceProps, 'authenticatedUserIamRole'); + +const getUnauthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => + getUserRole(getInstanceProps, 'unauthenticatedUserIamRole'); + +// getting acceptor objects for different role types (defined in auth) +const getUserRole = ( + getInstanceProps: ConstructFactoryGetInstanceProps, // instance properties of the auth factory? + roleName: AuthRoleName | string, // name of role to get acceptors from +): IRole => { + const authResources = getInstanceProps.constructContainer + .getConstructFactory< + ResourceProvider & ResourceAccessAcceptorFactory + >( + // getting construct container to look for a specific construct factory + 'AuthResources', + ) + ?.getInstance(getInstanceProps).resources as AuthResources; // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors + if (!authResources) { + throw new Error( + `Cannot specify geo resource access for ${ + roleName as string + } users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.`, + ); + } else { + if (roleName === authResources.authenticatedUserIamRole.roleName) { + return authResources.authenticatedUserIamRole; + } else if (roleName === authResources.unauthenticatedUserIamRole.roleName) { + return authResources.unauthenticatedUserIamRole; + } else if (authResources.groups[roleName]) { + return authResources.groups[roleName].role; + } + throw new Error( + `Role: ${ + roleName as string + } does not exist. See https://console.aws.amazon.com/iam/ to define the role.`, + ); + } +}; diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts new file mode 100644 index 00000000000..7c0b1cbf986 --- /dev/null +++ b/packages/backend-geo/src/construct.ts @@ -0,0 +1,56 @@ +import { Construct } from 'constructs'; +import { AmplifyGeoProps, GeoResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { + GeofenceCollection, + GeofenceCollectionProps, +} from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; + +/** + * Amplify Geo CDK Construct + * + * Designed as a wrapper around a Geofence Collection provided by Amazon Location Services. + */ +export class AmplifyGeo + extends Construct + implements ResourceProvider, StackProvider +{ + readonly stack: Stack; // current stack + readonly resources: GeoResources; + readonly name: string; // construct name + readonly id: string; + + /** + * Constructs a new AmplifyGeo instance + */ + constructor(scope: Construct, id: string, props: AmplifyGeoProps) { + super(scope, id); // call to Construct class as part of inheritance + this.name = props.name; + this.id = id; + if (props.collectionProps) { + const geofenceCollectionProps: GeofenceCollectionProps = { + // property mapping + geofenceCollectionName: props.collectionProps.geofenceCollectionName, + description: props.collectionProps.description, + kmsKey: props.collectionProps.kmsKey, + }; + + const geofenceCollection = new GeofenceCollection( + this, + id, + geofenceCollectionProps, + ); // L2 construct call + + this.resources = { + collection: geofenceCollection, + cfnResources: { + cfnCollection: geofenceCollection.node.findChild( + 'Resource', + ) as CfnGeofenceCollection, // getting L1 child instance + }, + }; + } + } +} diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts new file mode 100644 index 00000000000..4bef73ec83c --- /dev/null +++ b/packages/backend-geo/src/factory.ts @@ -0,0 +1,166 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aws } from 'aws-cdk-lib/core'; +import { AmplifyGeoFactoryProps, GeoResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyGeo } from './construct.js'; +import { Tags } from 'aws-cdk-lib'; +import { TagName } from '@aws-amplify/platform-core'; + +// define factory class +/** + * Amplify Geo Construct Factory + * + * Designed to manage the initialization of AmplifyGeo construct among other constructs and construct-agnostic resource access orchestration. + */ +export class AmplifyGeoFactory + implements ConstructFactory> +{ + private geoGenerator: ConstructContainerEntryGenerator; + + // define constructor for class + /** + * Constructs a new AmplifyGeoFactory instance + * @param props - properties for the geo factory + * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) + */ + constructor( + private readonly props: AmplifyGeoFactoryProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + // define GetInstance function + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyGeo => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // AWS-MANAGED RESOURCE ACCESS ORCHESTRATION HERE + if (this.props.resourceIdentifier !== 'collection') { + // only limited to collections currently + const geoAccessOrchestrator = + this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + getInstanceProps, + ); + + // access orchestration for non-collection resources done here + geoAccessOrchestrator.orchestrateGeoAccess( + `arn:${Aws.PARTITION}:geo-${this.props.resourceIdentifier}:${this.props.region}::provider/default`, + ); + } + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyGeoGenerator(this.props, getInstanceProps); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyGeo; + }; +} + +// define generator class +/** + * Amplify Geo Construct Generator + * + * Designed to manage the generation of the Amplify Geo construct instance along with resource access orchestration. + */ +export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Constructs an AmplifyGeoGenerator instance + * @param props - properties for the Amplify Geo Factory + * @param getInstanceProps - instance properties of a specific construct factory + * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) + */ + constructor( + private readonly props: AmplifyGeoFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + /* This function will perform the following actions: + 1. create an instance of the L3 construct created for Geo (AmplifyGeo) + 2. call the defineGeoAccess() function with access permissions for all three resources + */ + generateContainerEntry = ({ + // construct-related activities + scope, + }: GenerateContainerEntryProps) => { + const amplifyGeo = new AmplifyGeo(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + // creating a CDK lookup tag + Tags.of(amplifyGeo).add(TagName.FRIENDLY_NAME, this.props.name); + + // CLOUDFORMATION CONSTRUCT RESOURCES WILL HAVE ACCESS ORCHESTRATION HERE + if (this.props.resourceIdentifier === 'collection') { + // only limited to collections currently + const geoAccessOrchestrator = + this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + ); + + // initializing orchestration process + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyGeo.resources.collection.geofenceCollectionArn, + ); + } + + return amplifyGeo; + }; +} + +// export defineX() functions + +/** + * Include a map within your Amplify backend. + */ +export const defineMap = ( + // doesn't return anything because it only configures access + props: AmplifyGeoFactoryProps, +) => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'map', + }); + +/** + * Include a place index within your Amplify backend. + */ +export const definePlace = ( + // doesn't return anything because it only configures access + props: AmplifyGeoFactoryProps, +) => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'place', + }); + +/** + * Include a geofence collection within your Amplify backend. + */ +export const defineCollection = ( + // returns resources and any stack errors + props: AmplifyGeoFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'collection', + }); // should this be called AmplifyCollectionFactory? diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts new file mode 100644 index 00000000000..3b019ab5208 --- /dev/null +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -0,0 +1,90 @@ +import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { + GeoAccessBuilder, + GeoAccessGenerator, + GeoResourceType, +} from './types.js'; +import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; +import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +// this file is responsible for implementing the following: +// 1. access orchestrator for geo +/** + * Access Orchestrator for Amplify Geo + * + * Configures access permissions to associate them with roles. + */ +export class GeoAccessOrchestrator { + /** + * Constructs an instance of GeoAccessOrchestrator + * @param geoAccessGenerator - access permissions defined by user for the resource + * @param getInstanceProps - instance properties of a specific construct factory + * @param geoPolicyFactory - instance of GeoAccessPolicyFactory to generate policyStatements + * @param roleAccessBuilder - permission reader and processor + */ + constructor( + private readonly geoAccessGenerator: GeoAccessGenerator, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoPolicyFactory: GeoAccessPolicyFactory = new GeoAccessPolicyFactory(), + private readonly roleAccessBuilder: GeoAccessBuilder = _roleAccessBuilder, + ) {} + + /** + * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. + * + * The high level steps are: + * 1. Invokes the geoAccessGenerator to produce a storageAccessDefinition + * 3. Organizes the storageAccessDefinition into internally managed maps to facilitate translation into allow / deny rules on IAM policies + * 4. Invokes the policy generator to produce a policy with appropriate allow / deny rules + * 5. Invokes the resourceAccessAcceptors for each entry in the geoAccessDefinition to accept the corresponding IAM policy + */ + orchestrateGeoAccess = (resourceArn: string) => { + // getting access definitions from allow calls + const geoAccessDefinitions = this.geoAccessGenerator( + this.roleAccessBuilder, + ); + + geoAccessDefinitions.forEach((definition) => { + // get all user roles for each definition + const uniqueRoleTokenSet = new Set(); + + definition.uniqueDefinitionValidators.forEach( + ({ uniqueRoleToken, validationErrorOptions }) => { + if (uniqueRoleTokenSet.has(uniqueRoleToken)) { + throw new AmplifyUserError( + 'InvalidGeoAccessDefinitionError', + validationErrorOptions, + ); + } else { + uniqueRoleTokenSet.add(uniqueRoleToken); + } + }, + ); + + definition.userRoles.forEach((user) => { + this.geoPolicyFactory.attachPolicy( + // attaching policy statement to principal policy of each user Role provided within access definition + user(this.getInstanceProps), + this.geoPolicyFactory.createPolicyStatement( + definition.actions, + resourceArn, + ), // creating a policy statement for all actions provided within definition + ); + }); + }); + }; +} + +// needed for test mocking +/** + * Instance Manager for Geo Access Orchestration + */ +export class GeoAccessOrchestratorFactory { + private readonly resourceType: GeoResourceType; + + getInstance = ( + geoAccessGenerator: GeoAccessGenerator, + getInstanceProps: ConstructFactoryGetInstanceProps, + ) => new GeoAccessOrchestrator(geoAccessGenerator, getInstanceProps); +} diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts new file mode 100644 index 00000000000..1e0f081623a --- /dev/null +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -0,0 +1,62 @@ +import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { GeoAction } from './types.js'; +import { AmplifyFault } from '@aws-amplify/platform-core'; + +/** + * Geo Access Policy Factory + * + * Responsible for policy statement generation and policy-role attachment. + */ +export class GeoAccessPolicyFactory { + // creating a singular IAM policy + createPolicyStatement = ( + permissions: GeoAction[], // organize create policy such that one resource type maps to the actions + resourceArn: string, + ): PolicyStatement => { + if (permissions.length === 0) { + throw new AmplifyFault('EmptyPolicyFault', { + message: 'At least one permission must be specified', + }); + } + + // policy statements created for each resource type? + const policyStatement: PolicyStatement = new PolicyStatement(); + + permissions.forEach((action) => { + policyStatement.addActions(...actionDirectory[action]); + }); + + policyStatement.addResources(resourceArn); + + return policyStatement; // returns policy statement with all policies + }; + + attachPolicy = (userRole: IRole, statement: PolicyStatement) => + userRole.addToPrincipalPolicy(statement); +} + +const actionDirectory: Record = { + get: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + autocomplete: ['geo-places:Autocomplete'], + geocode: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], + search: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ], + create: ['geo:CreateGeofenceCollection'], + read: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + update: [ + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + delete: ['geo:BatchDeleteGeofence', 'geo:DeleteGeofenceCollection'], + list: ['geo:ListGeofences', 'geo:ListGeofenceCollections'], +}; diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts new file mode 100644 index 00000000000..82e76679859 --- /dev/null +++ b/packages/backend-geo/src/index.ts @@ -0,0 +1,7 @@ +export { + defineMap, + definePlace, + defineCollection, + AmplifyGeoFactory, +} from './factory.js'; +export * from './types.js'; diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts new file mode 100644 index 00000000000..da8fb917434 --- /dev/null +++ b/packages/backend-geo/src/types.ts @@ -0,0 +1,80 @@ +import { + BackendOutputStorageStrategy, + ConstructFactoryGetInstanceProps, +} from '@aws-amplify/plugin-types'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { + GeofenceCollection, + GeofenceCollectionProps, +} from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; + +// ----------------------------------- factory properties ---------------------------------------------- + +// factory properties include construct properties without output strategy (because that's loaded inside factory) +export type AmplifyGeoFactoryProps = Omit< + AmplifyGeoProps, + 'outputStorageStrategy' +> & { + region: string; + access: GeoAccessGenerator; + resourceIdentifier?: GeoResourceType; +}; + +export type AmplifyGeoProps = { + name: string; + collectionProps?: GeofenceCollectionProps; + + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +export type GeoResources = { + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; + +// ----------------------------------- access definitions ---------------------------------------------- + +export type GeoAccessGenerator = ( + allow: GeoAccessBuilder, +) => GeoAccessDefinition[]; + +export type GeoAccessBuilder = { + authenticated: GeoActionBuilder; + guest: GeoActionBuilder; + groups: (groupNames: string[]) => GeoActionBuilder; +}; + +export type GeoActionBuilder = { + // access builder (within defineX()) + to: (actions: GeoAction[]) => GeoAccessDefinition; +}; + +export type GeoAccessDefinition = { + userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; + actions: GeoAction[]; + uniqueDefinitionValidators: { + uniqueRoleToken: string; + validationErrorOptions: AmplifyUserErrorOptions; + }[]; +}; + +// ----------------------------------- misc. types ---------------------------------------------- + +export type MapAction = 'get'; + +export type IndexAction = 'autocomplete' | 'geocode' | 'search'; + +export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; + +export type GeoAction = MapAction | IndexAction | CollectionAction; + +export const geoCfnResourceTypes = ['collection']; + +export const geoManagedResourceTypes = ['map', 'place']; + +export type GeoResourceType = 'map' | 'place' | 'collection'; diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json new file mode 100644 index 00000000000..2aab102e9b4 --- /dev/null +++ b/packages/backend-geo/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "references": [] +} diff --git a/packages/backend-geo/typedoc.json b/packages/backend-geo/typedoc.json new file mode 100644 index 00000000000..35fed2c958c --- /dev/null +++ b/packages/backend-geo/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} From d0d92bfc72b819d520c8170d595e7934f0583f5e Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 16:01:14 -0700 Subject: [PATCH 07/93] updating API and exposed endpoints --- packages/backend-geo/API.md | 23 +++-------------------- packages/backend-geo/src/factory.ts | 12 +++++------- packages/backend-geo/src/index.ts | 7 +------ packages/backend-geo/tsconfig.json | 5 ++++- 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 5c6feff41d0..ad142371e83 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -4,32 +4,18 @@ ```ts -import { AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { Construct } from 'constructs'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { IRole } from 'aws-cdk-lib/aws-iam'; -import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { ResourceProvider } from '@aws-amplify/plugin-types'; -import { Stack } from 'aws-cdk-lib'; import { StackProvider } from '@aws-amplify/plugin-types'; -// @public -export class AmplifyGeoFactory implements ConstructFactory> { - // Warning: (ae-forgotten-export) The symbol "GeoAccessOrchestratorFactory" needs to be exported by the entry point index.d.ts - constructor(props: AmplifyGeoFactoryProps, geoAccessOrchestratorFactory?: GeoAccessOrchestratorFactory); - // Warning: (ae-forgotten-export) The symbol "AmplifyGeo" needs to be exported by the entry point index.d.ts - // - // (undocumented) - getInstance: (getInstanceProps: ConstructFactoryGetInstanceProps) => AmplifyGeo; -} - // @public (undocumented) export type AmplifyGeoFactoryProps = Omit & { region: string; @@ -48,16 +34,13 @@ export type AmplifyGeoProps = { export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; // @public -export const defineCollection: (// returns resources and any stack errors -props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export const defineCollection: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public -export const defineMap: (// doesn't return anything because it only configures access -props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; +export const defineMap: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public -export const definePlace: (// doesn't return anything because it only configures access -props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; +export const definePlace: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public (undocumented) export type GeoAccessBuilder = { diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts index 4bef73ec83c..fe6e0a06582 100644 --- a/packages/backend-geo/src/factory.ts +++ b/packages/backend-geo/src/factory.ts @@ -24,17 +24,15 @@ export class AmplifyGeoFactory implements ConstructFactory> { private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); // define constructor for class /** * Constructs a new AmplifyGeoFactory instance * @param props - properties for the geo factory - * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) */ - constructor( - private readonly props: AmplifyGeoFactoryProps, - private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), - ) {} + constructor(private readonly props: AmplifyGeoFactoryProps) {} // define GetInstance function getInstance = ( @@ -135,7 +133,7 @@ export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { export const defineMap = ( // doesn't return anything because it only configures access props: AmplifyGeoFactoryProps, -) => +): ConstructFactory & StackProvider> => new AmplifyGeoFactory({ ...props, resourceIdentifier: 'map', @@ -147,7 +145,7 @@ export const defineMap = ( export const definePlace = ( // doesn't return anything because it only configures access props: AmplifyGeoFactoryProps, -) => +): ConstructFactory & StackProvider> => new AmplifyGeoFactory({ ...props, resourceIdentifier: 'place', diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 82e76679859..8c575b470e0 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,7 +1,2 @@ -export { - defineMap, - definePlace, - defineCollection, - AmplifyGeoFactory, -} from './factory.js'; +export { defineMap, definePlace, defineCollection } from './factory.js'; export * from './types.js'; diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json index 2aab102e9b4..6dd4b4b566a 100644 --- a/packages/backend-geo/tsconfig.json +++ b/packages/backend-geo/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "src", "outDir": "lib" }, - "references": [] + "references": [ + { "path": "../backend-output-schemas" }, + { "path": "../platform-core" } + ] } From c438aaf11c5e5ab065392bd3f1464b97e199f1f5 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sun, 20 Jul 2025 12:42:41 -0700 Subject: [PATCH 08/93] adding output definition --- packages/backend-geo/src/construct.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts index 7c0b1cbf986..5b375a7ca3d 100644 --- a/packages/backend-geo/src/construct.ts +++ b/packages/backend-geo/src/construct.ts @@ -7,6 +7,7 @@ import { GeofenceCollectionProps, } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { geoOutputKey } from '@aws-amplify/backend-output-schemas'; /** * Amplify Geo CDK Construct @@ -51,6 +52,15 @@ export class AmplifyGeo ) as CfnGeofenceCollection, // getting L1 child instance }, }; + + props.outputStorageStrategy?.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: { + defaultCollection: this.resources.collection.geofenceCollectionName, + geoRegion: this.stack.region, + collections: `["${this.resources.collection.geofenceCollectionName}"]`, + }, + }); } } } From 59e43019e74ed2fd21d814ba4e32f6be78658ff0 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 23 Jul 2025 10:05:49 -0700 Subject: [PATCH 09/93] split APi definition and outputs aspects defined --- packages/backend-geo/src/access_builder.ts | 54 +++--- .../backend-geo/src/collection_construct.ts | 53 ++++++ .../backend-geo/src/collection_factory.ts | 109 ++++++++++++ packages/backend-geo/src/construct.ts | 66 ------- packages/backend-geo/src/factory.ts | 164 ------------------ .../src/geo_access_orchestrator.ts | 82 ++++++--- .../src/geo_access_policy_factory.ts | 23 +-- .../backend-geo/src/geo_outputs_aspect.ts | 152 ++++++++++++++++ packages/backend-geo/src/index.ts | 2 +- packages/backend-geo/src/map_factory.ts | 115 ++++++++++++ packages/backend-geo/src/map_resource.ts | 33 ++++ packages/backend-geo/src/place_factory.ts | 119 +++++++++++++ packages/backend-geo/src/place_resource.ts | 33 ++++ packages/backend-geo/src/types.ts | 126 +++++++++++--- packages/backend-output-schemas/src/geo/v1.ts | 10 +- 15 files changed, 815 insertions(+), 326 deletions(-) create mode 100644 packages/backend-geo/src/collection_construct.ts create mode 100644 packages/backend-geo/src/collection_factory.ts delete mode 100644 packages/backend-geo/src/construct.ts delete mode 100644 packages/backend-geo/src/factory.ts create mode 100644 packages/backend-geo/src/geo_outputs_aspect.ts create mode 100644 packages/backend-geo/src/map_factory.ts create mode 100644 packages/backend-geo/src/map_resource.ts create mode 100644 packages/backend-geo/src/place_factory.ts create mode 100644 packages/backend-geo/src/place_resource.ts diff --git a/packages/backend-geo/src/access_builder.ts b/packages/backend-geo/src/access_builder.ts index a07d0b95b94..75c3f522003 100644 --- a/packages/backend-geo/src/access_builder.ts +++ b/packages/backend-geo/src/access_builder.ts @@ -1,20 +1,16 @@ import { - AuthResources, AuthRoleName, ConstructFactoryGetInstanceProps, ResourceAccessAcceptorFactory, ResourceProvider, } from '@aws-amplify/plugin-types'; -import { IRole } from 'aws-cdk-lib/aws-iam'; import { GeoAccessBuilder } from './types.js'; -export type Roles = IRole; - export const roleAccessBuilder: GeoAccessBuilder = { authenticated: { // access for authenticated users to: (actions) => ({ - userRoles: [getAuthRole], + getAccessAcceptors: [getAuthRoleAcceptor], actions, uniqueDefinitionValidators: [ { @@ -30,11 +26,11 @@ export const roleAccessBuilder: GeoAccessBuilder = { guest: { // access for guest users to: (actions) => ({ - userRoles: [getUnauthRole], + getAccessAcceptors: [getUnauthRoleAcceptor], actions, uniqueDefinitionValidators: [ { - uniqueRoleToken: `guest`, + uniqueRoleToken: 'guest', validationErrorOptions: { message: `Access definition for guest users specified multiple times.`, resolution: `Combine all access definitions for guest users into one access rule.`, @@ -46,60 +42,52 @@ export const roleAccessBuilder: GeoAccessBuilder = { groups: (groupNames) => ({ // access for user groups to: (actions) => ({ - userRoles: groupNames.map( + getAccessAcceptors: groupNames.map( // for each group in the user groups (groupName) => (getInstanceProps) => - getUserRole(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) + getUserRoleAcceptor(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) ), - actions, uniqueDefinitionValidators: groupNames.map((groupName) => ({ - uniqueRoleToken: `${groupName}`, + uniqueRoleToken: `group-${groupName}`, validationErrorOptions: { message: `Access definition for the group ${groupName} specified multiple times.`, resolution: `Combine all access definitions for the group ${groupName} into one access rule.`, }, })), + actions, }), }), }; -const getAuthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => - getUserRole(getInstanceProps, 'authenticatedUserIamRole'); +const getAuthRoleAcceptor = ( + getInstanceProps: ConstructFactoryGetInstanceProps, +) => getUserRoleAcceptor(getInstanceProps, 'authenticatedUserIamRole'); -const getUnauthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => - getUserRole(getInstanceProps, 'unauthenticatedUserIamRole'); +const getUnauthRoleAcceptor = ( + getInstanceProps: ConstructFactoryGetInstanceProps, +) => getUserRoleAcceptor(getInstanceProps, 'unauthenticatedUserIamRole'); // getting acceptor objects for different role types (defined in auth) -const getUserRole = ( +const getUserRoleAcceptor = ( getInstanceProps: ConstructFactoryGetInstanceProps, // instance properties of the auth factory? roleName: AuthRoleName | string, // name of role to get acceptors from -): IRole => { - const authResources = getInstanceProps.constructContainer +) => { + const resourceAccessAcceptor = getInstanceProps.constructContainer .getConstructFactory< ResourceProvider & ResourceAccessAcceptorFactory >( // getting construct container to look for a specific construct factory 'AuthResources', ) - ?.getInstance(getInstanceProps).resources as AuthResources; // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors - if (!authResources) { + ?.getInstance(getInstanceProps) + .getResourceAccessAcceptor(roleName); // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors + + if (!resourceAccessAcceptor) { throw new Error( `Cannot specify geo resource access for ${ roleName as string } users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.`, ); - } else { - if (roleName === authResources.authenticatedUserIamRole.roleName) { - return authResources.authenticatedUserIamRole; - } else if (roleName === authResources.unauthenticatedUserIamRole.roleName) { - return authResources.unauthenticatedUserIamRole; - } else if (authResources.groups[roleName]) { - return authResources.groups[roleName].role; - } - throw new Error( - `Role: ${ - roleName as string - } does not exist. See https://console.aws.amazon.com/iam/ to define the role.`, - ); } + return resourceAccessAcceptor; }; diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts new file mode 100644 index 00000000000..fe1724eb16a --- /dev/null +++ b/packages/backend-geo/src/collection_construct.ts @@ -0,0 +1,53 @@ +import { Construct } from 'constructs'; +import { AmplifyCollectionProps, CollectionResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { Policy } from 'aws-cdk-lib/aws-iam'; + +/** + * Amplify Collection CDK Construct + * + * Provisions a Collection through `alpha` L2 Construct + */ +export class AmplifyCollection + extends Construct + implements ResourceProvider, StackProvider +{ + readonly stack: Stack; + readonly resources: CollectionResources; + readonly name: string; + readonly id: string; + readonly isDefault: boolean; + readonly policies: Policy[]; + + /** + * Creates an instance of AmplifyCollection construct + * @param scope - CDK stack where the construct should provision resources + * @param id - CDK ID of Geofence Collection + * @param props - properties of AmplifyCollection + */ + constructor(scope: Construct, id: string, props: AmplifyCollectionProps) { + super(scope, id); + + this.name = props.name; + this.id = id; + this.isDefault = props.isDefault || false; + + const geofenceCollection = new GeofenceCollection( + this, + id, + props.collectionProps, + ); + this.resources = { + collection: geofenceCollection, + policies: this.policies, + cfnResources: { + cfnCollection: geofenceCollection.node.findChild( + 'Resource', + ) as CfnGeofenceCollection, + }, + }; + } +} diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts new file mode 100644 index 00000000000..65c92853a51 --- /dev/null +++ b/packages/backend-geo/src/collection_factory.ts @@ -0,0 +1,109 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyCollectionFactoryProps, CollectionResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyCollection } from './collection_construct.js'; +import { TagName } from '@aws-amplify/platform-core'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct factory for AmplifyCollection + */ +export class AmplifyCollectionFactory + implements ConstructFactory> +{ + private collectionGenerator: AmplifyCollectionGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Creates an instance of AmplifyCollectionFactory + * @param props - collection construct properties + */ + constructor(private readonly props: AmplifyCollectionFactoryProps) {} + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyCollection => { + const { constructContainer, resourceNameValidator } = getInstanceProps; + + resourceNameValidator?.validate(this.props.name); + + if (!this.collectionGenerator) { + this.collectionGenerator = new AmplifyCollectionGenerator( + this.props, + getInstanceProps, + ); + } + + return constructContainer.getOrCompute( + this.collectionGenerator, + ) as AmplifyCollection; + }; +} + +/** + * Construct Container Entry Generator for AmplifyCollection + */ +export class AmplifyCollectionGenerator + implements ConstructContainerEntryGenerator +{ + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of the AmplifyCollectionGenerator + */ + constructor( + private readonly props: AmplifyCollectionFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyCollection = new AmplifyCollection(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyCollection).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyCollection.resources.policies = + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyCollection.resources.collection.geofenceCollectionArn, + 'collection', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyCollection)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyCollection; + }; +} + +/** + * Provision a geofence collection within your Amplify backend. + * @see https://docs.amplify.aws/react/build-a-backend/add-aws-services/geo/configure-geofencing/ + */ +export const defineCollection = ( + props: AmplifyCollectionFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyCollectionFactory(props); diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts deleted file mode 100644 index 5b375a7ca3d..00000000000 --- a/packages/backend-geo/src/construct.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Construct } from 'constructs'; -import { AmplifyGeoProps, GeoResources } from './types.js'; -import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; -import { Stack } from 'aws-cdk-lib'; -import { - GeofenceCollection, - GeofenceCollectionProps, -} from '@aws-cdk/aws-location-alpha'; -import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { geoOutputKey } from '@aws-amplify/backend-output-schemas'; - -/** - * Amplify Geo CDK Construct - * - * Designed as a wrapper around a Geofence Collection provided by Amazon Location Services. - */ -export class AmplifyGeo - extends Construct - implements ResourceProvider, StackProvider -{ - readonly stack: Stack; // current stack - readonly resources: GeoResources; - readonly name: string; // construct name - readonly id: string; - - /** - * Constructs a new AmplifyGeo instance - */ - constructor(scope: Construct, id: string, props: AmplifyGeoProps) { - super(scope, id); // call to Construct class as part of inheritance - this.name = props.name; - this.id = id; - if (props.collectionProps) { - const geofenceCollectionProps: GeofenceCollectionProps = { - // property mapping - geofenceCollectionName: props.collectionProps.geofenceCollectionName, - description: props.collectionProps.description, - kmsKey: props.collectionProps.kmsKey, - }; - - const geofenceCollection = new GeofenceCollection( - this, - id, - geofenceCollectionProps, - ); // L2 construct call - - this.resources = { - collection: geofenceCollection, - cfnResources: { - cfnCollection: geofenceCollection.node.findChild( - 'Resource', - ) as CfnGeofenceCollection, // getting L1 child instance - }, - }; - - props.outputStorageStrategy?.addBackendOutputEntry(geoOutputKey, { - version: '1', - payload: { - defaultCollection: this.resources.collection.geofenceCollectionName, - geoRegion: this.stack.region, - collections: `["${this.resources.collection.geofenceCollectionName}"]`, - }, - }); - } - } -} diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts deleted file mode 100644 index fe6e0a06582..00000000000 --- a/packages/backend-geo/src/factory.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - AmplifyResourceGroupName, - ConstructContainerEntryGenerator, - ConstructFactory, - ConstructFactoryGetInstanceProps, - GenerateContainerEntryProps, - ResourceProvider, - StackProvider, -} from '@aws-amplify/plugin-types'; -import { Aws } from 'aws-cdk-lib/core'; -import { AmplifyGeoFactoryProps, GeoResources } from './types.js'; -import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; -import { AmplifyGeo } from './construct.js'; -import { Tags } from 'aws-cdk-lib'; -import { TagName } from '@aws-amplify/platform-core'; - -// define factory class -/** - * Amplify Geo Construct Factory - * - * Designed to manage the initialization of AmplifyGeo construct among other constructs and construct-agnostic resource access orchestration. - */ -export class AmplifyGeoFactory - implements ConstructFactory> -{ - private geoGenerator: ConstructContainerEntryGenerator; - private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = - new GeoAccessOrchestratorFactory(); - - // define constructor for class - /** - * Constructs a new AmplifyGeoFactory instance - * @param props - properties for the geo factory - */ - constructor(private readonly props: AmplifyGeoFactoryProps) {} - - // define GetInstance function - getInstance = ( - getInstanceProps: ConstructFactoryGetInstanceProps, - ): AmplifyGeo => { - // get construct factory instance properties - const { constructContainer, resourceNameValidator } = getInstanceProps; - - // validates the user-entered resource name (according to CDK naming regulations) - resourceNameValidator?.validate(this.props.name); - - // AWS-MANAGED RESOURCE ACCESS ORCHESTRATION HERE - if (this.props.resourceIdentifier !== 'collection') { - // only limited to collections currently - const geoAccessOrchestrator = - this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - getInstanceProps, - ); - - // access orchestration for non-collection resources done here - geoAccessOrchestrator.orchestrateGeoAccess( - `arn:${Aws.PARTITION}:geo-${this.props.resourceIdentifier}:${this.props.region}::provider/default`, - ); - } - - // generates a singleton container entry for this construct factory - if (!this.geoGenerator) { - this.geoGenerator = new AmplifyGeoGenerator(this.props, getInstanceProps); - } - - // this getOrCompute accesses the internal construct container cache - return constructContainer.getOrCompute(this.geoGenerator) as AmplifyGeo; - }; -} - -// define generator class -/** - * Amplify Geo Construct Generator - * - * Designed to manage the generation of the Amplify Geo construct instance along with resource access orchestration. - */ -export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { - readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; - - /** - * Constructs an AmplifyGeoGenerator instance - * @param props - properties for the Amplify Geo Factory - * @param getInstanceProps - instance properties of a specific construct factory - * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) - */ - constructor( - private readonly props: AmplifyGeoFactoryProps, - private readonly getInstanceProps: ConstructFactoryGetInstanceProps, - private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), - ) {} - - /* This function will perform the following actions: - 1. create an instance of the L3 construct created for Geo (AmplifyGeo) - 2. call the defineGeoAccess() function with access permissions for all three resources - */ - generateContainerEntry = ({ - // construct-related activities - scope, - }: GenerateContainerEntryProps) => { - const amplifyGeo = new AmplifyGeo(scope, this.props.name, { - ...this.props, - outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, - }); - - // creating a CDK lookup tag - Tags.of(amplifyGeo).add(TagName.FRIENDLY_NAME, this.props.name); - - // CLOUDFORMATION CONSTRUCT RESOURCES WILL HAVE ACCESS ORCHESTRATION HERE - if (this.props.resourceIdentifier === 'collection') { - // only limited to collections currently - const geoAccessOrchestrator = - this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - ); - - // initializing orchestration process - geoAccessOrchestrator.orchestrateGeoAccess( - amplifyGeo.resources.collection.geofenceCollectionArn, - ); - } - - return amplifyGeo; - }; -} - -// export defineX() functions - -/** - * Include a map within your Amplify backend. - */ -export const defineMap = ( - // doesn't return anything because it only configures access - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'map', - }); - -/** - * Include a place index within your Amplify backend. - */ -export const definePlace = ( - // doesn't return anything because it only configures access - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'place', - }); - -/** - * Include a geofence collection within your Amplify backend. - */ -export const defineCollection = ( - // returns resources and any stack errors - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'collection', - }); // should this be called AmplifyCollectionFactory? diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 3b019ab5208..5ab49e6c674 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -1,12 +1,17 @@ -import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { + ConstructFactoryGetInstanceProps, + SsmEnvironmentEntry, +} from '@aws-amplify/plugin-types'; import { GeoAccessBuilder, GeoAccessGenerator, - GeoResourceType, + resourceActionRecord, } from './types.js'; import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { Policy } from 'aws-cdk-lib/aws-iam'; +import { Stack } from 'aws-cdk-lib'; // this file is responsible for implementing the following: // 1. access orchestrator for geo @@ -16,30 +21,36 @@ import { AmplifyUserError } from '@aws-amplify/platform-core'; * Configures access permissions to associate them with roles. */ export class GeoAccessOrchestrator { + private resourceStack: Stack; + private policies: Policy[] = []; /** * Constructs an instance of GeoAccessOrchestrator * @param geoAccessGenerator - access permissions defined by user for the resource * @param getInstanceProps - instance properties of a specific construct factory - * @param geoPolicyFactory - instance of GeoAccessPolicyFactory to generate policyStatements - * @param roleAccessBuilder - permission reader and processor + * @param geoStack - instance of GeoAccessPolicyFactory to generate policyStatements + * @param ssmEnvironmentEntries - permission reader and processor + * @param geoPolicyFactory - + * @param roleAccessBuilder - */ constructor( private readonly geoAccessGenerator: GeoAccessGenerator, private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoStack: Stack, + private readonly ssmEnvironmentEntries: SsmEnvironmentEntry[], private readonly geoPolicyFactory: GeoAccessPolicyFactory = new GeoAccessPolicyFactory(), private readonly roleAccessBuilder: GeoAccessBuilder = _roleAccessBuilder, - ) {} + ) { + this.resourceStack = geoStack; + } /** * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. * - * The high level steps are: - * 1. Invokes the geoAccessGenerator to produce a storageAccessDefinition - * 3. Organizes the storageAccessDefinition into internally managed maps to facilitate translation into allow / deny rules on IAM policies - * 4. Invokes the policy generator to produce a policy with appropriate allow / deny rules - * 5. Invokes the resourceAccessAcceptors for each entry in the geoAccessDefinition to accept the corresponding IAM policy */ - orchestrateGeoAccess = (resourceArn: string) => { + orchestrateGeoAccess = ( + resourceArn: string, + resourceIdentifier: string, + ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( this.roleAccessBuilder, @@ -62,17 +73,38 @@ export class GeoAccessOrchestrator { }, ); - definition.userRoles.forEach((user) => { - this.geoPolicyFactory.attachPolicy( - // attaching policy statement to principal policy of each user Role provided within access definition - user(this.getInstanceProps), - this.geoPolicyFactory.createPolicyStatement( - definition.actions, - resourceArn, - ), // creating a policy statement for all actions provided within definition + // checking for valid actions for resource type + definition.actions.forEach((action) => { + if (!resourceActionRecord[resourceIdentifier].includes(action)) { + throw new AmplifyUserError('ActionNotFoundError', { + message: `Desired access action not found for the specific ${resourceIdentifier} resource.`, + resolution: `Please refer to specific ${resourceIdentifier} access actions for more information.`, + }); + } + }); + + const roleTokens = Array.from(uniqueRoleTokenSet); + + let roleIndex: number = 0; // need respective roleToken for policy generation + definition.getAccessAcceptors.forEach((acceptor) => { + // for each acceptor within auth, guest, or user groups + + const policy: Policy = this.geoPolicyFactory.createPolicy( + definition.actions, + resourceArn, + roleTokens[roleIndex], + this.resourceStack, ); + acceptor(this.getInstanceProps).acceptResourceAccess( + policy, + this.ssmEnvironmentEntries, + ); + this.policies.push(policy); + roleIndex += 1; }); }); + + return this.policies; }; } @@ -81,10 +113,16 @@ export class GeoAccessOrchestrator { * Instance Manager for Geo Access Orchestration */ export class GeoAccessOrchestratorFactory { - private readonly resourceType: GeoResourceType; - getInstance = ( geoAccessGenerator: GeoAccessGenerator, getInstanceProps: ConstructFactoryGetInstanceProps, - ) => new GeoAccessOrchestrator(geoAccessGenerator, getInstanceProps); + stack: Stack, + ssmEnvironmentEntries: SsmEnvironmentEntry[], + ) => + new GeoAccessOrchestrator( + geoAccessGenerator, + getInstanceProps, + stack, + ssmEnvironmentEntries, + ); } diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 1e0f081623a..5cc305395b1 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -1,6 +1,6 @@ -import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { GeoAction } from './types.js'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { AmplifyFault } from '@aws-amplify/platform-core'; +import { Stack } from 'aws-cdk-lib'; /** * Geo Access Policy Factory @@ -8,11 +8,12 @@ import { AmplifyFault } from '@aws-amplify/platform-core'; * Responsible for policy statement generation and policy-role attachment. */ export class GeoAccessPolicyFactory { - // creating a singular IAM policy - createPolicyStatement = ( - permissions: GeoAction[], // organize create policy such that one resource type maps to the actions + createPolicy = ( + permissions: string[], // organize create policy such that one resource type maps to the actions resourceArn: string, - ): PolicyStatement => { + roleToken: string, + stack: Stack, + ) => { if (permissions.length === 0) { throw new AmplifyFault('EmptyPolicyFault', { message: 'At least one permission must be specified', @@ -28,14 +29,14 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - return policyStatement; // returns policy statement with all policies + return new Policy(stack, `geo-access-policy`, { + policyName: `geo-${roleToken}-access-policy`, + statements: [policyStatement], + }); // returns policy with policy statement of all actions }; - - attachPolicy = (userRole: IRole, statement: PolicyStatement) => - userRole.addToPrincipalPolicy(statement); } -const actionDirectory: Record = { +const actionDirectory: Record = { get: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], autocomplete: ['geo-places:Autocomplete'], geocode: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts new file mode 100644 index 00000000000..4a4275e8403 --- /dev/null +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -0,0 +1,152 @@ +import { IAspect, Stack } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; +import { AmplifyCollection } from './collection_construct.js'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyPlace } from './place_resource.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; + +/** + * Aspect Implementation for Geo Resources + */ +export class AmplifyGeoOutputsAspect implements IAspect { + /** + * Steps to be accomplished within this class: + * 1. constructor setup (receives output strategy -> schema) + * 2. default collection processing (multiplicity error handling) + * 3. store the outputs for all collections within outputStorageStrategy + */ + isGeoOutputProcessed: boolean = false; + defaultCollectionName: string | undefined = undefined; + private readonly geoOutputStorageStrategy: BackendOutputStorageStrategy; + /** + * Constructs an instance of the AmplifyGeoOutputsAspect + * @param outputStorageStrategy - storage schema for Geo outputs + */ + constructor(outputStorageStrategy: BackendOutputStorageStrategy) { + this.geoOutputStorageStrategy = outputStorageStrategy; + } + + /** + * Interface requirement of IAspect that is called during CDK synthesis time + * @param node - current construct + */ + public visit(node: IConstruct): void { + if ( + !(node instanceof AmplifyMap) || + !(node instanceof AmplifyPlace) || + !(node instanceof AmplifyCollection) || + this.isGeoOutputProcessed + ) { + return; + } + + this.isGeoOutputProcessed = true; // once this is visited, this no longer remains false + + const mapInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyMap, + ) as AmplifyMap[]; + + const placeInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyPlace, + ) as AmplifyPlace[]; + + const collectionInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyCollection, + ) as AmplifyCollection[]; + + if ( + mapInstances.length > 0 || + placeInstances.length > 0 || + collectionInstances.length > 0 + ) { + this.addBackendOutput( + collectionInstances, + this.geoOutputStorageStrategy, + Stack.of(node).region, + ); + } + } + + private findDefaultCollectionName = ( + nodes: AmplifyCollection[], + currentNode: AmplifyCollection, + ): string | undefined => { + const geoCount = nodes.length; + + let defaultCollectionName: string | undefined = undefined; + + // go through all children and find the default (make duplicity check on defaults) + nodes.forEach((instance) => { + if (!defaultCollectionName && instance.isDefault) { + // if no default exists and instance is default, mark it + defaultCollectionName = + instance.resources.collection?.geofenceCollectionName; + } else if (instance.isDefault && defaultCollectionName) { + // if default exists and instance is default (throw multiple defaults error) + throw new AmplifyUserError('MultipleDefaultCollectionError', { + message: + 'Multiple instances of geofence collections have been marked as default.', + resolution: + 'Remove `isDefault: true` from all but one `defineCollection` call.', + }); + } + }); + + if (geoCount === 1 && !defaultCollectionName) { + // if no defaults and only one construct, instance assumed to be default + defaultCollectionName = + currentNode.resources.collection?.geofenceCollectionName; + } else if (geoCount > 1 && !defaultCollectionName) { + // if multiple constructs with default collection, throw error + throw new AmplifyUserError('NoDefaultCollectionError', { + message: + 'No instances of geofence collections have been marked as default.', + resolution: + 'Add `isDefault: true` to one of the `defineCollection` calls.', + }); + } + + return defaultCollectionName; + }; + + /** + * Function responsible for add all collection outputs (with defaults) + * @param collections - all construct instances of AmplifyGeo + * @param outputStorageStrategy - backend output schema of type GeoOutput + * @param region - + */ + private addBackendOutput( + collections: AmplifyCollection[], + outputStorageStrategy: BackendOutputStorageStrategy, + region: string, + ) { + const defaultCollectionName: string | undefined = + this.findDefaultCollectionName(collections, collections[0]); + + outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: { + aws_region: region, + geofence_collections: JSON.stringify({ + default: defaultCollectionName, + items: defaultCollectionName, + }), + }, + }); + + collections.forEach((collection) => { + if (!collection.isDefault) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofence_collections: JSON.stringify({ + items: collection.resources.collection.geofenceCollectionName, + }), + }, + }); + } + }); + } +} diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 8c575b470e0..9237d106c6d 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,2 +1,2 @@ -export { defineMap, definePlace, defineCollection } from './factory.js'; +export { defineCollection } from './collection_factory.js'; export * from './types.js'; diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts new file mode 100644 index 00000000000..3d374d22cd9 --- /dev/null +++ b/packages/backend-geo/src/map_factory.ts @@ -0,0 +1,115 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyMapFactoryProps, MapResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct Factory for AmplifyMap + */ +export class AmplifyMapFactory + implements ConstructFactory> +{ + static mapCount: number = 0; + + private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Constructs a new AmplifyMapFactory instance + * @param props - map resource properties + */ + constructor(private readonly props: AmplifyMapFactoryProps) { + if (AmplifyMapFactory.mapCount > 0) { + throw new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `defineMap` calls not permitted within an Amplify backend', + resolution: 'Maintain one `defineMap` call', + }); + } + AmplifyMapFactory.mapCount++; + } + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyMap => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyMapGenerator(this.props, getInstanceProps); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyMap; + }; +} + +/** + * Construct Container Entry Generator for AmplifyMap + */ +export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of AmplifyMapGenerator + */ + constructor( + private readonly props: AmplifyMapFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyMap = new AmplifyMap(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyMap).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( + amplifyMap.getResourceArn(), + 'map', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyMap)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyMap; + }; +} + +/** + * Integrate access for an AWS-managed map within your backend. + */ +export const defineMap = ( + props: AmplifyMapFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyMapFactory(props); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts new file mode 100644 index 00000000000..7fa416e2fdc --- /dev/null +++ b/packages/backend-geo/src/map_resource.ts @@ -0,0 +1,33 @@ +import { AmplifyMapProps, MapResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Aws, Resource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +/** + * Resource for AWS-managed Maps + */ +export class AmplifyMap + extends Resource + implements ResourceProvider, StackProvider +{ + readonly resources: MapResources; + readonly id: string; + readonly name: string; + + /** + * Creates an instance of AmplifyMap + */ + constructor(scope: Construct, id: string, props: AmplifyMapProps) { + super(scope, id); + + this.name = props.name; + } + + getResourceArn = (): string => { + return `arn:${Aws.PARTITION}:geo-maps:${this.stack.region}::provider/default`; + }; + + getResourceName = (): string => { + return this.name; + }; +} diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts new file mode 100644 index 00000000000..9b5bb3ea0b4 --- /dev/null +++ b/packages/backend-geo/src/place_factory.ts @@ -0,0 +1,119 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyPlaceFactoryProps, PlaceResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; +import { AmplifyPlace } from './place_resource.js'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct Factory for AmplifyPlace + */ +export class AmplifyPlaceFactory + implements ConstructFactory> +{ + static mapCount: number = 0; + + private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Constructs a new AmplifyPlaceFactory instance + * @param props - place resource properties + */ + constructor(private readonly props: AmplifyPlaceFactoryProps) { + if (AmplifyPlaceFactory.mapCount > 0) { + throw new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `definePlace` calls not permitted within an Amplify backend', + resolution: 'Maintain one `definePlace` call', + }); + } + AmplifyPlaceFactory.mapCount++; + } + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyPlace => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyPlaceGenerator( + this.props, + getInstanceProps, + ); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyPlace; + }; +} + +/** + * Construct Container Entry Generator for AmplifyPlace + */ +export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of AmplifyPlaceGenerator + */ + constructor( + private readonly props: AmplifyPlaceFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyPlace = new AmplifyPlace(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyPlace.resources.policies = + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyPlace.getResourceArn(), + 'map', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyPlace)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyPlace; + }; +} + +/** + * Integrate access for an AWS-managed place index within your backend. + */ +export const definePlace = ( + props: AmplifyPlaceFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyPlaceFactory(props); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts new file mode 100644 index 00000000000..7dcd13dca65 --- /dev/null +++ b/packages/backend-geo/src/place_resource.ts @@ -0,0 +1,33 @@ +import { AmplifyPlaceProps, PlaceResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Aws, Resource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +/** + * Resource for AWS-managed Place Indices + */ +export class AmplifyPlace + extends Resource + implements ResourceProvider, StackProvider +{ + readonly resources: PlaceResources; + readonly id: string; + readonly name: string; + + /** + * Creates an instance of AmplifyPlace + */ + constructor(scope: Construct, id: string, props: AmplifyPlaceProps) { + super(scope, id); + + this.name = props.name; + } + + getResourceArn = (): string => { + return `arn:${Aws.PARTITION}:geo-places:${this.stack.region}::provider/default`; + }; + + getResourceName = (): string => { + return this.name; + }; +} diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index da8fb917434..34decc54b0f 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -1,6 +1,7 @@ import { BackendOutputStorageStrategy, ConstructFactoryGetInstanceProps, + ResourceAccessAcceptor, } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { @@ -8,30 +9,110 @@ import { GeofenceCollectionProps, } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { IRole } from 'aws-cdk-lib/aws-iam'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; +import { Policy } from 'aws-cdk-lib/aws-iam'; // ----------------------------------- factory properties ---------------------------------------------- -// factory properties include construct properties without output strategy (because that's loaded inside factory) -export type AmplifyGeoFactoryProps = Omit< - AmplifyGeoProps, +/** + * Properties of AmplifyMap + */ +export type AmplifyMapFactoryProps = Omit< + AmplifyMapProps, 'outputStorageStrategy' > & { - region: string; + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const map = defineMap({ + * access: (allow) => ( + * allow.authenticated.to(["get"]) + * ) + * }) + */ access: GeoAccessGenerator; - resourceIdentifier?: GeoResourceType; }; -export type AmplifyGeoProps = { +/** + * Properties of AmplifyPlace + */ +export type AmplifyPlaceFactoryProps = Omit< + AmplifyPlaceProps, + 'outputStorageStrategy' +> & { + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const index = definePlace({ + * access: (allow) => ( + * allow.authenticated.to(["geocode"]) + * ) + * }) + */ + access: GeoAccessGenerator; +}; + +/** + * Properties of AmplifyCollection + */ +export type AmplifyCollectionFactoryProps = Omit< + AmplifyCollectionProps, + 'outputStorageStrategy' +> & { + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const collection = defineCollection({ + * access: (allow) => ( + * allow.authenticated.to(["create"]) + * ) + * }) + */ + access: GeoAccessGenerator; +}; + +export type AmplifyMapProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +export type AmplifyPlaceProps = { name: string; - collectionProps?: GeofenceCollectionProps; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; +export type AmplifyCollectionProps = { + name: string; + collectionProps: GeofenceCollectionProps; + isDefault: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; -export type GeoResources = { +/** + * Backend-accessible resources from AmplifyMap + * @param policies - access policies of the frontend-accessible map resource + */ +export type MapResources = { + policies: Policy[]; +}; + +/** + * Backend-accessible resources from AmplifyPlace + * @param policies - access policies of the frontend-accessible place resource + */ +export type PlaceResources = { + policies: Policy[]; +}; + +/** + * Backend-accessible resources from AmplifyCollection + * @param collection - provisioned geofence collection resource + * @param policies - access policies of the provisioned collection resource + * @param cfnResources - cloudformation resources exposed from the abstracted collection provisioned from collection + */ +export type CollectionResources = { collection: GeofenceCollection; + policies: Policy[]; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -50,13 +131,14 @@ export type GeoAccessBuilder = { }; export type GeoActionBuilder = { - // access builder (within defineX()) - to: (actions: GeoAction[]) => GeoAccessDefinition; + to: (actions: string[]) => GeoAccessDefinition; }; export type GeoAccessDefinition = { - userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; - actions: GeoAction[]; + getAccessAcceptors: (( + getInstanceProps: ConstructFactoryGetInstanceProps, + ) => ResourceAccessAcceptor)[]; + actions: string[]; uniqueDefinitionValidators: { uniqueRoleToken: string; validationErrorOptions: AmplifyUserErrorOptions; @@ -65,16 +147,8 @@ export type GeoAccessDefinition = { // ----------------------------------- misc. types ---------------------------------------------- -export type MapAction = 'get'; - -export type IndexAction = 'autocomplete' | 'geocode' | 'search'; - -export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; - -export type GeoAction = MapAction | IndexAction | CollectionAction; - -export const geoCfnResourceTypes = ['collection']; - -export const geoManagedResourceTypes = ['map', 'place']; - -export type GeoResourceType = 'map' | 'place' | 'collection'; +export const resourceActionRecord: Record = { + map: ['get'], + place: ['autocomplete', 'geocode', 'search'], + collection: ['create', 'read', 'update', 'delete', 'list'], +}; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 2c74fc45cab..4a569fb9343 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -1,10 +1,14 @@ import { z } from 'zod'; +const collectionSchema = z.object({ + default: z.string(), + items: z.string(z.array(z.string())).optional(), +}); + export const geoOutputSchema = z.object({ version: z.literal('1'), payload: z.object({ - defaultCollection: z.string(), - geoRegion: z.string(), - collections: z.string(z.array(z.string()).optional()), // JSON serialized array of collection names + aws_region: z.string(), + geofence_collections: z.string(collectionSchema).optional(), }), }); From 20bb37bc110d7d72020b9bbad9c409aaa32d9d3e Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:29:00 -0700 Subject: [PATCH 10/93] unit testing v1 and debugging --- package-lock.json | 1 + packages/backend-geo/package.json | 1 + .../backend-geo/src/access_builder.test.ts | 290 +++++++++ .../src/collection_construct.test.ts | 284 +++++++++ .../backend-geo/src/collection_construct.ts | 11 + .../src/collection_factory.test.ts | 161 +++++ .../backend-geo/src/collection_factory.ts | 18 +- .../src/geo_access_orchestrator.test.ts | 557 ++++++++++++++++++ .../src/geo_access_orchestrator.ts | 19 +- .../src/geo_access_policy_factory.test.ts | 482 +++++++++++++++ .../src/geo_access_policy_factory.ts | 7 +- .../src/geo_outputs_aspect.test.ts | 233 ++++++++ .../backend-geo/src/geo_outputs_aspect.ts | 37 +- packages/backend-geo/src/map_factory.test.ts | 172 ++++++ packages/backend-geo/src/map_factory.ts | 14 +- packages/backend-geo/src/map_resource.test.ts | 107 ++++ packages/backend-geo/src/map_resource.ts | 11 +- .../backend-geo/src/place_factory.test.ts | 179 ++++++ packages/backend-geo/src/place_factory.ts | 26 +- .../backend-geo/src/place_resource.test.ts | 111 ++++ packages/backend-geo/src/place_resource.ts | 6 + packages/backend-geo/src/types.ts | 30 +- 22 files changed, 2685 insertions(+), 72 deletions(-) create mode 100644 packages/backend-geo/src/access_builder.test.ts create mode 100644 packages/backend-geo/src/collection_construct.test.ts create mode 100644 packages/backend-geo/src/collection_factory.test.ts create mode 100644 packages/backend-geo/src/geo_access_orchestrator.test.ts create mode 100644 packages/backend-geo/src/geo_access_policy_factory.test.ts create mode 100644 packages/backend-geo/src/geo_outputs_aspect.test.ts create mode 100644 packages/backend-geo/src/map_factory.test.ts create mode 100644 packages/backend-geo/src/map_resource.test.ts create mode 100644 packages/backend-geo/src/place_factory.test.ts create mode 100644 packages/backend-geo/src/place_resource.test.ts diff --git a/package-lock.json b/package-lock.json index 79d21f9f0f9..6c36bbec3ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49772,6 +49772,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" }, diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index 06c6879d8af..bb3219ca9f5 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } diff --git a/packages/backend-geo/src/access_builder.test.ts b/packages/backend-geo/src/access_builder.test.ts new file mode 100644 index 00000000000..88ef3387565 --- /dev/null +++ b/packages/backend-geo/src/access_builder.test.ts @@ -0,0 +1,290 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import assert from 'node:assert'; +import { roleAccessBuilder } from './access_builder.js'; +import { + ConstructContainer, + ConstructFactoryGetInstanceProps, + ResourceProvider, +} from '@aws-amplify/plugin-types'; + +void describe('GeoAccessBuilder', () => { + const resourceAccessAcceptorMock = mock.fn(); + const group1AccessAcceptorMock = mock.fn(); + const group2AccessAcceptorMock = mock.fn(); + + const getResourceAccessAcceptorMock = mock.fn((roleName: string) => { + switch (roleName) { + case 'group1Name': + return group1AccessAcceptorMock; + case 'group2Name': + return group2AccessAcceptorMock; + default: + return resourceAccessAcceptorMock; + } + }); + + const getConstructFactoryMock = mock.fn( + // this lets us get proper typing on the mock args + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_: string) => ({ + getInstance: () => + ({ + getResourceAccessAcceptor: getResourceAccessAcceptorMock, + }) as unknown as T, + }), + ); + + const mockGetInstanceProps: ConstructFactoryGetInstanceProps = { + constructContainer: { + getConstructFactory: getConstructFactoryMock, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + beforeEach(() => { + getResourceAccessAcceptorMock.mock.resetCalls(); + getConstructFactoryMock.mock.resetCalls(); + resourceAccessAcceptorMock.mock.resetCalls(); + group1AccessAcceptorMock.mock.resetCalls(); + group2AccessAcceptorMock.mock.resetCalls(); + }); + + void it('builds geo access definition for authenticated role', () => { + const accessDefinition = roleAccessBuilder.authenticated.to([ + 'get', + 'search', + ]); + + assert.deepStrictEqual(accessDefinition.actions, ['get', 'search']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [resourceAccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'authenticatedUserIamRole', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'authenticated', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for authenticated users specified multiple times.', + ); + }); + + void it('builds geo access definition for guest role', () => { + const accessDefinition = roleAccessBuilder.guest.to(['get']); + + assert.deepStrictEqual(accessDefinition.actions, ['get']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [resourceAccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'unauthenticatedUserIamRole', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'guest', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for guest users specified multiple times.', + ); + }); + + void it('builds geo access definition for single user pool group', () => { + const accessDefinition = roleAccessBuilder + .groups(['group1Name']) + .to(['create', 'read']); + + assert.deepStrictEqual(accessDefinition.actions, ['create', 'read']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [group1AccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'group1Name', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'group-group1Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for the group group1Name specified multiple times.', + ); + }); + + void it('builds geo access definition for multiple user pool groups', () => { + const accessDefinition = roleAccessBuilder + .groups(['group1Name', 'group2Name']) + .to(['update', 'delete']); + + assert.deepStrictEqual(accessDefinition.actions, ['update', 'delete']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [group1AccessAcceptorMock, group2AccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getConstructFactoryMock.mock.calls[1].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'group1Name', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[1].arguments[0], + 'group2Name', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 2); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'group-group1Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[1].uniqueRoleToken, + 'group-group2Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for the group group1Name specified multiple times.', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[1].validationErrorOptions + .message, + 'Access definition for the group group2Name specified multiple times.', + ); + }); + + void it('throws error when auth construct factory is not found', () => { + const getConstructFactoryMockReturnsNull = mock.fn(() => null); + const stubGetInstancePropsWithNullFactory: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockReturnsNull, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder.authenticated.to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullFactory, + ); + }, + { + message: + 'Cannot specify geo resource access for authenticatedUserIamRole users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('throws error when auth construct factory getInstance returns null resource access acceptor', () => { + const getResourceAccessAcceptorMockReturnsNull = mock.fn(() => null); + const getConstructFactoryMockWithNullAcceptor = mock.fn(() => ({ + getInstance: () => ({ + getResourceAccessAcceptor: getResourceAccessAcceptorMockReturnsNull, + }), + })); + + const stubGetInstancePropsWithNullAcceptor: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockWithNullAcceptor, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder.guest.to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullAcceptor, + ); + }, + { + message: + 'Cannot specify geo resource access for unauthenticatedUserIamRole users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('throws error for group access when auth is not defined', () => { + const getConstructFactoryMockReturnsNull = mock.fn(() => null); + const stubGetInstancePropsWithNullFactory: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockReturnsNull, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder + .groups(['testGroup']) + .to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullFactory, + ); + }, + { + message: + 'Cannot specify geo resource access for testGroup users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('handles empty group array', () => { + const accessDefinition = roleAccessBuilder.groups([]).to(['get']); + + assert.deepStrictEqual(accessDefinition.actions, ['get']); + assert.equal(accessDefinition.getAccessAcceptors.length, 0); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 0); + }); + + void it('handles empty actions array', () => { + const accessDefinition = roleAccessBuilder.authenticated.to([]); + + assert.deepStrictEqual(accessDefinition.actions, []); + assert.equal(accessDefinition.getAccessAcceptors.length, 1); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + }); +}); diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts new file mode 100644 index 00000000000..3d30b3a1c77 --- /dev/null +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -0,0 +1,284 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyCollection } from './collection_construct.js'; +import { App, Stack } from 'aws-cdk-lib'; +import { Match, Template } from 'aws-cdk-lib/assertions'; +import assert from 'node:assert'; +import * as kms from 'aws-cdk-lib/aws-kms'; + +void describe('AmplifyCollection', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + void it('creates a geofence collection', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + }); + + void it('sets collection name correctly', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'myTestCollection', + collectionProps: { + geofenceCollectionName: 'myTestCollection', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'myTestCollection', + }); + }); + + void it('sets isDefault property correctly when true', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: true, + }); + + assert.equal(collection.isDefault, true); + assert.equal(collection.name, 'testCollectionName'); + assert.equal(collection.id, 'testCollection'); + }); + + void it('sets isDefault property correctly when false', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + assert.equal(collection.isDefault, false); + }); + + void it('defaults isDefault to false when not specified', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + }); + + assert.equal(collection.isDefault, false); + }); + + void it('stores attribution data in stack', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + const template = Template.fromStack(stack); + assert.equal( + JSON.parse(template.toJSON().Description).stackType, + 'geo-GeofenceCollection', + ); + }); + + void it('sets collection description when provided', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + description: 'Test geofence collection for unit testing', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Description: 'Test geofence collection for unit testing', + }); + }); + + void it('sets KMS key when provided', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + kmsKey: new kms.Key(stack, 'testKey', {}), + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + KmsKeyId: { + 'Fn::GetAtt': ['testKey1CDDDD5E', 'Arn'], + }, + }); + }); + + void it('exposes collection resource correctly', () => { + const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + assert.ok(amplifyCollection.resources.collection); + assert.ok(amplifyCollection.resources.cfnResources.cfnCollection); + }); + + void it('exposes CFN resources for overrides', () => { + const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Test that CFN resource is accessible for overrides + assert.ok(amplifyCollection.resources.cfnResources.cfnCollection); + assert.equal( + amplifyCollection.resources.cfnResources.cfnCollection.collectionName, + 'testCollectionName', + ); + }); + + void it('sets tags when provided via CFN resource', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Set tags via the exposed CFN resource + collection.resources.cfnResources.cfnCollection.tags = [ + { + key: 'Environment', + value: 'test', + }, + { + key: 'Project', + value: 'amplify-geo', + }, + ]; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Tags: [ + { + Key: 'Environment', + Value: 'test', + }, + { + Key: 'Project', + Value: 'amplify-geo', + }, + ], + }); + }); + + void describe('collection overrides', () => { + void it('can override collection properties', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Override the description via CFN resource + collection.resources.cfnResources.cfnCollection.description = + 'Overridden description'; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Description: 'Overridden description', + }); + }); + + void it('can override KMS key via CFN resource', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + collection.resources.cfnResources.cfnCollection.kmsKeyId = + 'arn:aws:kms:us-west-2:123456789012:key/87654321-4321-4321-4321-210987654321'; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + KmsKeyId: + 'arn:aws:kms:us-west-2:123456789012:key/87654321-4321-4321-4321-210987654321', + }); + }); + }); + + void describe('resource properties validation', () => { + void it('creates collection with minimal required properties', () => { + const collection = new AmplifyCollection(stack, 'minimalCollection', { + name: 'minimal', + collectionProps: {}, + isDefault: false, + }); + + assert.equal(collection.name, 'minimal'); + assert.equal(collection.id, 'minimalCollection'); + assert.equal(collection.isDefault, false); + assert.ok(collection.resources.collection); + assert.ok(collection.resources.cfnResources.cfnCollection); + + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: Match.stringLikeRegexp('.*minimal.*'), + }); + }); + + void it('creates collection with all optional properties', () => { + const collection = new AmplifyCollection(stack, 'fullCollection', { + name: 'fullFeatureCollection', + collectionProps: { + geofenceCollectionName: 'fullFeatureCollection', + description: 'A fully configured geofence collection', + kmsKey: new kms.Key(stack, 'testKey', {}), + }, + isDefault: true, + }); + + assert.equal(collection.name, 'fullFeatureCollection'); + assert.equal(collection.id, 'fullCollection'); + assert.equal(collection.isDefault, true); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'fullFeatureCollection', + Description: 'A fully configured geofence collection', + KmsKeyId: { + 'Fn::GetAtt': ['testKey1CDDDD5E', 'Arn'], + }, + }); + }); + }); +}); diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index fe1724eb16a..564497e63bb 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -5,6 +5,10 @@ import { Stack } from 'aws-cdk-lib'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { Policy } from 'aws-cdk-lib/aws-iam'; +import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage'; +import { fileURLToPath } from 'node:url'; + +const geoStackType = 'geo-GeofenceCollection'; /** * Amplify Collection CDK Construct @@ -34,6 +38,7 @@ export class AmplifyCollection this.name = props.name; this.id = id; this.isDefault = props.isDefault || false; + this.stack = Stack.of(scope); const geofenceCollection = new GeofenceCollection( this, @@ -49,5 +54,11 @@ export class AmplifyCollection ) as CfnGeofenceCollection, }, }; + + new AttributionMetadataStorage().storeAttributionMetadata( + Stack.of(this), + geoStackType, + fileURLToPath(new URL('../package.json', import.meta.url)), + ); } } diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts new file mode 100644 index 00000000000..d718505caf7 --- /dev/null +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -0,0 +1,161 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { defineCollection } from './collection_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { CollectionResources } from './types.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let collectionFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyCollectionFactory', () => { + beforeEach(() => { + collectionFactory = defineCollection({ + name: 'testCollection', + collectionProps: { + geofenceCollectionName: 'testCollection', + }, + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = collectionFactory.getInstance(getInstanceProps); + const instance2 = collectionFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('adds construct to stack', () => { + const collectionConstruct = collectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const collectionFactory = defineCollection({ + name: '|$%#86430resource', + collectionProps: {}, + }); + assert.throws( + () => + collectionFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + + void it('applies friendly name tag', () => { + const collectionConstruct = collectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + + // Check that the friendly name tag is applied + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + Tags: [ + { + Key: 'amplify:friendly-name', + Value: 'testCollection', + }, + ], + }); + }); + + void it('creates collection with custom collection properties', () => { + const customCollectionFactory = defineCollection({ + name: 'customCollection', + collectionProps: { + geofenceCollectionName: 'customCollection', + description: 'Custom test collection', + }, + }); + + const collectionConstruct = + customCollectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'customCollection', + Description: 'Custom test collection', + }); + }); + + void it('verifies stack property exists and is equal to collection stack', () => { + const collectionConstructFactory = defineCollection({ + name: 'testCollection', + collectionProps: { + geofenceCollectionName: 'testCollection', + }, + }).getInstance(getInstanceProps); + + assert.equal( + collectionConstructFactory.stack, + Stack.of(collectionConstructFactory.resources.collection), + ); + }); +}); diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index 65c92853a51..cb9e1cb6fc0 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -70,13 +70,6 @@ export class AmplifyCollectionGenerator generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { - const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - Stack.of(scope), - [], - ); - const amplifyCollection = new AmplifyCollection(scope, this.props.name, { ...this.props, outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, @@ -84,6 +77,17 @@ export class AmplifyCollectionGenerator Tags.of(amplifyCollection).add(TagName.FRIENDLY_NAME, this.props.name); + if (!this.props.access) { + return amplifyCollection; + } + + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + amplifyCollection.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyCollection.resources.collection.geofenceCollectionArn, diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts new file mode 100644 index 00000000000..c9402da07d6 --- /dev/null +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -0,0 +1,557 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { + ConstructFactoryGetInstanceProps, + SsmEnvironmentEntry, +} from '@aws-amplify/plugin-types'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +void describe('GeoAccessOrchestrator', () => { + void describe('orchestrateGeoAccess', () => { + let stack: Stack; + + const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = [ + { name: 'TEST_GEO_RESOURCE_NAME', path: 'test/ssm/path/to/geo/resource' }, + ]; + + const testResourceArn = + 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + + beforeEach(() => { + stack = createStackAndSetContext(); + }); + + void it('throws if invalid actions are provided for resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['invalidAction'], // Invalid action for collection + getAccessAcceptors: [ + () => ({ + identifier: 'testAcceptor', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + new AmplifyUserError('ActionNotFoundError', { + message: + 'Desired access action not found for the specific collection resource.', + resolution: + 'Please refer to specific collection access actions for more information.', + }), + ); + }); + + void it('throws if duplicate role tokens are provided', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'testAcceptor', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }, + }, + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + new AmplifyUserError('InvalidGeoAccessDefinitionError', { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }), + ); + }); + + void it('handles multiple actions for single access definition', () => { + const acceptResourceAccessMock = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read', 'create', 'update'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + const policies = geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.equal(policies.length, 1); + }); + + void it('handles multiple access acceptors for single definition', () => { + const acceptResourceAccessMock1 = mock.fn(); + const acceptResourceAccessMock2 = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'group-admin', + acceptResourceAccess: acceptResourceAccessMock1, + }), + () => ({ + identifier: 'group-user', + acceptResourceAccess: acceptResourceAccessMock2, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'group-admin', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + { + uniqueRoleToken: 'group-user', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + + assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); + assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); + + assert.deepStrictEqual( + acceptResourceAccessMock1.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.deepStrictEqual( + acceptResourceAccessMock2.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('handles multiple access definitions', () => { + const acceptResourceAccessMock1 = mock.fn(); + const acceptResourceAccessMock2 = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock1, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + { + actions: ['create', 'update'], + getAccessAcceptors: [ + () => ({ + identifier: 'group-admin', + acceptResourceAccess: acceptResourceAccessMock2, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'group-admin', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + + assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); + assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock1.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.deepStrictEqual( + acceptResourceAccessMock2.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('validates actions for map resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + // Should not throw for valid map action + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo-maps:us-east-1::provider/default', + 'map', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Effect: 'Allow', + Resource: 'arn:aws:geo-maps:us-east-1::provider/default', + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('validates actions for place resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['search', 'geocode'], // Valid for place + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + // Should not throw for valid place actions + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo-places:us-east-1::provider/default', + 'place', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + ], + Effect: 'Allow', + Resource: 'arn:aws:geo-places:us-east-1::provider/default', + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('throws for invalid action on map resource', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['create'], // Invalid for map (valid for collection) + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo:us-east-1:123456789012:map/test-map', + 'map', + ), + new AmplifyUserError('ActionNotFoundError', { + message: + 'Desired access action not found for the specific map resource.', + resolution: + 'Please refer to specific map access actions for more information.', + }), + ); + }); + + void it('handles empty actions array', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: [], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + { message: 'At least one permission must be specified' }, + ); + }); + }); +}); + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 5ab49e6c674..704cc8a646e 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -5,6 +5,7 @@ import { import { GeoAccessBuilder, GeoAccessGenerator, + GeoResourceType, resourceActionRecord, } from './types.js'; import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; @@ -29,8 +30,8 @@ export class GeoAccessOrchestrator { * @param getInstanceProps - instance properties of a specific construct factory * @param geoStack - instance of GeoAccessPolicyFactory to generate policyStatements * @param ssmEnvironmentEntries - permission reader and processor - * @param geoPolicyFactory - - * @param roleAccessBuilder - + * @param geoPolicyFactory - instance of the GeoAccessPolicyFactory for policy generation + * @param roleAccessBuilder - instance of the GeoAccessBuilder for access definition transformation */ constructor( private readonly geoAccessGenerator: GeoAccessGenerator, @@ -45,11 +46,12 @@ export class GeoAccessOrchestrator { /** * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. - * + * @param resourceArn - Amazon Resource Name (ARN) for the resource with access permissions + * @param resourceIdentifier - type of resource being defined */ orchestrateGeoAccess = ( resourceArn: string, - resourceIdentifier: string, + resourceIdentifier: GeoResourceType, ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( @@ -57,7 +59,6 @@ export class GeoAccessOrchestrator { ); geoAccessDefinitions.forEach((definition) => { - // get all user roles for each definition const uniqueRoleTokenSet = new Set(); definition.uniqueDefinitionValidators.forEach( @@ -83,16 +84,12 @@ export class GeoAccessOrchestrator { } }); - const roleTokens = Array.from(uniqueRoleTokenSet); - - let roleIndex: number = 0; // need respective roleToken for policy generation definition.getAccessAcceptors.forEach((acceptor) => { // for each acceptor within auth, guest, or user groups - const policy: Policy = this.geoPolicyFactory.createPolicy( definition.actions, resourceArn, - roleTokens[roleIndex], + acceptor(this.getInstanceProps).identifier, this.resourceStack, ); acceptor(this.getInstanceProps).acceptResourceAccess( @@ -100,7 +97,6 @@ export class GeoAccessOrchestrator { this.ssmEnvironmentEntries, ); this.policies.push(policy); - roleIndex += 1; }); }); @@ -108,7 +104,6 @@ export class GeoAccessOrchestrator { }; } -// needed for test mocking /** * Instance Manager for Geo Access Orchestration */ diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts new file mode 100644 index 00000000000..76b46b218f2 --- /dev/null +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -0,0 +1,482 @@ +import { App, Stack } from 'aws-cdk-lib'; +import { beforeEach, describe, it } from 'node:test'; +import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; +import assert from 'node:assert'; +import { Template } from 'aws-cdk-lib/assertions'; +import { AccountPrincipal, Policy, Role } from 'aws-cdk-lib/aws-iam'; + +void describe('GeoAccessPolicyFactory', () => { + let stack: Stack; + let geoAccessPolicyFactory: GeoAccessPolicyFactory; + const testResourceArn = + 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + + beforeEach(() => { + const app = new App(); + stack = new Stack(app); + geoAccessPolicyFactory = new GeoAccessPolicyFactory(); + }); + + void it('throws if no permissions are specified', () => { + assert.throws(() => + geoAccessPolicyFactory.createPolicy( + [], + testResourceArn, + 'test-role', + stack, + ), + ); + }); + + void it('returns policy with get actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['get'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with autocomplete actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['autocomplete'], + testResourceArn, + 'guest', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-guest-access-policy', + PolicyDocument: { + Statement: [ + { + Action: 'geo-places:Autocomplete', + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with geocode actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['geocode'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with search actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['search'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with create actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['create'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: 'geo:CreateGeofenceCollection', + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with read actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with update actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['update'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with delete actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['delete'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo:BatchDeleteGeofence', 'geo:DeleteGeofenceCollection'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with list actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['list'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo:ListGeofences', 'geo:ListGeofenceCollections'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('handles multiple actions in single policy', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read', 'create', 'update'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('creates policy with custom role token', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read'], + testResourceArn, + 'custom-role-token', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-custom-role-token-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('handles different resource ARNs', () => { + const mapResourceArn = 'arn:aws:geo:us-east-1:123456789012:map/test-map'; + const policy = geoAccessPolicyFactory.createPolicy( + ['get'], + mapResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Resource: mapResourceArn, + }, + ], + }, + }); + }); + + void it('creates policy with place index resource for search actions', () => { + const placeIndexArn = + 'arn:aws:geo:us-east-1:123456789012:place-index/test-place-index'; + const policy = geoAccessPolicyFactory.createPolicy( + ['search', 'geocode'], + placeIndexArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + ], + Resource: placeIndexArn, + }, + ], + }, + }); + }); + + void it('handles group role tokens', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read', 'update'], + testResourceArn, + 'group-admin', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-group-admin-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); +}); diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 5cc305395b1..a0efbc69b9c 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -29,10 +29,11 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - return new Policy(stack, `geo-access-policy`, { - policyName: `geo-${roleToken}-access-policy`, + const policyIDName: string = `geo-${roleToken}-access-policy`; + return new Policy(stack, policyIDName, { + policyName: policyIDName, statements: [policyStatement], - }); // returns policy with policy statement of all actions + }); }; } diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts new file mode 100644 index 00000000000..bd1e0dec9d1 --- /dev/null +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -0,0 +1,233 @@ +import { afterEach, beforeEach, describe, it, mock } from 'node:test'; +import assert from 'node:assert'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; +import { AmplifyCollection } from './collection_construct.js'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyPlace } from './place_resource.js'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { App, Stack } from 'aws-cdk-lib'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +void describe('AmplifyGeoOutputsAspect', () => { + let app: App; + let stack: Stack; + let outputStorageStrategy: BackendOutputStorageStrategy; + let aspect: AmplifyGeoOutputsAspect; + + const addBackendOutputEntryMock = mock.fn(); + const appendToBackendOutputListMock = mock.fn(); + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + + outputStorageStrategy = { + addBackendOutputEntry: addBackendOutputEntryMock, + appendToBackendOutputList: appendToBackendOutputListMock, + }; + }); + + afterEach(() => { + addBackendOutputEntryMock.mock.resetCalls(); + appendToBackendOutputListMock.mock.resetCalls(); + }); + + void describe('visit', () => { + void it('output storage invoked with AmplifyMap node', () => { + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + }); + + void it('only backend output entry invoked with AmplifyMap node', () => { + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + }); + + void it('output storage invoked with AmplifyPlace node', () => { + const placeNode = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(placeNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + }); + + void it('only backend output entry invoked with AmplifyPlace node', () => { + const placeNode = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(placeNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + }); + + void it('both backend output entry and append list invoked with AmplifyCollection node', () => { + const collectionNode = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: {}, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(collectionNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); + }); + + void it('output entry called once with multiple collections created', () => { + new AmplifyCollection(stack, 'testCollection_1', { + name: 'testCollection1', + collectionProps: {}, + isDefault: true, + }); // set as default collection + new AmplifyCollection(stack, 'testCollection_2', { + name: 'testCollection2', + collectionProps: {}, + }); + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + }); + }); + + void describe('resource validation for outputs', () => { + void it('throws if no collection set to default', () => { + const noDuplicateStack = new Stack(app, 'noDuplicateStack'); + const newNode = new AmplifyCollection( + noDuplicateStack, + 'testCollection2', + { name: 'testCollection_2', collectionProps: {} }, + ); + new AmplifyCollection(noDuplicateStack, 'testCollection3', { + name: 'testCollection_3', + collectionProps: {}, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + aspect.visit(newNode); + }, + new AmplifyUserError('NoDefaultCollectionError', { + message: + 'No instances of geofence collections have been marked as default.', + resolution: + 'Add `isDefault: true` to one of the `defineCollection` calls.', + }), + ); + }); + + void it('throws if multiple default collections', () => { + const node = new AmplifyCollection(stack, 'testCollection', { + name: 'defaultCollection', + collectionProps: {}, + isDefault: true, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + new AmplifyCollection(stack, 'defaultCollection', { + name: 'default_collection', + collectionProps: {}, + isDefault: true, + }); + aspect.visit(node); + }, + new AmplifyUserError('MultipleDefaultCollectionError', { + message: + 'Multiple instances of geofence collections have been marked as default.', + resolution: + 'Remove `isDefault: true` from all but one `defineCollection` call.', + }), + ); + }); + }); + + void describe('output validation', () => { + void it('output without collection', () => { + const node = new AmplifyMap(stack, 'mapResource', { + name: 'testMapResource', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(node); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + + assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[0], + 'AWS::Amplify::Geo', + ); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.aws_region, + Stack.of(node).region, + ); + assert.deepStrictEqual( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofence_collections, + undefined, + ); + }); + + void it('output with multiple collections and all resources', () => { + const node = new AmplifyMap(stack, 'mapResource', { + name: 'testMapResource', + }); + new AmplifyPlace(stack, 'placeResource', { name: 'testPlaceIndex' }); + new AmplifyCollection(stack, 'defaultCollection', { + name: 'default_collection', + collectionProps: { geofenceCollectionName: 'newCollection' }, + isDefault: true, + }); + new AmplifyCollection(stack, 'testCollection', { + name: 'default_collection', + collectionProps: {}, + }); + + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(node); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + + assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[0], + 'AWS::Amplify::Geo', + ); + + const parsedEntry1 = JSON.parse( + appendToBackendOutputListMock.mock.calls[0].arguments[1].payload + .geofence_collections, + ); + assert.ok(parsedEntry1.default.includes('TOKEN')); + assert.ok(parsedEntry1.items.includes('TOKEN')); + + const parsedEntry2 = JSON.parse( + appendToBackendOutputListMock.mock.calls[0].arguments[1].payload + .geofence_collections, + ); + assert.ok(parsedEntry2.items.includes('TOKEN')); + }); + }); +}); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 4a4275e8403..94d24f247c2 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -34,9 +34,9 @@ export class AmplifyGeoOutputsAspect implements IAspect { */ public visit(node: IConstruct): void { if ( - !(node instanceof AmplifyMap) || - !(node instanceof AmplifyPlace) || - !(node instanceof AmplifyCollection) || + !(node instanceof AmplifyMap) && + !(node instanceof AmplifyPlace) && + !(node instanceof AmplifyCollection) && this.isGeoOutputProcessed ) { return; @@ -69,11 +69,11 @@ export class AmplifyGeoOutputsAspect implements IAspect { } } - private findDefaultCollectionName = ( + private validateDefaultCollection = ( nodes: AmplifyCollection[], currentNode: AmplifyCollection, - ): string | undefined => { - const geoCount = nodes.length; + ) => { + const collectionCount = nodes.length; let defaultCollectionName: string | undefined = undefined; @@ -94,11 +94,11 @@ export class AmplifyGeoOutputsAspect implements IAspect { } }); - if (geoCount === 1 && !defaultCollectionName) { + if (collectionCount === 1 && !defaultCollectionName) { // if no defaults and only one construct, instance assumed to be default defaultCollectionName = currentNode.resources.collection?.geofenceCollectionName; - } else if (geoCount > 1 && !defaultCollectionName) { + } else if (collectionCount > 1 && !defaultCollectionName) { // if multiple constructs with default collection, throw error throw new AmplifyUserError('NoDefaultCollectionError', { message: @@ -115,29 +115,34 @@ export class AmplifyGeoOutputsAspect implements IAspect { * Function responsible for add all collection outputs (with defaults) * @param collections - all construct instances of AmplifyGeo * @param outputStorageStrategy - backend output schema of type GeoOutput - * @param region - + * @param region - region of geo resources */ private addBackendOutput( collections: AmplifyCollection[], outputStorageStrategy: BackendOutputStorageStrategy, region: string, ) { - const defaultCollectionName: string | undefined = - this.findDefaultCollectionName(collections, collections[0]); + this.validateDefaultCollection(collections, collections[0]); outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { version: '1', payload: { aws_region: region, - geofence_collections: JSON.stringify({ - default: defaultCollectionName, - items: defaultCollectionName, - }), }, }); collections.forEach((collection) => { - if (!collection.isDefault) { + if (collection.isDefault) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofence_collections: JSON.stringify({ + default: collection.resources.collection.geofenceCollectionName, + items: collection.resources.collection.geofenceCollectionName, + }), + }, + }); + } else { outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { version: '1', payload: { diff --git a/packages/backend-geo/src/map_factory.test.ts b/packages/backend-geo/src/map_factory.test.ts new file mode 100644 index 00000000000..de92746c905 --- /dev/null +++ b/packages/backend-geo/src/map_factory.test.ts @@ -0,0 +1,172 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { AmplifyMapFactory, defineMap } from './map_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { MapResources } from './types.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { AmplifyMap } from './map_resource.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let mapFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyMapFactory', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyMapFactory.mapCount = 0; + + mapFactory = defineMap({ + name: 'testMap', + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void describe('singleton validation', () => { + beforeEach(() => { + AmplifyMapFactory.mapCount = 0; + + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = mapFactory.getInstance(getInstanceProps); + const instance2 = mapFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('allows single map creation', () => { + const mapFactory = defineMap({ + name: 'singleMap', + }); + + const mapConstruct = mapFactory.getInstance( + getInstanceProps, + ) as AmplifyMap; + assert.equal(mapConstruct.name, 'singleMap'); + }); + + void it('prevents multiple map factory creation', () => { + defineMap({ + name: 'firstMap', + }); + + assert.throws( + () => + defineMap({ + name: 'secondMap', + }), + new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `defineMap` calls not permitted within an Amplify backend', + resolution: 'Maintain one `defineMap` call', + }), + ); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const mapInvalidFactory = defineMap({ + name: '|$%#86430resource', + }); + assert.throws( + () => + mapInvalidFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + }); + + void it('adds construct to stack', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + // Maps don't create CloudFormation resources, but the construct + assert.ok(mapConstruct.stack); + assert.equal(mapConstruct.name, 'testMap'); + }); + + void it('creates map with proper name and properties', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + assert.equal(mapConstruct.name, 'testMap'); + assert.ok(mapConstruct.resources); + }); + + void it('verifies stack property exists and is equal to map stack', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + assert.equal(mapConstruct.stack, Stack.of(mapConstruct)); + }); +}); diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 3d374d22cd9..17bd75ff587 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -78,6 +78,15 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { + const amplifyMap = new AmplifyMap(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + if (!this.props.access) { + return amplifyMap; + } + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( this.props.access, this.getInstanceProps, @@ -85,11 +94,6 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { [], ); - const amplifyMap = new AmplifyMap(scope, this.props.name, { - ...this.props, - outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, - }); - Tags.of(amplifyMap).add(TagName.FRIENDLY_NAME, this.props.name); amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts new file mode 100644 index 00000000000..a1f7be3aebd --- /dev/null +++ b/packages/backend-geo/src/map_resource.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyMap } from './map_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyMap', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a map resource', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map); + assert.equal(map.name, 'testMapName'); + assert.equal(map.id, 'testMap'); + }); + + void it('sets name property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'myTestMap', + }); + + assert.equal(map.name, 'myTestMap'); + }); + + void it('exposes map resources correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map.resources); + assert.ok(Array.isArray(map.resources.policies)); + assert.equal(typeof map.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-maps')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.equal(map.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const map = new AmplifyMap(stackWithRegion, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:*::provider\/default$/, + ), + ); + }); + + void it('handles multiple map resources', () => { + const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; + + mapNames.forEach((mapName, index) => { + const map = new AmplifyMap(stack, `testMap${index}`, { + name: mapName, + }); + + assert.equal(map.name, mapName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates map with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const map = new AmplifyMap(stackWithRegion, 'minimalMap', { + name: 'minimal', + }); + + assert.equal(map.name, 'minimal'); + assert.equal(map.id, 'minimalMap'); + assert.ok(map.resources); + assert.equal(map.resources.region, 'us-west-2'); + assert.ok(map.stack); + }); + }); +}); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 7fa416e2fdc..0f6b0933ccb 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -19,15 +19,16 @@ export class AmplifyMap */ constructor(scope: Construct, id: string, props: AmplifyMapProps) { super(scope, id); - this.name = props.name; + this.id = id; + + this.resources = { + region: this.stack.region, + policies: [], + }; } getResourceArn = (): string => { return `arn:${Aws.PARTITION}:geo-maps:${this.stack.region}::provider/default`; }; - - getResourceName = (): string => { - return this.name; - }; } diff --git a/packages/backend-geo/src/place_factory.test.ts b/packages/backend-geo/src/place_factory.test.ts new file mode 100644 index 00000000000..dc81c67df90 --- /dev/null +++ b/packages/backend-geo/src/place_factory.test.ts @@ -0,0 +1,179 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { AmplifyPlaceFactory, definePlace } from './place_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { PlaceResources } from './types.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { AmplifyPlace } from './place_resource.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let placeFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyPlaceFactory', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyPlaceFactory.placeCount = 0; + + placeFactory = definePlace({ + name: 'testPlace', + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void describe('singleton validation', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyPlaceFactory.placeCount = 0; + + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = placeFactory.getInstance(getInstanceProps); + const instance2 = placeFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('allows single place creation', () => { + const placeFactory = definePlace({ + name: 'singlePlace', + }); + + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + assert.equal(placeConstruct.name, 'singlePlace'); + }); + + void it('prevents multiple place factory creation', () => { + definePlace({ + name: 'firstPlace', + }); + + assert.throws( + () => + definePlace({ + name: 'secondPlace', + }), + new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `definePlace` calls not permitted within an Amplify backend', + resolution: 'Maintain one `definePlace` call', + }), + ); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const placeInvalidFactory = definePlace({ + name: '|$%#86430resource', + }); + assert.throws( + () => + placeInvalidFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + }); + + void it('adds construct to stack', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + // Places don't create CloudFormation resources, but the construct + assert.ok(placeConstruct.stack); + assert.equal(placeConstruct.name, 'testPlace'); + }); + + void it('creates place with proper name and properties', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + assert.equal(placeConstruct.name, 'testPlace'); + assert.ok(placeConstruct.resources); + }); + + void it('verifies stack property exists and is equal to place stack', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + assert.equal(placeConstruct.stack, Stack.of(placeConstruct)); + }); +}); diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 9b5bb3ea0b4..01229555445 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -20,7 +20,7 @@ import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; export class AmplifyPlaceFactory implements ConstructFactory> { - static mapCount: number = 0; + static placeCount: number = 0; private geoGenerator: ConstructContainerEntryGenerator; private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = @@ -31,14 +31,14 @@ export class AmplifyPlaceFactory * @param props - place resource properties */ constructor(private readonly props: AmplifyPlaceFactoryProps) { - if (AmplifyPlaceFactory.mapCount > 0) { + if (AmplifyPlaceFactory.placeCount > 0) { throw new AmplifyUserError('MultipleSingletonResourcesError', { message: 'Multiple `definePlace` calls not permitted within an Amplify backend', resolution: 'Maintain one `definePlace` call', }); } - AmplifyPlaceFactory.mapCount++; + AmplifyPlaceFactory.placeCount++; } getInstance = ( @@ -81,13 +81,6 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { - const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - Stack.of(scope), - [], - ); - const amplifyPlace = new AmplifyPlace(scope, this.props.name, { ...this.props, outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, @@ -95,10 +88,21 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); + if (!this.props.access) { + return amplifyPlace; + } + + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + amplifyPlace.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyPlace.getResourceArn(), - 'map', + 'place', ); const geoAspects = Aspects.of(Stack.of(amplifyPlace)); diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts new file mode 100644 index 00000000000..633c506e390 --- /dev/null +++ b/packages/backend-geo/src/place_resource.test.ts @@ -0,0 +1,111 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyPlace } from './place_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyPlace', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a place resource', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.ok(place); + assert.equal(place.name, 'testPlaceName'); + assert.equal(place.id, 'testPlace'); + }); + + void it('sets name property correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'myTestPlace', + }); + + assert.equal(place.name, 'myTestPlace'); + }); + + void it('exposes place resources correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.ok(place.resources); + assert.ok(Array.isArray(place.resources.policies)); + assert.equal(typeof place.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + const arn = place.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-places')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.equal(place.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const place = new AmplifyPlace(stackWithRegion, 'testPlace', { + name: 'testPlaceName', + }); + + const arn = place.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:*::provider\/default$/, + ), + ); + }); + + void it('handles multiple place resources', () => { + const placeNames = [ + 'simple-place', + 'complex_place_name', + 'PlaceWithCamelCase', + ]; + + placeNames.forEach((placeName, index) => { + const place = new AmplifyPlace(stack, `testPlace${index}`, { + name: placeName, + }); + + assert.equal(place.name, placeName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates place with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const place = new AmplifyPlace(stackWithRegion, 'minimalPlace', { + name: 'minimal', + }); + + assert.equal(place.name, 'minimal'); + assert.equal(place.id, 'minimalPlace'); + assert.ok(place.resources); + assert.equal(place.resources.region, 'us-west-2'); + assert.ok(place.stack); + }); + }); +}); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index 7dcd13dca65..f928cc304cd 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -21,6 +21,12 @@ export class AmplifyPlace super(scope, id); this.name = props.name; + this.id = id; + + this.resources = { + region: this.stack.region, + policies: [], + }; } getResourceArn = (): string => { diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 34decc54b0f..736383b539e 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -30,7 +30,7 @@ export type AmplifyMapFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; /** @@ -49,7 +49,7 @@ export type AmplifyPlaceFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; /** @@ -68,23 +68,23 @@ export type AmplifyCollectionFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; -export type AmplifyMapProps = { - name: string; - outputStorageStrategy?: BackendOutputStorageStrategy; -}; +export type AmplifyMapProps = Omit< + AmplifyCollectionProps, + 'collectionProps' | 'isDefault' +>; -export type AmplifyPlaceProps = { - name: string; - outputStorageStrategy?: BackendOutputStorageStrategy; -}; +export type AmplifyPlaceProps = Omit< + AmplifyCollectionProps, + 'collectionProps' | 'isDefault' +>; export type AmplifyCollectionProps = { name: string; collectionProps: GeofenceCollectionProps; - isDefault: boolean; + isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; @@ -94,6 +94,7 @@ export type AmplifyCollectionProps = { */ export type MapResources = { policies: Policy[]; + region: string; }; /** @@ -102,6 +103,7 @@ export type MapResources = { */ export type PlaceResources = { policies: Policy[]; + region: string; }; /** @@ -111,8 +113,8 @@ export type PlaceResources = { * @param cfnResources - cloudformation resources exposed from the abstracted collection provisioned from collection */ export type CollectionResources = { - collection: GeofenceCollection; policies: Policy[]; + collection: GeofenceCollection; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -152,3 +154,5 @@ export const resourceActionRecord: Record = { place: ['autocomplete', 'geocode', 'search'], collection: ['create', 'read', 'update', 'delete', 'list'], }; + +export type GeoResourceType = 'map' | 'place' | 'collection'; From ecbf8ed4c1ee3cff711210a1fcaaca66f15873fb Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:29:44 -0700 Subject: [PATCH 11/93] updating tsconfig --- packages/backend-geo/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json index 6dd4b4b566a..1a3b4eaa39e 100644 --- a/packages/backend-geo/tsconfig.json +++ b/packages/backend-geo/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib" }, "references": [ { "path": "../backend-output-schemas" }, + { "path": "../backend-output-storage" }, { "path": "../platform-core" } ] } From 3053f40cfccd863169bed6b166c7f6d62960619e Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:37:14 -0700 Subject: [PATCH 12/93] fixing some expressions --- packages/backend-geo/src/geo_access_orchestrator.test.ts | 4 +--- packages/backend-geo/src/map_resource.test.ts | 2 +- packages/backend-geo/src/place_resource.test.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index c9402da07d6..de2148293a2 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -12,9 +12,7 @@ void describe('GeoAccessOrchestrator', () => { void describe('orchestrateGeoAccess', () => { let stack: Stack; - const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = [ - { name: 'TEST_GEO_RESOURCE_NAME', path: 'test/ssm/path/to/geo/resource' }, - ]; + const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = []; const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts index a1f7be3aebd..713a69f282a 100644 --- a/packages/backend-geo/src/map_resource.test.ts +++ b/packages/backend-geo/src/map_resource.test.ts @@ -71,7 +71,7 @@ void describe('AmplifyMap', () => { const arn = map.getResourceArn(); assert.ok( arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:*::provider\/default$/, + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, ), ); }); diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts index 633c506e390..bc135096b18 100644 --- a/packages/backend-geo/src/place_resource.test.ts +++ b/packages/backend-geo/src/place_resource.test.ts @@ -71,7 +71,7 @@ void describe('AmplifyPlace', () => { const arn = place.getResourceArn(); assert.ok( arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:*::provider\/default$/, + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:[^:]*::provider\/default$/, ), ); }); From 25d296a96d0f0559e1e62204669ba3f8f668a678 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:42:27 -0700 Subject: [PATCH 13/93] updating API from recent commits --- packages/backend-geo/API.md | 82 +++++++++++++++----------- packages/backend-geo/src/index.ts | 2 + packages/backend-output-schemas/API.md | 60 ++++++++----------- 3 files changed, 74 insertions(+), 70 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index ad142371e83..947948b9c3f 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -12,35 +12,57 @@ import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; -import { IRole } from 'aws-cdk-lib/aws-iam'; +import { Policy } from 'aws-cdk-lib/aws-iam'; +import { ResourceAccessAcceptor } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; import { StackProvider } from '@aws-amplify/plugin-types'; -// @public (undocumented) -export type AmplifyGeoFactoryProps = Omit & { - region: string; - access: GeoAccessGenerator; - resourceIdentifier?: GeoResourceType; +// @public +export type AmplifyCollectionFactoryProps = Omit & { + access?: GeoAccessGenerator; }; // @public (undocumented) -export type AmplifyGeoProps = { +export type AmplifyCollectionProps = { name: string; - collectionProps?: GeofenceCollectionProps; + collectionProps: GeofenceCollectionProps; + isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; +// @public +export type AmplifyMapFactoryProps = Omit & { + access?: GeoAccessGenerator; +}; + // @public (undocumented) -export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; +export type AmplifyMapProps = Omit; // @public -export const defineCollection: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export type AmplifyPlaceFactoryProps = Omit & { + access?: GeoAccessGenerator; +}; + +// @public (undocumented) +export type AmplifyPlaceProps = Omit; // @public -export const defineMap: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export type CollectionResources = { + policies: Policy[]; + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; // @public -export const definePlace: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export const defineCollection: (props: AmplifyCollectionFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const defineMap: (props: AmplifyMapFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const definePlace: (props: AmplifyPlaceFactoryProps) => ConstructFactory & StackProvider>; // @public (undocumented) export type GeoAccessBuilder = { @@ -51,8 +73,8 @@ export type GeoAccessBuilder = { // @public (undocumented) export type GeoAccessDefinition = { - userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; - actions: GeoAction[]; + getAccessAcceptors: ((getInstanceProps: ConstructFactoryGetInstanceProps) => ResourceAccessAcceptor)[]; + actions: string[]; uniqueDefinitionValidators: { uniqueRoleToken: string; validationErrorOptions: AmplifyUserErrorOptions; @@ -62,36 +84,28 @@ export type GeoAccessDefinition = { // @public (undocumented) export type GeoAccessGenerator = (allow: GeoAccessBuilder) => GeoAccessDefinition[]; -// @public (undocumented) -export type GeoAction = MapAction | IndexAction | CollectionAction; - // @public (undocumented) export type GeoActionBuilder = { - to: (actions: GeoAction[]) => GeoAccessDefinition; + to: (actions: string[]) => GeoAccessDefinition; }; // @public (undocumented) -export const geoCfnResourceTypes: string[]; - -// @public (undocumented) -export const geoManagedResourceTypes: string[]; +export type GeoResourceType = 'map' | 'place' | 'collection'; -// @public (undocumented) -export type GeoResources = { - collection: GeofenceCollection; - cfnResources: { - cfnCollection: CfnGeofenceCollection; - }; +// @public +export type MapResources = { + policies: Policy[]; + region: string; }; -// @public (undocumented) -export type GeoResourceType = 'map' | 'place' | 'collection'; - -// @public (undocumented) -export type IndexAction = 'autocomplete' | 'geocode' | 'search'; +// @public +export type PlaceResources = { + policies: Policy[]; + region: string; +}; // @public (undocumented) -export type MapAction = 'get'; +export const resourceActionRecord: Record; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 9237d106c6d..bfc07f0e61a 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,2 +1,4 @@ export { defineCollection } from './collection_factory.js'; +export { defineMap } from './map_factory.js'; +export { definePlace } from './place_factory.js'; export * from './types.js'; diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 706b7ab13dd..705af068c4e 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -380,31 +380,26 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo": z.ZodOptional; payload: z.ZodObject<{ - defaultCollection: z.ZodString; - geoRegion: z.ZodString; - collections: z.ZodString; + aws_region: z.ZodString; + geofence_collections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }>]>>; }, "strip", z.ZodTypeAny, { @@ -482,9 +477,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; } | undefined; }, { @@ -562,9 +556,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; } | undefined; }>; @@ -756,31 +749,26 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ - defaultCollection: z.ZodString; - geoRegion: z.ZodString; - collections: z.ZodString; + aws_region: z.ZodString; + geofence_collections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }>]>; From 6ca3046977822dc5cf8701b468d9de632679b0e6 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:46:52 -0700 Subject: [PATCH 14/93] removing all previous changesets --- .changeset/README.md | 8 -------- .changeset/config.json | 11 ----------- .changeset/empty-trains-cut.md | 5 ----- .changeset/warm-garlics-flow.md | 5 ----- 4 files changed, 29 deletions(-) delete mode 100644 .changeset/README.md delete mode 100644 .changeset/config.json delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6a67..00000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 6d2119a4592..00000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. From 4bfb06f100f417bf920fcea29eb141155e7e207c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:49:21 -0700 Subject: [PATCH 15/93] Revert "removing all previous changesets" This reverts commit e2af23b38347f48f992ce361193a142bb1928f8e. --- .changeset/README.md | 8 ++++++++ .changeset/config.json | 11 +++++++++++ .changeset/empty-trains-cut.md | 5 +++++ .changeset/warm-garlics-flow.md | 5 +++++ 4 files changed, 29 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/empty-trains-cut.md create mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..6d2119a4592 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. From d6b1f7ab356d7b20d144e88426145ecb9247b595 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:49:59 -0700 Subject: [PATCH 16/93] removing changeset files instead of directory --- .changeset/empty-trains-cut.md | 5 - .changeset/warm-garlics-flow.md | 5 - packages/backend-geo/src/map_resource.test.ts | 107 ------------------ 3 files changed, 117 deletions(-) delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md delete mode 100644 packages/backend-geo/src/map_resource.test.ts diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts deleted file mode 100644 index 713a69f282a..00000000000 --- a/packages/backend-geo/src/map_resource.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { beforeEach, describe, it } from 'node:test'; -import { AmplifyMap } from './map_resource.js'; -import { App, Stack } from 'aws-cdk-lib'; -import assert from 'node:assert'; - -void describe('AmplifyMap', () => { - let app: App; - let stack: Stack; - - beforeEach(() => { - app = new App(); - stack = new Stack(app); - }); - - void it('creates a map resource', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.ok(map); - assert.equal(map.name, 'testMapName'); - assert.equal(map.id, 'testMap'); - }); - - void it('sets name property correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'myTestMap', - }); - - assert.equal(map.name, 'myTestMap'); - }); - - void it('exposes map resources correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.ok(map.resources); - assert.ok(Array.isArray(map.resources.policies)); - assert.equal(typeof map.resources.region, 'string'); - }); - - void it('returns correct resource ARN', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - const arn = map.getResourceArn(); - assert.ok(arn.includes('arn:')); - assert.ok(arn.includes('geo-maps')); - assert.ok(arn.includes('provider/default')); - }); - - void it('sets stack property correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.equal(map.stack, stack); - }); - - void it('generates ARN with correct partition and region', () => { - const stackWithRegion = new Stack(app, 'TestStack', { - env: { region: 'us-west-2' }, - }); - - const map = new AmplifyMap(stackWithRegion, 'testMap', { - name: 'testMapName', - }); - - const arn = map.getResourceArn(); - assert.ok( - arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, - ), - ); - }); - - void it('handles multiple map resources', () => { - const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; - - mapNames.forEach((mapName, index) => { - const map = new AmplifyMap(stack, `testMap${index}`, { - name: mapName, - }); - - assert.equal(map.name, mapName); - }); - }); - - void describe('resource properties validation', () => { - void it('creates map with minimal required properties', () => { - const stackWithRegion = new Stack(app, 'TestStack', { - env: { region: 'us-west-2' }, - }); - const map = new AmplifyMap(stackWithRegion, 'minimalMap', { - name: 'minimal', - }); - - assert.equal(map.name, 'minimal'); - assert.equal(map.id, 'minimalMap'); - assert.ok(map.resources); - assert.equal(map.resources.region, 'us-west-2'); - assert.ok(map.stack); - }); - }); -}); From 616a934001e16a468352b0e8df608dc0afbc4555 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:52:56 -0700 Subject: [PATCH 17/93] Revert "removing changeset files instead of directory" This reverts commit 93e024f7a2519a9a8218088cc19a6c310a217dcf. --- .changeset/empty-trains-cut.md | 5 + .changeset/warm-garlics-flow.md | 5 + packages/backend-geo/src/map_resource.test.ts | 107 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 .changeset/empty-trains-cut.md create mode 100644 .changeset/warm-garlics-flow.md create mode 100644 packages/backend-geo/src/map_resource.test.ts diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts new file mode 100644 index 00000000000..713a69f282a --- /dev/null +++ b/packages/backend-geo/src/map_resource.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyMap } from './map_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyMap', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a map resource', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map); + assert.equal(map.name, 'testMapName'); + assert.equal(map.id, 'testMap'); + }); + + void it('sets name property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'myTestMap', + }); + + assert.equal(map.name, 'myTestMap'); + }); + + void it('exposes map resources correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map.resources); + assert.ok(Array.isArray(map.resources.policies)); + assert.equal(typeof map.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-maps')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.equal(map.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const map = new AmplifyMap(stackWithRegion, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, + ), + ); + }); + + void it('handles multiple map resources', () => { + const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; + + mapNames.forEach((mapName, index) => { + const map = new AmplifyMap(stack, `testMap${index}`, { + name: mapName, + }); + + assert.equal(map.name, mapName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates map with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const map = new AmplifyMap(stackWithRegion, 'minimalMap', { + name: 'minimal', + }); + + assert.equal(map.name, 'minimal'); + assert.equal(map.id, 'minimalMap'); + assert.ok(map.resources); + assert.equal(map.resources.region, 'us-west-2'); + assert.ok(map.stack); + }); + }); +}); From 727423cd94a1d3bcd4eafe7033ae1b6fc91a7571 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:54:04 -0700 Subject: [PATCH 18/93] removing changeset files --- .changeset/empty-trains-cut.md | 5 ----- .changeset/warm-garlics-flow.md | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. From 21107af8e2038f9574335de069b74ed19457d49f Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 02:00:03 -0700 Subject: [PATCH 19/93] adding changeset --- .changeset/puny-hornets-brush.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/puny-hornets-brush.md diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md new file mode 100644 index 00000000000..9486bd36484 --- /dev/null +++ b/.changeset/puny-hornets-brush.md @@ -0,0 +1,6 @@ +--- +'@aws-amplify/backend-geo': minor +'@aws-amplify/backend-output-schemas': patch +--- + +This changeset involves the introduction of a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. From f55239b5d33a8c0ff1478f1deab883ec7ad33d42 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 09:39:03 -0700 Subject: [PATCH 20/93] clean up --- packages/backend-geo/src/geo_access_orchestrator.ts | 2 -- packages/backend-geo/src/geo_outputs_aspect.ts | 2 +- packages/backend-geo/src/map_factory.ts | 4 ---- packages/backend-geo/src/place_factory.ts | 4 ---- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 704cc8a646e..594040b213f 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -14,8 +14,6 @@ import { AmplifyUserError } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; import { Stack } from 'aws-cdk-lib'; -// this file is responsible for implementing the following: -// 1. access orchestrator for geo /** * Access Orchestrator for Amplify Geo * diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 94d24f247c2..c5e25acdd70 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -42,7 +42,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { return; } - this.isGeoOutputProcessed = true; // once this is visited, this no longer remains false + this.isGeoOutputProcessed = true; // once this is visited, shouldn't process geo outputs again const mapInstances = Stack.of(node).node.children.filter( (el) => el instanceof AmplifyMap, diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 17bd75ff587..ea5fc4105f7 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -44,18 +44,14 @@ export class AmplifyMapFactory getInstance = ( getInstanceProps: ConstructFactoryGetInstanceProps, ): AmplifyMap => { - // get construct factory instance properties const { constructContainer, resourceNameValidator } = getInstanceProps; - // validates the user-entered resource name (according to CDK naming regulations) resourceNameValidator?.validate(this.props.name); - // generates a singleton container entry for this construct factory if (!this.geoGenerator) { this.geoGenerator = new AmplifyMapGenerator(this.props, getInstanceProps); } - // this getOrCompute accesses the internal construct container cache return constructContainer.getOrCompute(this.geoGenerator) as AmplifyMap; }; } diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 01229555445..ac87796d543 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -44,13 +44,10 @@ export class AmplifyPlaceFactory getInstance = ( getInstanceProps: ConstructFactoryGetInstanceProps, ): AmplifyPlace => { - // get construct factory instance properties const { constructContainer, resourceNameValidator } = getInstanceProps; - // validates the user-entered resource name (according to CDK naming regulations) resourceNameValidator?.validate(this.props.name); - // generates a singleton container entry for this construct factory if (!this.geoGenerator) { this.geoGenerator = new AmplifyPlaceGenerator( this.props, @@ -58,7 +55,6 @@ export class AmplifyPlaceFactory ); } - // this getOrCompute accesses the internal construct container cache return constructContainer.getOrCompute(this.geoGenerator) as AmplifyPlace; }; } From 6a85f9ad1624376cf5b36c870908e8195a4ab45b Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sat, 26 Jul 2025 13:45:25 -0700 Subject: [PATCH 21/93] geo client-output bug fixed --- .../backend-geo/src/collection_factory.ts | 6 +- .../src/geo_outputs_aspect.test.ts | 21 +---- .../backend-geo/src/geo_outputs_aspect.ts | 53 ++++++------- packages/backend-geo/src/map_factory.ts | 6 +- packages/backend-geo/src/place_factory.ts | 6 +- packages/backend-output-schemas/src/geo/v1.ts | 8 +- .../client_config_contributor_factory.ts | 5 ++ .../client_config_contributor_v1.test.ts | 55 +++++++++++++ .../client_config_contributor_v1.ts | 46 +++++++++++ .../unified_client_config_generator.test.ts | 78 +++++++++++++++++++ 10 files changed, 235 insertions(+), 49 deletions(-) diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index cb9e1cb6fc0..e770c55e9cd 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -96,7 +96,11 @@ export class AmplifyCollectionGenerator const geoAspects = Aspects.of(Stack.of(amplifyCollection)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyCollection; diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index bd1e0dec9d1..a512260ff80 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -106,7 +106,7 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); }); @@ -179,12 +179,12 @@ void describe('AmplifyGeoOutputsAspect', () => { 'AWS::Amplify::Geo', ); assert.equal( - addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.aws_region, + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.geoRegion, Stack.of(node).region, ); assert.deepStrictEqual( addBackendOutputEntryMock.mock.calls[0].arguments[1].payload - .geofence_collections, + .geofenceCollections, undefined, ); }); @@ -208,26 +208,13 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( addBackendOutputEntryMock.mock.calls[0].arguments[0], 'AWS::Amplify::Geo', ); - - const parsedEntry1 = JSON.parse( - appendToBackendOutputListMock.mock.calls[0].arguments[1].payload - .geofence_collections, - ); - assert.ok(parsedEntry1.default.includes('TOKEN')); - assert.ok(parsedEntry1.items.includes('TOKEN')); - - const parsedEntry2 = JSON.parse( - appendToBackendOutputListMock.mock.calls[0].arguments[1].payload - .geofence_collections, - ); - assert.ok(parsedEntry2.items.includes('TOKEN')); }); }); }); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index c5e25acdd70..b58d83eae0d 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -36,12 +36,13 @@ export class AmplifyGeoOutputsAspect implements IAspect { if ( !(node instanceof AmplifyMap) && !(node instanceof AmplifyPlace) && - !(node instanceof AmplifyCollection) && - this.isGeoOutputProcessed + !(node instanceof AmplifyCollection) ) { return; } + if (this.isGeoOutputProcessed) return; + this.isGeoOutputProcessed = true; // once this is visited, shouldn't process geo outputs again const mapInstances = Stack.of(node).node.children.filter( @@ -122,36 +123,36 @@ export class AmplifyGeoOutputsAspect implements IAspect { outputStorageStrategy: BackendOutputStorageStrategy, region: string, ) { - this.validateDefaultCollection(collections, collections[0]); + const defaultCollectionName = this.validateDefaultCollection( + collections, + collections[0], + ); + // Add the main geo output entry with aws_region (snake_case to match schema) outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { version: '1', payload: { - aws_region: region, + geoRegion: region, }, }); - collections.forEach((collection) => { - if (collection.isDefault) { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - geofence_collections: JSON.stringify({ - default: collection.resources.collection.geofenceCollectionName, - items: collection.resources.collection.geofenceCollectionName, - }), - }, - }); - } else { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - geofence_collections: JSON.stringify({ - items: collection.resources.collection.geofenceCollectionName, - }), - }, - }); - } - }); + // Collect all collection names for the items array + const collectionNames = collections.map( + (collection) => collection.resources.collection.geofenceCollectionName, + ); + + // Add geofence_collections as a single entry with all collections + if (collections.length > 0 && defaultCollectionName) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofenceCollections: JSON.stringify({ + // Changed from geofenceCollections to geofence_collections + default: defaultCollectionName, + items: collectionNames, // Array of all collection names + }), + }, + }); + } } } diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index ea5fc4105f7..3419cf8bb5f 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -99,7 +99,11 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { const geoAspects = Aspects.of(Stack.of(amplifyMap)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyMap; diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index ac87796d543..1620e915c87 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -103,7 +103,11 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { const geoAspects = Aspects.of(Stack.of(amplifyPlace)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyPlace; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 4a569fb9343..9be0b349d14 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -2,13 +2,15 @@ import { z } from 'zod'; const collectionSchema = z.object({ default: z.string(), - items: z.string(z.array(z.string())).optional(), + items: z.array(z.string()), }); export const geoOutputSchema = z.object({ version: z.literal('1'), payload: z.object({ - aws_region: z.string(), - geofence_collections: z.string(collectionSchema).optional(), + geoRegion: z.string(), + maps: z.string().optional(), // JSON serialized string + searchIndices: z.string().optional(), // JSON serialized string + geofenceCollections: z.string(collectionSchema).optional(), // JSON serialized string }), }); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts index 6a0aba105f4..6aeb34dab86 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts @@ -4,6 +4,7 @@ import { AuthClientConfigContributor as Auth1_3, CustomClientConfigContributor as Custom1_1, DataClientConfigContributor as Data1_1, + GeoClientConfigContributor as Geo1, StorageClientConfigContributorV1 as Storage1, StorageClientConfigContributorV1_1 as Storage1_1, StorageClientConfigContributor as Storage1_2, @@ -39,6 +40,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_4]: [ new Auth1_3(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributor1_4(), new Custom1_1(), @@ -47,6 +49,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_3]: [ new Auth1_3(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributorV1_3(), new Custom1_1(), @@ -55,6 +58,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_2]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributorV1_2(), new Custom1_1(), @@ -63,6 +67,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_1]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_1(), new VersionContributorV1_1(), new Custom1_1(), diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 7a934558721..706268e636b 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -3,6 +3,7 @@ import { AuthClientConfigContributor, CustomClientConfigContributor, DataClientConfigContributor, + GeoClientConfigContributor, StorageClientConfigContributor, VersionContributor, } from './client_config_contributor_v1.js'; @@ -15,6 +16,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, storageOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -586,6 +588,59 @@ void describe('data client config contributor v1', () => { }); }); +void describe('geo client config contributor v1', () => { + void it('empty outputs if no geo output provided', () => { + const contributor = new GeoClientConfigContributor(); + assert.deepStrictEqual( + contributor.contribute({ + [graphqlOutputKey]: { + version: '1', + payload: { + awsAppsyncApiEndpoint: 'testApiEndpoint', + awsAppsyncRegion: 'us-east-1', + awsAppsyncAuthenticationType: 'API_KEY', + awsAppsyncAdditionalAuthenticationTypes: 'API_KEY', + awsAppsyncConflictResolutionMode: undefined, + awsAppsyncApiKey: 'testApiKey', + awsAppsyncApiId: 'testApiId', + amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', + }, + }, + }), + {}, + ); + }); + + void it('returns correct config when geo collections exist', () => { + const contributor = new GeoClientConfigContributor(); + assert.deepStrictEqual( + contributor.contribute({ + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-west-2', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, + }), + { + geo: { + aws_region: 'us-west-2', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, + }, + ); + }); +}); + void describe('storage client config contributor v1', () => { void it('returns an empty object if output has no storage output', () => { const contributor = new StorageClientConfigContributor(); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index 72e5505dbb9..e1838d3833b 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -3,6 +3,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, storageOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -468,6 +469,51 @@ export class DataClientConfigContributor implements ClientConfigContributor { }; } +/** + * Transformer for Geo segment of ClientConfig (V1.1 or later) + */ +export class GeoClientConfigContributor implements ClientConfigContributor { + contribute = ({ + [geoOutputKey]: geoOutput, + }: UnifiedBackendOutput): Partial | Record => { + if (geoOutput === undefined) { + return {}; + } + + const config: Partial = {}; + + config.geo = { + aws_region: geoOutput.payload.geoRegion, + }; + + let geofenceCollectionsObj; + + if (geoOutput.payload.geofenceCollections) { + const firstParse = JSON.parse( + JSON.parse(geoOutput.payload.geofenceCollections), + ); + + if ( + firstParse && + typeof firstParse === 'object' && + !Array.isArray(firstParse) && + firstParse.default + ) { + geofenceCollectionsObj = firstParse; + } + + if (geofenceCollectionsObj && geofenceCollectionsObj.default) { + config.geo!.geofence_collections = { + default: geofenceCollectionsObj.default, + items: geofenceCollectionsObj.items || [], + }; + } + } + + return config; + }; +} + /** * Translator for the Storage portion of ClientConfig in V1.2 */ diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index e39e5301342..b2d984b49a2 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -5,6 +5,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, platformOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -79,6 +80,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -91,6 +104,7 @@ void describe('UnifiedClientConfigGenerator', () => { }, }, }; + const outputRetrieval = mock.fn(async () => stubOutput); const modelSchemaAdapter = new ModelIntrospectionSchemaAdapter( stubClientProvider, @@ -150,6 +164,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', @@ -213,6 +234,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -277,6 +310,13 @@ void describe('UnifiedClientConfigGenerator', () => { }, ], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, data: { url: 'testApiEndpoint', aws_region: 'us-east-1', @@ -334,6 +374,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -393,6 +445,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', @@ -443,6 +502,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -502,6 +573,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', From 1f4ebcc89a8dd4ad4ca1c7bbc8567349723690c1 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sat, 26 Jul 2025 14:52:57 -0700 Subject: [PATCH 22/93] updating API and fixing policy name duplication bug --- .../backend-geo/src/collection_factory.ts | 1 + .../src/geo_access_orchestrator.test.ts | 21 +++++- .../src/geo_access_orchestrator.ts | 13 +++- .../src/geo_access_policy_factory.test.ts | 44 ++++++++---- .../src/geo_access_policy_factory.ts | 3 +- packages/backend-geo/src/map_factory.ts | 1 + packages/backend-geo/src/place_factory.ts | 1 + packages/backend-output-schemas/API.md | 72 ++++++++++++------- 8 files changed, 114 insertions(+), 42 deletions(-) diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index e770c55e9cd..0a5e8a154c0 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -92,6 +92,7 @@ export class AmplifyCollectionGenerator geoAccessOrchestrator.orchestrateGeoAccess( amplifyCollection.resources.collection.geofenceCollectionArn, 'collection', + amplifyCollection.name, ); const geoAspects = Aspects.of(Stack.of(amplifyCollection)); diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index de2148293a2..263a18bb956 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -17,6 +17,8 @@ void describe('GeoAccessOrchestrator', () => { const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + const testResourceName = 'testResource'; + beforeEach(() => { stack = createStackAndSetContext(); }); @@ -55,6 +57,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), new AmplifyUserError('ActionNotFoundError', { message: @@ -106,6 +109,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), new AmplifyUserError('InvalidGeoAccessDefinitionError', { message: 'Duplicate authenticated access definition', @@ -147,6 +151,7 @@ void describe('GeoAccessOrchestrator', () => { const policies = geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -217,7 +222,11 @@ void describe('GeoAccessOrchestrator', () => { ssmEnvironmentEntriesStub, ); - geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + testResourceName, + ); assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); @@ -310,7 +319,11 @@ void describe('GeoAccessOrchestrator', () => { ssmEnvironmentEntriesStub, ); - geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + testResourceName, + ); assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); @@ -386,6 +399,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo-maps:us-east-1::provider/default', 'map', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -436,6 +450,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo-places:us-east-1::provider/default', 'place', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -494,6 +509,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo:us-east-1:123456789012:map/test-map', 'map', + testResourceName, ), new AmplifyUserError('ActionNotFoundError', { message: @@ -538,6 +554,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), { message: 'At least one permission must be specified' }, ); diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 594040b213f..4e643c76322 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -50,14 +50,17 @@ export class GeoAccessOrchestrator { orchestrateGeoAccess = ( resourceArn: string, resourceIdentifier: GeoResourceType, + resourceName: string, ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( this.roleAccessBuilder, ); + const uniqueRoleTokenSet = new Set(); + geoAccessDefinitions.forEach((definition) => { - const uniqueRoleTokenSet = new Set(); + const uniqueActionSet = new Set(); definition.uniqueDefinitionValidators.forEach( ({ uniqueRoleToken, validationErrorOptions }) => { @@ -80,6 +83,13 @@ export class GeoAccessOrchestrator { resolution: `Please refer to specific ${resourceIdentifier} access actions for more information.`, }); } + if (uniqueActionSet.has(action)) { + throw new AmplifyUserError('DuplicateActionFoundError', { + message: `Desired access action is duplicated for the specific ${resourceIdentifier} resource.`, + resolution: `Remove all but one mentions of the ${action} action for the specific ${resourceIdentifier} resource.`, + }); + } + uniqueActionSet.add(action); }); definition.getAccessAcceptors.forEach((acceptor) => { @@ -88,6 +98,7 @@ export class GeoAccessOrchestrator { definition.actions, resourceArn, acceptor(this.getInstanceProps).identifier, + resourceName, this.resourceStack, ); acceptor(this.getInstanceProps).acceptResourceAccess( diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts index 76b46b218f2..9d0879d3d5b 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.test.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -10,6 +10,7 @@ void describe('GeoAccessPolicyFactory', () => { let geoAccessPolicyFactory: GeoAccessPolicyFactory; const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + const testResourceName = 'testResource'; beforeEach(() => { const app = new App(); @@ -23,6 +24,7 @@ void describe('GeoAccessPolicyFactory', () => { [], testResourceArn, 'test-role', + testResourceName, stack, ), ); @@ -33,6 +35,7 @@ void describe('GeoAccessPolicyFactory', () => { ['get'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -45,7 +48,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -62,6 +65,7 @@ void describe('GeoAccessPolicyFactory', () => { ['autocomplete'], testResourceArn, 'guest', + testResourceName, stack, ); @@ -74,7 +78,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-guest-access-policy', + PolicyName: 'geo-testResource-guest-access-policy', PolicyDocument: { Statement: [ { @@ -91,6 +95,7 @@ void describe('GeoAccessPolicyFactory', () => { ['geocode'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -103,7 +108,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -120,6 +125,7 @@ void describe('GeoAccessPolicyFactory', () => { ['search'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -132,7 +138,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -154,6 +160,7 @@ void describe('GeoAccessPolicyFactory', () => { ['create'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -166,7 +173,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -183,6 +190,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -195,7 +203,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -217,6 +225,7 @@ void describe('GeoAccessPolicyFactory', () => { ['update'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -229,7 +238,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -250,6 +259,7 @@ void describe('GeoAccessPolicyFactory', () => { ['delete'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -262,7 +272,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -279,6 +289,7 @@ void describe('GeoAccessPolicyFactory', () => { ['list'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -291,7 +302,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -308,6 +319,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read', 'create', 'update'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -320,7 +332,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -346,6 +358,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read'], testResourceArn, 'custom-role-token', + testResourceName, stack, ); @@ -358,7 +371,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-custom-role-token-access-policy', + PolicyName: 'geo-testResource-custom-role-token-access-policy', PolicyDocument: { Statement: [ { @@ -381,6 +394,7 @@ void describe('GeoAccessPolicyFactory', () => { ['get'], mapResourceArn, 'authenticated', + testResourceName, stack, ); @@ -393,7 +407,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -412,6 +426,7 @@ void describe('GeoAccessPolicyFactory', () => { ['search', 'geocode'], placeIndexArn, 'authenticated', + testResourceName, stack, ); @@ -424,7 +439,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -448,6 +463,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read', 'update'], testResourceArn, 'group-admin', + testResourceName, stack, ); @@ -460,7 +476,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-group-admin-access-policy', + PolicyName: 'geo-testResource-group-admin-access-policy', PolicyDocument: { Statement: [ { diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index a0efbc69b9c..5c394860091 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -12,6 +12,7 @@ export class GeoAccessPolicyFactory { permissions: string[], // organize create policy such that one resource type maps to the actions resourceArn: string, roleToken: string, + resourceName: string, stack: Stack, ) => { if (permissions.length === 0) { @@ -29,7 +30,7 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - const policyIDName: string = `geo-${roleToken}-access-policy`; + const policyIDName: string = `geo-${resourceName}-${roleToken}-access-policy`; return new Policy(stack, policyIDName, { policyName: policyIDName, statements: [policyStatement], diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 3419cf8bb5f..858a27b381a 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -95,6 +95,7 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyMap.getResourceArn(), 'map', + amplifyMap.name, ); const geoAspects = Aspects.of(Stack.of(amplifyMap)); diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 1620e915c87..9c8de2a9f63 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -99,6 +99,7 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { geoAccessOrchestrator.orchestrateGeoAccess( amplifyPlace.getResourceArn(), 'place', + amplifyPlace.name, ); const geoAspects = Aspects.of(Stack.of(amplifyPlace)); diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 705af068c4e..b27ed5d7bfe 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -380,26 +380,36 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo": z.ZodOptional; payload: z.ZodObject<{ - aws_region: z.ZodString; - geofence_collections: z.ZodOptional; + geoRegion: z.ZodString; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; + geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }>]>>; }, "strip", z.ZodTypeAny, { @@ -477,8 +487,10 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; } | undefined; }, { @@ -556,8 +568,10 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; } | undefined; }>; @@ -749,26 +763,36 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ - aws_region: z.ZodString; - geofence_collections: z.ZodOptional; + geoRegion: z.ZodString; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; + geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }>]>; From 4b62242ea8534feb2569815993edc6bb1b66a220 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 09:39:46 -0700 Subject: [PATCH 23/93] adding changeset for changed packages and updated README --- .changeset/old-dodos-create.md | 7 +++++++ packages/backend-geo/README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/old-dodos-create.md diff --git a/.changeset/old-dodos-create.md b/.changeset/old-dodos-create.md new file mode 100644 index 00000000000..7cc98260461 --- /dev/null +++ b/.changeset/old-dodos-create.md @@ -0,0 +1,7 @@ +--- +'@aws-amplify/client-config': patch +'@aws-amplify/backend-geo': patch +'create-amplify': patch +--- + +Adding client configuration for auto-generated backend geo resource outputs. diff --git a/packages/backend-geo/README.md b/packages/backend-geo/README.md index 793417be040..9ad14260ef6 100644 --- a/packages/backend-geo/README.md +++ b/packages/backend-geo/README.md @@ -1,3 +1,3 @@ # Description -Replace with a description of this package +This package defines an L3 construct for the Amplify Geo category. It includes the L3 CDK constructs and resources along with 3 exposed endpoints `defineMap`, `definePlace`, and `defineCollection` to provision those resources. From 8e4ff91e0b79581d806ce8454b0ecfd9d16b2359 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 15:22:11 -0700 Subject: [PATCH 24/93] first iteration of api key support for map and place resources --- .changeset/bitter-emus-sell.md | 5 +++ packages/backend-geo/API.md | 30 +++++++++++++--- packages/backend-geo/src/access_builder.ts | 16 +++++++++ .../src/geo_access_orchestrator.ts | 10 ++++++ .../src/geo_access_policy_factory.ts | 12 +++++++ packages/backend-geo/src/map_factory.ts | 15 ++++++++ packages/backend-geo/src/map_resource.ts | 21 +++++++++--- packages/backend-geo/src/place_factory.ts | 19 +++++++++-- packages/backend-geo/src/place_resource.ts | 19 +++++++---- packages/backend-geo/src/types.ts | 34 ++++++++++++++++--- 10 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 .changeset/bitter-emus-sell.md diff --git a/.changeset/bitter-emus-sell.md b/.changeset/bitter-emus-sell.md new file mode 100644 index 00000000000..0080d78b656 --- /dev/null +++ b/.changeset/bitter-emus-sell.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': patch +--- + +Adding API key support for map and place resources as part of L3 Geo Construct. diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 947948b9c3f..7cfeed30f71 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -5,7 +5,10 @@ ```ts import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; +import { ApiKey } from '@aws-cdk/aws-location-alpha'; +import { ApiKeyProps } from '@aws-cdk/aws-location-alpha'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; @@ -36,7 +39,9 @@ export type AmplifyMapFactoryProps = Omit; +export type AmplifyMapProps = Omit & { + apiKeyProps?: GeoApiKeyProps; +}; // @public export type AmplifyPlaceFactoryProps = Omit & { @@ -44,7 +49,9 @@ export type AmplifyPlaceFactoryProps = Omit; +export type AmplifyPlaceProps = Omit & { + apiKeyProps?: GeoApiKeyProps; +}; // @public export type CollectionResources = { @@ -69,6 +76,7 @@ export type GeoAccessBuilder = { authenticated: GeoActionBuilder; guest: GeoActionBuilder; groups: (groupNames: string[]) => GeoActionBuilder; + apiKey?: GeoActionBuilder; }; // @public (undocumented) @@ -89,19 +97,33 @@ export type GeoActionBuilder = { to: (actions: string[]) => GeoAccessDefinition; }; +// @public (undocumented) +export type GeoApiKeyProps = Omit; + +// @public (undocumented) +export type GeoCollectionAccessGenerator = (allow: Omit) => GeoAccessDefinition[]; + // @public (undocumented) export type GeoResourceType = 'map' | 'place' | 'collection'; // @public export type MapResources = { - policies: Policy[]; region: string; + policies: Policy[]; + apiKey?: ApiKey; + cfnResources: { + cfnAPIKey?: CfnAPIKey; + }; }; // @public export type PlaceResources = { - policies: Policy[]; region: string; + policies: Policy[]; + apiKey: ApiKey; + cfnResources: { + cfnAPIKey: CfnAPIKey; + }; }; // @public (undocumented) diff --git a/packages/backend-geo/src/access_builder.ts b/packages/backend-geo/src/access_builder.ts index 75c3f522003..f425313b530 100644 --- a/packages/backend-geo/src/access_builder.ts +++ b/packages/backend-geo/src/access_builder.ts @@ -57,6 +57,22 @@ export const roleAccessBuilder: GeoAccessBuilder = { actions, }), }), + apiKey: { + // access for api keys + to: (actions) => ({ + getAccessAcceptors: [], + actions, + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: `Access definition for api key specified multiple times.`, + resolution: `Combine all access definitions for api key into one access rule.`, + }, + }, + ], + }), + }, }; const getAuthRoleAcceptor = ( diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 4e643c76322..3400f479990 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -22,6 +22,8 @@ import { Stack } from 'aws-cdk-lib'; export class GeoAccessOrchestrator { private resourceStack: Stack; private policies: Policy[] = []; + private apiKeyActions: string[] = []; + /** * Constructs an instance of GeoAccessOrchestrator * @param geoAccessGenerator - access permissions defined by user for the resource @@ -92,6 +94,11 @@ export class GeoAccessOrchestrator { uniqueActionSet.add(action); }); + // api key has no acceptors + if (!definition.getAccessAcceptors.length) { + this.apiKeyActions = definition.actions; + } + definition.getAccessAcceptors.forEach((acceptor) => { // for each acceptor within auth, guest, or user groups const policy: Policy = this.geoPolicyFactory.createPolicy( @@ -111,6 +118,9 @@ export class GeoAccessOrchestrator { return this.policies; }; + + orchestrateKeyAccess = () => + this.geoPolicyFactory.generateKeyActions(this.apiKeyActions); } /** diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 5c394860091..94db3c9af8a 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -36,6 +36,18 @@ export class GeoAccessPolicyFactory { statements: [policyStatement], }); }; + + generateKeyActions = (actions: string[]): string[] => { + const keyPermissions: string[] = []; + + actions.forEach((action) => { + actionDirectory[action].forEach((permission) => { + keyPermissions.push(permission); + }); + }); + + return keyPermissions; + }; } const actionDirectory: Record = { diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 858a27b381a..95621d1f7c8 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -13,6 +13,7 @@ import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; import { AmplifyMap } from './map_resource.js'; import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; +import { AllowMapsAction } from '@aws-cdk/aws-location-alpha'; /** * Construct Factory for AmplifyMap @@ -98,6 +99,20 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { amplifyMap.name, ); + // orchestrateGeoAccess already called and ApiKey actions processed + const mapActions = + geoAccessOrchestrator.orchestrateKeyAccess() as AllowMapsAction[]; + + if (!mapActions.length && this.props.apiKeyProps) { + throw new AmplifyUserError('NoApiKeyAccessError', { + message: + 'No API key can be created for maps without access definitions defined for it.', + resolution: 'Add at least one map action in the access definition.', + }); + } else { + amplifyMap.generateApiKey(mapActions); + } + const geoAspects = Aspects.of(Stack.of(amplifyMap)); if (!geoAspects.all.length) { geoAspects.add( diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 0f6b0933ccb..86c96a3d024 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -1,6 +1,8 @@ import { AmplifyMapProps, MapResources } from './types.js'; import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { AllowMapsAction, ApiKey } from '@aws-cdk/aws-location-alpha'; import { Aws, Resource } from 'aws-cdk-lib'; +import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { Construct } from 'constructs'; /** @@ -13,6 +15,7 @@ export class AmplifyMap readonly resources: MapResources; readonly id: string; readonly name: string; + private readonly props: AmplifyMapProps; /** * Creates an instance of AmplifyMap @@ -22,13 +25,23 @@ export class AmplifyMap this.name = props.name; this.id = id; - this.resources = { - region: this.stack.region, - policies: [], - }; + this.props = props; + + this.resources.region = this.stack.region; + this.resources.policies = []; } getResourceArn = (): string => { return `arn:${Aws.PARTITION}:geo-maps:${this.stack.region}::provider/default`; }; + + generateApiKey = (actions: AllowMapsAction[]) => { + this.resources.apiKey = new ApiKey(this, this.props.name, { + ...this.props.apiKeyProps, + allowMapsActions: actions, + }); + + this.resources.cfnResources.cfnAPIKey = + this.resources.apiKey.node.findChild('Resource') as CfnAPIKey; + }; } diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 9c8de2a9f63..a084f708521 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -13,6 +13,7 @@ import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; import { AmplifyPlace } from './place_resource.js'; import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; +import { AllowPlacesAction } from '@aws-cdk/aws-location-alpha'; /** * Construct Factory for AmplifyPlace @@ -82,12 +83,12 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, }); - Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); - if (!this.props.access) { return amplifyPlace; } + Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( this.props.access, this.getInstanceProps, @@ -102,6 +103,20 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { amplifyPlace.name, ); + // orchestrateGeoAccess already called and ApiKey actions processed + const placeActions = + geoAccessOrchestrator.orchestrateKeyAccess() as AllowPlacesAction[]; + + if (!placeActions.length && this.props.apiKeyProps) { + throw new AmplifyUserError('NoApiKeyAccessError', { + message: + 'No API key can be created for places without access definitions defined for it.', + resolution: 'Add at least one place action in the access definition.', + }); + } else { + amplifyPlace.generateApiKey(placeActions); + } + const geoAspects = Aspects.of(Stack.of(amplifyPlace)); if (!geoAspects.all.length) { geoAspects.add( diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index f928cc304cd..f9caac1394a 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -1,6 +1,8 @@ import { AmplifyPlaceProps, PlaceResources } from './types.js'; import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { AllowPlacesAction, ApiKey } from '@aws-cdk/aws-location-alpha'; import { Aws, Resource } from 'aws-cdk-lib'; +import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { Construct } from 'constructs'; /** @@ -13,6 +15,7 @@ export class AmplifyPlace readonly resources: PlaceResources; readonly id: string; readonly name: string; + private readonly props: AmplifyPlaceProps; /** * Creates an instance of AmplifyPlace @@ -23,17 +26,21 @@ export class AmplifyPlace this.name = props.name; this.id = id; - this.resources = { - region: this.stack.region, - policies: [], - }; + this.resources.region = this.stack.region; + this.resources.policies = []; } getResourceArn = (): string => { return `arn:${Aws.PARTITION}:geo-places:${this.stack.region}::provider/default`; }; - getResourceName = (): string => { - return this.name; + generateApiKey = (actions: AllowPlacesAction[]) => { + this.resources.apiKey = new ApiKey(this, this.props.name, { + ...this.props.apiKeyProps, + allowPlacesActions: actions, + }); + + this.resources.cfnResources.cfnAPIKey = + this.resources.apiKey.node.findChild('Resource') as CfnAPIKey; }; } diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 736383b539e..784f296fba4 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -5,10 +5,12 @@ import { } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { + ApiKey, + ApiKeyProps, GeofenceCollection, GeofenceCollectionProps, } from '@aws-cdk/aws-location-alpha'; -import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { CfnAPIKey, CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; @@ -74,12 +76,16 @@ export type AmplifyCollectionFactoryProps = Omit< export type AmplifyMapProps = Omit< AmplifyCollectionProps, 'collectionProps' | 'isDefault' ->; +> & { + apiKeyProps?: GeoApiKeyProps; +}; export type AmplifyPlaceProps = Omit< AmplifyCollectionProps, 'collectionProps' | 'isDefault' ->; +> & { + apiKeyProps?: GeoApiKeyProps; +}; export type AmplifyCollectionProps = { name: string; @@ -88,13 +94,22 @@ export type AmplifyCollectionProps = { outputStorageStrategy?: BackendOutputStorageStrategy; }; +export type GeoApiKeyProps = Omit< + ApiKeyProps, + 'allowMapsActions' | 'allowPlacesActions' +>; + /** * Backend-accessible resources from AmplifyMap * @param policies - access policies of the frontend-accessible map resource */ export type MapResources = { - policies: Policy[]; region: string; + policies: Policy[]; + apiKey?: ApiKey; + cfnResources: { + cfnAPIKey?: CfnAPIKey; + }; }; /** @@ -102,8 +117,12 @@ export type MapResources = { * @param policies - access policies of the frontend-accessible place resource */ export type PlaceResources = { - policies: Policy[]; region: string; + policies: Policy[]; + apiKey: ApiKey; + cfnResources: { + cfnAPIKey: CfnAPIKey; + }; }; /** @@ -126,10 +145,15 @@ export type GeoAccessGenerator = ( allow: GeoAccessBuilder, ) => GeoAccessDefinition[]; +export type GeoCollectionAccessGenerator = ( + allow: Omit, +) => GeoAccessDefinition[]; + export type GeoAccessBuilder = { authenticated: GeoActionBuilder; guest: GeoActionBuilder; groups: (groupNames: string[]) => GeoActionBuilder; + apiKey?: GeoActionBuilder; }; export type GeoActionBuilder = { From 3ca21c9f9bc0cc6f79ed8e59fdd40f9e27c280bd Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 15:51:57 -0700 Subject: [PATCH 25/93] small fixes --- .changeset/puny-hornets-brush.md | 2 +- .eslint_dictionary.json | 3 +++ package.json | 5 ----- packages/backend-geo/package.json | 3 ++- scripts/check_package_versions.ts | 1 + 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md index 9486bd36484..23aae824a41 100644 --- a/.changeset/puny-hornets-brush.md +++ b/.changeset/puny-hornets-brush.md @@ -3,4 +3,4 @@ '@aws-amplify/backend-output-schemas': patch --- -This changeset involves the introduction of a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. +Introduces a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. diff --git a/.eslint_dictionary.json b/.eslint_dictionary.json index 946c5570d81..fa02bbf53af 100644 --- a/.eslint_dictionary.json +++ b/.eslint_dictionary.json @@ -76,7 +76,9 @@ "frontmatter", "fullname", "func", + "geo", "geofence", + "geofences", "getaddrinfo", "gitignore", "gitignored", @@ -213,6 +215,7 @@ "userpool", "utf", "utimes", + "validators", "verdaccio", "verifier", "versioned", diff --git a/package.json b/package.json index 99dca9415b0..29cc9161332 100644 --- a/package.json +++ b/package.json @@ -117,10 +117,5 @@ "overrides": { "minimatch": "10.0.1", "@graphql-tools/merge": "9.0.22" - }, - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index bb3219ca9f5..e8f82ea102e 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -12,6 +12,7 @@ "require": "./lib/index.js" } }, + "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { "update:api": "api-extractor run --local" @@ -25,6 +26,6 @@ "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" } } diff --git a/scripts/check_package_versions.ts b/scripts/check_package_versions.ts index 31b5208a0d7..b2ea5d0d62f 100644 --- a/scripts/check_package_versions.ts +++ b/scripts/check_package_versions.ts @@ -14,6 +14,7 @@ const getExpectedMajorVersion = (packageName: string) => { return '0.'; case '@aws-amplify/backend-deployer': case '@aws-amplify/cli-core': + case '@aws-amplify/backend-geo': case '@aws-amplify/sandbox': return '2.'; default: From 1cf96ce17e046bd35305dd92a29fc66bfb494c87 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 16:07:43 -0700 Subject: [PATCH 26/93] bumping packages --- packages/backend-geo/package.json | 2 +- scripts/check_package_versions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index e8f82ea102e..85fe77c11e8 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -19,7 +19,7 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" }, "dependencies": { diff --git a/scripts/check_package_versions.ts b/scripts/check_package_versions.ts index b2ea5d0d62f..8b01802bf0a 100644 --- a/scripts/check_package_versions.ts +++ b/scripts/check_package_versions.ts @@ -11,10 +11,10 @@ const packagePaths = await glob('./packages/*'); const getExpectedMajorVersion = (packageName: string) => { switch (packageName) { case 'ampx': + case '@aws-amplify/backend-geo': return '0.'; case '@aws-amplify/backend-deployer': case '@aws-amplify/cli-core': - case '@aws-amplify/backend-geo': case '@aws-amplify/sandbox': return '2.'; default: From d8b39ef5c805148ad7c247df5f2d87d397556485 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Tue, 29 Jul 2025 11:30:55 -0700 Subject: [PATCH 27/93] unit testing for backend-geo files complete --- .../src/geo_access_orchestrator.test.ts | 305 ++++++++++++++++++ .../src/geo_access_orchestrator.ts | 2 + .../src/geo_access_policy_factory.test.ts | 130 ++++++++ .../src/geo_access_policy_factory.ts | 23 +- packages/backend-geo/src/map_resource.test.ts | 87 +++++ packages/backend-geo/src/map_resource.ts | 9 +- .../backend-geo/src/place_resource.test.ts | 86 +++++ packages/backend-geo/src/place_resource.ts | 10 +- packages/backend-geo/src/types.ts | 8 +- 9 files changed, 649 insertions(+), 11 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index 263a18bb956..482138c17d4 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -7,6 +7,10 @@ import { import { App, Stack } from 'aws-cdk-lib'; import assert from 'node:assert'; import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { + AllowMapsAction, + AllowPlacesAction, +} from '@aws-cdk/aws-location-alpha'; void describe('GeoAccessOrchestrator', () => { void describe('orchestrateGeoAccess', () => { @@ -560,6 +564,307 @@ void describe('GeoAccessOrchestrator', () => { ); }); }); + + void describe('API key functionality', () => { + let stack: Stack; + const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = []; + const testResourceArn = 'arn:aws:geo-maps:us-east-1::provider/default'; + const testResourceName = 'testMapResource'; + + beforeEach(() => { + stack = createStackAndSetContext(); + }); + + void it('handles API key access definition with empty acceptors', () => { + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get'], + getAccessAcceptors: [], // Empty for API key + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + const policies = geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ); + + // Should return empty policies array for API key + assert.equal(policies.length, 0); + }); + + void it('orchestrateKeyAccess returns correct actions for map', () => { + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get'], + getAccessAcceptors: [], // API key definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + // First orchestrate geo access to set up API key actions + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ); + + // Then get the key actions + const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + + assert.deepStrictEqual(keyActions, [ + AllowMapsAction.GET_STATIC_MAP, + AllowMapsAction.GET_TILE, + ]); + }); + + void it('orchestrateKeyAccess returns correct actions for place', () => { + const placeResourceArn = 'arn:aws:geo-places:us-east-1::provider/default'; + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['search', 'geocode'], + getAccessAcceptors: [], // API key definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess( + placeResourceArn, + 'place', + 'testPlaceResource', + ); + + const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + + assert.deepStrictEqual(keyActions, [ + AllowPlacesAction.GET_PLACE, + AllowPlacesAction.SEARCH_NEARBY, + AllowPlacesAction.SEARCH_TEXT, + AllowPlacesAction.SUGGEST, + AllowPlacesAction.GEOCODE, + AllowPlacesAction.REVERSE_GEOCODE, + ]); + }); + + void it('orchestrateKeyAccess returns empty array when no API key actions', () => { + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: [], + getAccessAcceptors: [], // Not API key - has acceptors + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: `Access definition for api key specified multiple times.`, + resolution: `Combine all access definitions for api key into one access rule.`, + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ); + + const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + + assert.deepStrictEqual(keyActions, []); + }); + + void it('handles mixed API key and role-based access', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get'], + getAccessAcceptors: [], // API key definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + { + actions: ['get'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], // Role-based definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + const policies = geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ); + + // Should have one policy for the role-based access + assert.equal(policies.length, 1); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + + // Should also have API key actions available + const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + assert.deepStrictEqual(keyActions, [ + AllowMapsAction.GET_STATIC_MAP, + AllowMapsAction.GET_TILE, + ]); + }); + + void it('handles multiple API key actions', () => { + const placeResourceArn = 'arn:aws:geo-places:us-east-1::provider/default'; + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['search', 'geocode', 'autocomplete'], + getAccessAcceptors: [], // API key definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess( + placeResourceArn, + 'place', + 'testPlaceResource', + ); + + const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + + assert.deepStrictEqual(keyActions, [ + AllowPlacesAction.GET_PLACE, + AllowPlacesAction.SEARCH_NEARBY, + AllowPlacesAction.SEARCH_TEXT, + AllowPlacesAction.SUGGEST, + AllowPlacesAction.GEOCODE, + AllowPlacesAction.REVERSE_GEOCODE, + AllowPlacesAction.AUTOCOMPLETE, + ]); + }); + + void it('validates API key actions for resource type', () => { + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['create'], // Invalid for map resource + getAccessAcceptors: [], // API key definition + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'api key', + validationErrorOptions: { + message: 'API key access defined multiple times', + resolution: 'Combine API key access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ), + new AmplifyUserError('ActionNotFoundError', { + message: + 'Desired access action not found for the specific map resource.', + resolution: + 'Please refer to specific map access actions for more information.', + }), + ); + }); + }); }); const createStackAndSetContext = (): Stack => { diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 3400f479990..8e85b45f669 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -59,6 +59,8 @@ export class GeoAccessOrchestrator { this.roleAccessBuilder, ); + this.apiKeyActions.length = 0; + const uniqueRoleTokenSet = new Set(); geoAccessDefinitions.forEach((definition) => { diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts index 9d0879d3d5b..0f63a996a72 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.test.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -495,4 +495,134 @@ void describe('GeoAccessPolicyFactory', () => { }, }); }); + + void describe('API key functionality', () => { + void it('generateKeyActions returns correct actions for single map action', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions(['get']); + + assert.deepStrictEqual(keyActions, [ + 'geo-maps:GetStaticMap', + 'geo-maps:GetTile', + ]); + }); + + void it('generateKeyActions returns correct actions for single place action', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions(['search']); + + assert.deepStrictEqual(keyActions, [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ]); + }); + + void it('generateKeyActions returns correct actions for geocode', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions(['geocode']); + + assert.deepStrictEqual(keyActions, [ + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + ]); + }); + + void it('generateKeyActions returns correct actions for autocomplete', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'autocomplete', + ]); + + assert.deepStrictEqual(keyActions, ['geo-places:Autocomplete']); + }); + + void it('generateKeyActions handles multiple actions', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'search', + 'geocode', + 'autocomplete', + ]); + + assert.deepStrictEqual(keyActions, [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + 'geo-places:Autocomplete', + ]); + }); + + void it('generateKeyActions handles mixed map and place actions', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'get', + 'search', + ]); + + assert.deepStrictEqual(keyActions, [ + 'geo-maps:GetStaticMap', + 'geo-maps:GetTile', + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ]); + }); + + void it('generateKeyActions returns empty array for empty input', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([]); + + assert.deepStrictEqual(keyActions, []); + }); + + void it('generateKeyActions handles duplicate actions', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'get', + 'get', + ]); + + // Should include duplicates as they appear in the input + assert.deepStrictEqual(keyActions, [ + 'geo-maps:GetStaticMap', + 'geo-maps:GetTile', + 'geo-maps:GetStaticMap', + 'geo-maps:GetTile', + ]); + }); + + void it('generateKeyActions handles all place actions', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'search', + 'geocode', + 'autocomplete', + ]); + + assert.deepStrictEqual(keyActions, [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + 'geo-places:Autocomplete', + ]); + }); + + void it('generateKeyActions preserves action order', () => { + const keyActions = geoAccessPolicyFactory.generateKeyActions([ + 'autocomplete', + 'get', + 'search', + ]); + + assert.deepStrictEqual(keyActions, [ + 'geo-places:Autocomplete', + 'geo-maps:GetStaticMap', + 'geo-maps:GetTile', + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ]); + }); + }); }); diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 94db3c9af8a..fd6a21090ed 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -1,6 +1,11 @@ import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { AmplifyFault } from '@aws-amplify/platform-core'; import { Stack } from 'aws-cdk-lib'; +import { + AllowMapsAction, + AllowPlacesAction, +} from '@aws-cdk/aws-location-alpha'; +import { GeoApiActionType } from './types.js'; /** * Geo Access Policy Factory @@ -38,12 +43,10 @@ export class GeoAccessPolicyFactory { }; generateKeyActions = (actions: string[]): string[] => { - const keyPermissions: string[] = []; + const keyPermissions: GeoApiActionType[] = []; actions.forEach((action) => { - actionDirectory[action].forEach((permission) => { - keyPermissions.push(permission); - }); + keyPermissions.push(...apiKeyActionDirectory[action]); }); return keyPermissions; @@ -75,3 +78,15 @@ const actionDirectory: Record = { delete: ['geo:BatchDeleteGeofence', 'geo:DeleteGeofenceCollection'], list: ['geo:ListGeofences', 'geo:ListGeofenceCollections'], }; + +const apiKeyActionDirectory: Record = { + get: [AllowMapsAction.GET_STATIC_MAP, AllowMapsAction.GET_TILE], + autocomplete: [AllowPlacesAction.AUTOCOMPLETE], + geocode: [AllowPlacesAction.GEOCODE, AllowPlacesAction.REVERSE_GEOCODE], + search: [ + AllowPlacesAction.GET_PLACE, + AllowPlacesAction.SEARCH_NEARBY, + AllowPlacesAction.SEARCH_TEXT, + AllowPlacesAction.SUGGEST, + ], +}; diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts index 713a69f282a..b2157a06346 100644 --- a/packages/backend-geo/src/map_resource.test.ts +++ b/packages/backend-geo/src/map_resource.test.ts @@ -2,6 +2,8 @@ import { beforeEach, describe, it } from 'node:test'; import { AmplifyMap } from './map_resource.js'; import { App, Stack } from 'aws-cdk-lib'; import assert from 'node:assert'; +import { AllowMapsAction } from '@aws-cdk/aws-location-alpha'; +import { Template } from 'aws-cdk-lib/assertions'; void describe('AmplifyMap', () => { let app: App; @@ -104,4 +106,89 @@ void describe('AmplifyMap', () => { assert.ok(map.stack); }); }); + + void describe('API key functionality', () => { + void it('initializes without API key', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.equal(map.resources.apiKey, undefined); + assert.equal(map.resources.cfnResources.cfnAPIKey, undefined); + }); + + void it('generates API key with provided actions', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + map.generateApiKey([AllowMapsAction.GET_TILE]); + + assert.ok(map.resources.apiKey); + assert.ok(map.resources.cfnResources.cfnAPIKey); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-maps:GetTile'], + }, + }); + }); + + void it('generates API key with empty actions array', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + map.generateApiKey([]); + + assert.ok(map.resources.apiKey); + assert.ok(map.resources.cfnResources.cfnAPIKey); + }); + + void it('generates API key with multiple actions', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + map.generateApiKey([ + AllowMapsAction.GET_STATIC_MAP, + AllowMapsAction.GET_TILE, + ]); + + assert.ok(map.resources.apiKey); + + assert.ok(map.resources.cfnResources.cfnAPIKey); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + }, + }); + }); + + void it('generates API key with custom apiKeyProps', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + apiKeyProps: { + description: 'Custom API key for maps', + }, + }); + + map.generateApiKey([AllowMapsAction.GET_STATIC_MAP]); + + assert.ok(map.resources.apiKey); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-maps:GetStaticMap'], + }, + }); + assert.equal( + map.resources.cfnResources.cfnAPIKey?.description, + 'Custom API key for maps', + ); + }); + }); }); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 86c96a3d024..32c621dcc6e 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -27,8 +27,11 @@ export class AmplifyMap this.props = props; - this.resources.region = this.stack.region; - this.resources.policies = []; + this.resources = { + region: this.stack.region, + policies: [], + cfnResources: {}, + }; } getResourceArn = (): string => { @@ -38,9 +41,9 @@ export class AmplifyMap generateApiKey = (actions: AllowMapsAction[]) => { this.resources.apiKey = new ApiKey(this, this.props.name, { ...this.props.apiKeyProps, + noExpiry: this.props.apiKeyProps?.noExpiry ?? true, allowMapsActions: actions, }); - this.resources.cfnResources.cfnAPIKey = this.resources.apiKey.node.findChild('Resource') as CfnAPIKey; }; diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts index bc135096b18..00766cdc27f 100644 --- a/packages/backend-geo/src/place_resource.test.ts +++ b/packages/backend-geo/src/place_resource.test.ts @@ -2,6 +2,8 @@ import { beforeEach, describe, it } from 'node:test'; import { AmplifyPlace } from './place_resource.js'; import { App, Stack } from 'aws-cdk-lib'; import assert from 'node:assert'; +import { Template } from 'aws-cdk-lib/assertions'; +import { AllowPlacesAction } from '@aws-cdk/aws-location-alpha'; void describe('AmplifyPlace', () => { let app: App; @@ -108,4 +110,88 @@ void describe('AmplifyPlace', () => { assert.ok(place.stack); }); }); + + void describe('API key functionality', () => { + void it('initializes without API key', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.equal(place.resources.apiKey, undefined); + assert.equal(place.resources.cfnResources.cfnAPIKey, undefined); + }); + + void it('generates API key with provided actions', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + place.generateApiKey([AllowPlacesAction.SEARCH_TEXT]); + + assert.ok(place.resources.apiKey); + assert.ok(place.resources.cfnResources.cfnAPIKey); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-places:SearchText'], + }, + }); + }); + + void it('generates API key with empty actions array', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + place.generateApiKey([]); + + assert.ok(place.resources.apiKey); + assert.ok(place.resources.cfnResources.cfnAPIKey); + }); + + void it('generates API key with multiple actions', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + place.generateApiKey([ + AllowPlacesAction.SEARCH_TEXT, + AllowPlacesAction.GET_PLACE, + ]); + + assert.ok(place.resources.apiKey); + assert.ok(place.resources.cfnResources.cfnAPIKey); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-places:SearchText', 'geo-places:GetPlace'], + }, + }); + }); + + void it('generates API key with custom apiKeyProps', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + apiKeyProps: { + description: 'Custom API key for places', + }, + }); + + place.generateApiKey([AllowPlacesAction.SEARCH_TEXT]); + + assert.ok(place.resources.apiKey); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::APIKey', { + Restrictions: { + AllowActions: ['geo-places:SearchText'], + }, + }); + assert.equal( + place.resources.cfnResources.cfnAPIKey?.description, + 'Custom API key for places', + ); + }); + }); }); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index f9caac1394a..aaf807d014f 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -26,8 +26,13 @@ export class AmplifyPlace this.name = props.name; this.id = id; - this.resources.region = this.stack.region; - this.resources.policies = []; + this.props = props; + + this.resources = { + region: this.stack.region, + policies: [], + cfnResources: {}, + }; } getResourceArn = (): string => { @@ -37,6 +42,7 @@ export class AmplifyPlace generateApiKey = (actions: AllowPlacesAction[]) => { this.resources.apiKey = new ApiKey(this, this.props.name, { ...this.props.apiKeyProps, + noExpiry: this.props.apiKeyProps?.noExpiry ?? true, allowPlacesActions: actions, }); diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 784f296fba4..ae5f1c07509 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -5,6 +5,8 @@ import { } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { + AllowMapsAction, + AllowPlacesAction, ApiKey, ApiKeyProps, GeofenceCollection, @@ -119,9 +121,9 @@ export type MapResources = { export type PlaceResources = { region: string; policies: Policy[]; - apiKey: ApiKey; + apiKey?: ApiKey; cfnResources: { - cfnAPIKey: CfnAPIKey; + cfnAPIKey?: CfnAPIKey; }; }; @@ -179,4 +181,6 @@ export const resourceActionRecord: Record = { collection: ['create', 'read', 'update', 'delete', 'list'], }; +export type GeoApiActionType = AllowMapsAction | AllowPlacesAction; + export type GeoResourceType = 'map' | 'place' | 'collection'; From 838ae032609c07479f8cfbd6c92d04f5235e2001 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Tue, 29 Jul 2025 12:15:00 -0700 Subject: [PATCH 28/93] updating API.md --- packages/backend-geo/API.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 7cfeed30f71..28b82fca248 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -4,6 +4,8 @@ ```ts +import { AllowMapsAction } from '@aws-cdk/aws-location-alpha'; +import { AllowPlacesAction } from '@aws-cdk/aws-location-alpha'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { ApiKey } from '@aws-cdk/aws-location-alpha'; import { ApiKeyProps } from '@aws-cdk/aws-location-alpha'; @@ -97,6 +99,9 @@ export type GeoActionBuilder = { to: (actions: string[]) => GeoAccessDefinition; }; +// @public (undocumented) +export type GeoApiActionType = AllowMapsAction | AllowPlacesAction; + // @public (undocumented) export type GeoApiKeyProps = Omit; @@ -120,9 +125,9 @@ export type MapResources = { export type PlaceResources = { region: string; policies: Policy[]; - apiKey: ApiKey; + apiKey?: ApiKey; cfnResources: { - cfnAPIKey: CfnAPIKey; + cfnAPIKey?: CfnAPIKey; }; }; From ea6fc856ce942a54ffc16ce42838f91bf647d3f3 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:07:14 -0700 Subject: [PATCH 29/93] adding output schemas --- packages/backend-output-schemas/src/geo/index.ts | 8 ++++++++ packages/backend-output-schemas/src/geo/v1.ts | 10 ++++++++++ packages/backend-output-schemas/src/index.ts | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 packages/backend-output-schemas/src/geo/index.ts create mode 100644 packages/backend-output-schemas/src/geo/v1.ts diff --git a/packages/backend-output-schemas/src/geo/index.ts b/packages/backend-output-schemas/src/geo/index.ts new file mode 100644 index 00000000000..4c0fddf1e22 --- /dev/null +++ b/packages/backend-output-schemas/src/geo/index.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { geoOutputSchema as geoOutputSchemaV1 } from './v1'; + +export const versionedGeoOutputSchema = z.discriminatedUnion('version', [ + geoOutputSchemaV1, +]); + +export type GeoOutput = z.infer; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts new file mode 100644 index 00000000000..2c74fc45cab --- /dev/null +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const geoOutputSchema = z.object({ + version: z.literal('1'), + payload: z.object({ + defaultCollection: z.string(), + geoRegion: z.string(), + collections: z.string(z.array(z.string()).optional()), // JSON serialized array of collection names + }), +}); diff --git a/packages/backend-output-schemas/src/index.ts b/packages/backend-output-schemas/src/index.ts index 11bfb14cd2d..0f4348fddee 100644 --- a/packages/backend-output-schemas/src/index.ts +++ b/packages/backend-output-schemas/src/index.ts @@ -6,6 +6,7 @@ import { versionedStackOutputSchema } from './stack/index.js'; import { versionedCustomOutputSchema } from './custom'; import { versionedFunctionOutputSchema } from './function/index.js'; import { versionedAIConversationOutputSchema } from './ai/conversation/index.js'; +import { versionedGeoOutputSchema } from './geo/index.js'; /** * The auth, graphql and storage exports here are duplicated from the submodule exports in the package.json file @@ -99,6 +100,20 @@ export * from './ai/conversation/index.js'; */ export const aiConversationOutputKey = 'AWS::Amplify::AI::Conversation'; +/** + * ---------- Geo exports ---------- + */ + +/** + * re-export the AI conversation output schema + */ +export * from './geo/index.js'; + +/** + * Expected key that AI conversation output is stored under + */ +export const geoOutputKey = 'AWS::Amplify::Geo'; + /** * ---------- Unified exports ---------- */ @@ -115,6 +130,7 @@ export const unifiedBackendOutputSchema = z.object({ [customOutputKey]: versionedCustomOutputSchema.optional(), [functionOutputKey]: versionedFunctionOutputSchema.optional(), [aiConversationOutputKey]: versionedAIConversationOutputSchema.optional(), + [geoOutputKey]: versionedGeoOutputSchema.optional(), }); /** * This type is a subset of the BackendOutput type that is exposed by the platform. From 31da2140ce466b2dc3ebe2aa57e9f32d84aa4f36 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:09:45 -0700 Subject: [PATCH 30/93] creating changeset --- .changeset/empty-trains-cut.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/empty-trains-cut.md diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct From 2b5a6142d478c4f0d9f8cf23b236288790602ef3 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:10:58 -0700 Subject: [PATCH 31/93] API changes --- packages/backend-output-schemas/API.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 23c5a41d89f..706b7ab13dd 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -66,6 +66,12 @@ export type FunctionOutput = z.infer; // @public export const functionOutputKey = "AWS::Amplify::Function"; +// @public (undocumented) +export type GeoOutput = z.infer; + +// @public +export const geoOutputKey = "AWS::Amplify::Geo"; + // @public (undocumented) export type GraphqlOutput = z.infer; @@ -371,6 +377,36 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; }>]>>; + "AWS::Amplify::Geo": z.ZodOptional; + payload: z.ZodObject<{ + defaultCollection: z.ZodString; + geoRegion: z.ZodString; + collections: z.ZodString; + }, "strip", z.ZodTypeAny, { + defaultCollection: string; + geoRegion: string; + collections: string; + }, { + defaultCollection: string; + geoRegion: string; + collections: string; + }>; + }, "strip", z.ZodTypeAny, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + }, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + }>]>>; }, "strip", z.ZodTypeAny, { "AWS::Amplify::Platform"?: { version: "1"; @@ -443,6 +479,14 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; } | undefined; + "AWS::Amplify::Geo"?: { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + } | undefined; }, { "AWS::Amplify::Platform"?: { version: "1"; @@ -515,6 +559,14 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ definedConversationHandlers: string; }; } | undefined; + "AWS::Amplify::Geo"?: { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; + } | undefined; }>; // @public (undocumented) @@ -700,6 +752,38 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ }; }>]>; +// @public (undocumented) +export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ + version: z.ZodLiteral<"1">; + payload: z.ZodObject<{ + defaultCollection: z.ZodString; + geoRegion: z.ZodString; + collections: z.ZodString; + }, "strip", z.ZodTypeAny, { + defaultCollection: string; + geoRegion: string; + collections: string; + }, { + defaultCollection: string; + geoRegion: string; + collections: string; + }>; +}, "strip", z.ZodTypeAny, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; +}, { + version: "1"; + payload: { + defaultCollection: string; + geoRegion: string; + collections: string; + }; +}>]>; + // @public (undocumented) export const versionedGraphqlOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; From 6d4fa3d0fbdfd6761aac89b52ec046a791bb7240 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 09:32:56 -0700 Subject: [PATCH 32/93] adding package and required files with partial implementation adding package and required files with partial implementation# --- package-lock.json | 29 +++++++++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 32 insertions(+) diff --git a/package-lock.json b/package-lock.json index 164f869649d..faca0848824 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "workspaces": [ "packages/*" ], + "dependencies": { + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + }, "devDependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", @@ -578,6 +581,10 @@ "resolved": "packages/backend-function", "link": true }, + "node_modules/@aws-amplify/backend-geo": { + "resolved": "packages/backend-geo", + "link": true + }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12810,6 +12817,19 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, + "node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.206.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", + "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.206.0", + "constructs": "^10.0.0" + } + }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -49745,6 +49765,15 @@ "constructs": "^10.0.0" } }, + "packages/backend-geo": { + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.158.0", + "constructs": "^10.0.0" + } + }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", diff --git a/package.json b/package.json index 29cc9161332..fe62027d13d 100644 --- a/package.json +++ b/package.json @@ -117,5 +117,8 @@ "overrides": { "minimatch": "10.0.1", "@graphql-tools/merge": "9.0.22" + }, + "dependencies": { + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } From bff93564e8f31004333554560a54869709277cec Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 15:46:00 -0700 Subject: [PATCH 33/93] adding initial version of construct and API --- .changeset/warm-garlics-flow.md | 5 + package-lock.json | 7 + package.json | 2 + packages/backend-geo/.npmignore | 14 ++ packages/backend-geo/API.md | 115 ++++++++++++ packages/backend-geo/README.md | 3 + packages/backend-geo/api-extractor.json | 3 + packages/backend-geo/package.json | 29 +++ packages/backend-geo/src/access_builder.ts | 105 +++++++++++ packages/backend-geo/src/construct.ts | 56 ++++++ packages/backend-geo/src/factory.ts | 166 ++++++++++++++++++ .../src/geo_access_orchestrator.ts | 90 ++++++++++ .../src/geo_access_policy_factory.ts | 62 +++++++ packages/backend-geo/src/index.ts | 7 + packages/backend-geo/src/types.ts | 80 +++++++++ packages/backend-geo/tsconfig.json | 5 + packages/backend-geo/typedoc.json | 3 + 17 files changed, 752 insertions(+) create mode 100644 .changeset/warm-garlics-flow.md create mode 100644 packages/backend-geo/.npmignore create mode 100644 packages/backend-geo/API.md create mode 100644 packages/backend-geo/README.md create mode 100644 packages/backend-geo/api-extractor.json create mode 100644 packages/backend-geo/package.json create mode 100644 packages/backend-geo/src/access_builder.ts create mode 100644 packages/backend-geo/src/construct.ts create mode 100644 packages/backend-geo/src/factory.ts create mode 100644 packages/backend-geo/src/geo_access_orchestrator.ts create mode 100644 packages/backend-geo/src/geo_access_policy_factory.ts create mode 100644 packages/backend-geo/src/index.ts create mode 100644 packages/backend-geo/src/types.ts create mode 100644 packages/backend-geo/tsconfig.json create mode 100644 packages/backend-geo/typedoc.json diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. diff --git a/package-lock.json b/package-lock.json index faca0848824..36c10a13041 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "packages/*" ], "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" }, "devDependencies": { @@ -49769,6 +49771,11 @@ "name": "@aws-amplify/backend-geo", "version": "0.1.0", "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + }, "peerDependencies": { "aws-cdk-lib": "^2.158.0", "constructs": "^10.0.0" diff --git a/package.json b/package.json index fe62027d13d..99dca9415b0 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,8 @@ "@graphql-tools/merge": "9.0.22" }, "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } diff --git a/packages/backend-geo/.npmignore b/packages/backend-geo/.npmignore new file mode 100644 index 00000000000..dbde1fb5dbc --- /dev/null +++ b/packages/backend-geo/.npmignore @@ -0,0 +1,14 @@ +# Be very careful editing this file. It is crafted to work around [this issue](https://github.com/npm/npm/issues/4479) + +# First ignore everything +**/* + +# Then add back in transpiled js and ts declaration files +!lib/**/*.js +!lib/**/*.d.ts + +# Then ignore test js and ts declaration files +*.test.js +*.test.d.ts + +# This leaves us with including only js and ts declaration files of functional code diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md new file mode 100644 index 00000000000..5c6feff41d0 --- /dev/null +++ b/packages/backend-geo/API.md @@ -0,0 +1,115 @@ +## API Report File for "@aws-amplify/backend-geo" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam'; +import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { Construct } from 'constructs'; +import { ConstructFactory } from '@aws-amplify/plugin-types'; +import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; +import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { ResourceProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { StackProvider } from '@aws-amplify/plugin-types'; + +// @public +export class AmplifyGeoFactory implements ConstructFactory> { + // Warning: (ae-forgotten-export) The symbol "GeoAccessOrchestratorFactory" needs to be exported by the entry point index.d.ts + constructor(props: AmplifyGeoFactoryProps, geoAccessOrchestratorFactory?: GeoAccessOrchestratorFactory); + // Warning: (ae-forgotten-export) The symbol "AmplifyGeo" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getInstance: (getInstanceProps: ConstructFactoryGetInstanceProps) => AmplifyGeo; +} + +// @public (undocumented) +export type AmplifyGeoFactoryProps = Omit & { + region: string; + access: GeoAccessGenerator; + resourceIdentifier?: GeoResourceType; +}; + +// @public (undocumented) +export type AmplifyGeoProps = { + name: string; + collectionProps?: GeofenceCollectionProps; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +// @public (undocumented) +export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; + +// @public +export const defineCollection: (// returns resources and any stack errors +props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const defineMap: (// doesn't return anything because it only configures access +props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; + +// @public +export const definePlace: (// doesn't return anything because it only configures access +props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; + +// @public (undocumented) +export type GeoAccessBuilder = { + authenticated: GeoActionBuilder; + guest: GeoActionBuilder; + groups: (groupNames: string[]) => GeoActionBuilder; +}; + +// @public (undocumented) +export type GeoAccessDefinition = { + userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; + actions: GeoAction[]; + uniqueDefinitionValidators: { + uniqueRoleToken: string; + validationErrorOptions: AmplifyUserErrorOptions; + }[]; +}; + +// @public (undocumented) +export type GeoAccessGenerator = (allow: GeoAccessBuilder) => GeoAccessDefinition[]; + +// @public (undocumented) +export type GeoAction = MapAction | IndexAction | CollectionAction; + +// @public (undocumented) +export type GeoActionBuilder = { + to: (actions: GeoAction[]) => GeoAccessDefinition; +}; + +// @public (undocumented) +export const geoCfnResourceTypes: string[]; + +// @public (undocumented) +export const geoManagedResourceTypes: string[]; + +// @public (undocumented) +export type GeoResources = { + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; + +// @public (undocumented) +export type GeoResourceType = 'map' | 'place' | 'collection'; + +// @public (undocumented) +export type IndexAction = 'autocomplete' | 'geocode' | 'search'; + +// @public (undocumented) +export type MapAction = 'get'; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/backend-geo/README.md b/packages/backend-geo/README.md new file mode 100644 index 00000000000..793417be040 --- /dev/null +++ b/packages/backend-geo/README.md @@ -0,0 +1,3 @@ +# Description + +Replace with a description of this package diff --git a/packages/backend-geo/api-extractor.json b/packages/backend-geo/api-extractor.json new file mode 100644 index 00000000000..0f56de03f66 --- /dev/null +++ b/packages/backend-geo/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.base.json" +} diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json new file mode 100644 index 00000000000..06c6879d8af --- /dev/null +++ b/packages/backend-geo/package.json @@ -0,0 +1,29 @@ +{ + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "type": "module", + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js", + "require": "./lib/index.js" + } + }, + "types": "lib/index.d.ts", + "scripts": { + "update:api": "api-extractor run --local" + }, + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.158.0", + "constructs": "^10.0.0" + }, + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + } +} diff --git a/packages/backend-geo/src/access_builder.ts b/packages/backend-geo/src/access_builder.ts new file mode 100644 index 00000000000..a07d0b95b94 --- /dev/null +++ b/packages/backend-geo/src/access_builder.ts @@ -0,0 +1,105 @@ +import { + AuthResources, + AuthRoleName, + ConstructFactoryGetInstanceProps, + ResourceAccessAcceptorFactory, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { GeoAccessBuilder } from './types.js'; + +export type Roles = IRole; + +export const roleAccessBuilder: GeoAccessBuilder = { + authenticated: { + // access for authenticated users + to: (actions) => ({ + userRoles: [getAuthRole], + actions, + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: `Access definition for authenticated users specified multiple times.`, + resolution: `Combine all access definitions for authenticated users into one access rule.`, + }, + }, + ], + }), + }, + guest: { + // access for guest users + to: (actions) => ({ + userRoles: [getUnauthRole], + actions, + uniqueDefinitionValidators: [ + { + uniqueRoleToken: `guest`, + validationErrorOptions: { + message: `Access definition for guest users specified multiple times.`, + resolution: `Combine all access definitions for guest users into one access rule.`, + }, + }, + ], + }), + }, + groups: (groupNames) => ({ + // access for user groups + to: (actions) => ({ + userRoles: groupNames.map( + // for each group in the user groups + (groupName) => (getInstanceProps) => + getUserRole(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) + ), + actions, + uniqueDefinitionValidators: groupNames.map((groupName) => ({ + uniqueRoleToken: `${groupName}`, + validationErrorOptions: { + message: `Access definition for the group ${groupName} specified multiple times.`, + resolution: `Combine all access definitions for the group ${groupName} into one access rule.`, + }, + })), + }), + }), +}; + +const getAuthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => + getUserRole(getInstanceProps, 'authenticatedUserIamRole'); + +const getUnauthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => + getUserRole(getInstanceProps, 'unauthenticatedUserIamRole'); + +// getting acceptor objects for different role types (defined in auth) +const getUserRole = ( + getInstanceProps: ConstructFactoryGetInstanceProps, // instance properties of the auth factory? + roleName: AuthRoleName | string, // name of role to get acceptors from +): IRole => { + const authResources = getInstanceProps.constructContainer + .getConstructFactory< + ResourceProvider & ResourceAccessAcceptorFactory + >( + // getting construct container to look for a specific construct factory + 'AuthResources', + ) + ?.getInstance(getInstanceProps).resources as AuthResources; // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors + if (!authResources) { + throw new Error( + `Cannot specify geo resource access for ${ + roleName as string + } users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.`, + ); + } else { + if (roleName === authResources.authenticatedUserIamRole.roleName) { + return authResources.authenticatedUserIamRole; + } else if (roleName === authResources.unauthenticatedUserIamRole.roleName) { + return authResources.unauthenticatedUserIamRole; + } else if (authResources.groups[roleName]) { + return authResources.groups[roleName].role; + } + throw new Error( + `Role: ${ + roleName as string + } does not exist. See https://console.aws.amazon.com/iam/ to define the role.`, + ); + } +}; diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts new file mode 100644 index 00000000000..7c0b1cbf986 --- /dev/null +++ b/packages/backend-geo/src/construct.ts @@ -0,0 +1,56 @@ +import { Construct } from 'constructs'; +import { AmplifyGeoProps, GeoResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { + GeofenceCollection, + GeofenceCollectionProps, +} from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; + +/** + * Amplify Geo CDK Construct + * + * Designed as a wrapper around a Geofence Collection provided by Amazon Location Services. + */ +export class AmplifyGeo + extends Construct + implements ResourceProvider, StackProvider +{ + readonly stack: Stack; // current stack + readonly resources: GeoResources; + readonly name: string; // construct name + readonly id: string; + + /** + * Constructs a new AmplifyGeo instance + */ + constructor(scope: Construct, id: string, props: AmplifyGeoProps) { + super(scope, id); // call to Construct class as part of inheritance + this.name = props.name; + this.id = id; + if (props.collectionProps) { + const geofenceCollectionProps: GeofenceCollectionProps = { + // property mapping + geofenceCollectionName: props.collectionProps.geofenceCollectionName, + description: props.collectionProps.description, + kmsKey: props.collectionProps.kmsKey, + }; + + const geofenceCollection = new GeofenceCollection( + this, + id, + geofenceCollectionProps, + ); // L2 construct call + + this.resources = { + collection: geofenceCollection, + cfnResources: { + cfnCollection: geofenceCollection.node.findChild( + 'Resource', + ) as CfnGeofenceCollection, // getting L1 child instance + }, + }; + } + } +} diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts new file mode 100644 index 00000000000..4bef73ec83c --- /dev/null +++ b/packages/backend-geo/src/factory.ts @@ -0,0 +1,166 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aws } from 'aws-cdk-lib/core'; +import { AmplifyGeoFactoryProps, GeoResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyGeo } from './construct.js'; +import { Tags } from 'aws-cdk-lib'; +import { TagName } from '@aws-amplify/platform-core'; + +// define factory class +/** + * Amplify Geo Construct Factory + * + * Designed to manage the initialization of AmplifyGeo construct among other constructs and construct-agnostic resource access orchestration. + */ +export class AmplifyGeoFactory + implements ConstructFactory> +{ + private geoGenerator: ConstructContainerEntryGenerator; + + // define constructor for class + /** + * Constructs a new AmplifyGeoFactory instance + * @param props - properties for the geo factory + * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) + */ + constructor( + private readonly props: AmplifyGeoFactoryProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + // define GetInstance function + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyGeo => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // AWS-MANAGED RESOURCE ACCESS ORCHESTRATION HERE + if (this.props.resourceIdentifier !== 'collection') { + // only limited to collections currently + const geoAccessOrchestrator = + this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + getInstanceProps, + ); + + // access orchestration for non-collection resources done here + geoAccessOrchestrator.orchestrateGeoAccess( + `arn:${Aws.PARTITION}:geo-${this.props.resourceIdentifier}:${this.props.region}::provider/default`, + ); + } + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyGeoGenerator(this.props, getInstanceProps); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyGeo; + }; +} + +// define generator class +/** + * Amplify Geo Construct Generator + * + * Designed to manage the generation of the Amplify Geo construct instance along with resource access orchestration. + */ +export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Constructs an AmplifyGeoGenerator instance + * @param props - properties for the Amplify Geo Factory + * @param getInstanceProps - instance properties of a specific construct factory + * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) + */ + constructor( + private readonly props: AmplifyGeoFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + /* This function will perform the following actions: + 1. create an instance of the L3 construct created for Geo (AmplifyGeo) + 2. call the defineGeoAccess() function with access permissions for all three resources + */ + generateContainerEntry = ({ + // construct-related activities + scope, + }: GenerateContainerEntryProps) => { + const amplifyGeo = new AmplifyGeo(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + // creating a CDK lookup tag + Tags.of(amplifyGeo).add(TagName.FRIENDLY_NAME, this.props.name); + + // CLOUDFORMATION CONSTRUCT RESOURCES WILL HAVE ACCESS ORCHESTRATION HERE + if (this.props.resourceIdentifier === 'collection') { + // only limited to collections currently + const geoAccessOrchestrator = + this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + ); + + // initializing orchestration process + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyGeo.resources.collection.geofenceCollectionArn, + ); + } + + return amplifyGeo; + }; +} + +// export defineX() functions + +/** + * Include a map within your Amplify backend. + */ +export const defineMap = ( + // doesn't return anything because it only configures access + props: AmplifyGeoFactoryProps, +) => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'map', + }); + +/** + * Include a place index within your Amplify backend. + */ +export const definePlace = ( + // doesn't return anything because it only configures access + props: AmplifyGeoFactoryProps, +) => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'place', + }); + +/** + * Include a geofence collection within your Amplify backend. + */ +export const defineCollection = ( + // returns resources and any stack errors + props: AmplifyGeoFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyGeoFactory({ + ...props, + resourceIdentifier: 'collection', + }); // should this be called AmplifyCollectionFactory? diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts new file mode 100644 index 00000000000..3b019ab5208 --- /dev/null +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -0,0 +1,90 @@ +import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { + GeoAccessBuilder, + GeoAccessGenerator, + GeoResourceType, +} from './types.js'; +import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; +import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +// this file is responsible for implementing the following: +// 1. access orchestrator for geo +/** + * Access Orchestrator for Amplify Geo + * + * Configures access permissions to associate them with roles. + */ +export class GeoAccessOrchestrator { + /** + * Constructs an instance of GeoAccessOrchestrator + * @param geoAccessGenerator - access permissions defined by user for the resource + * @param getInstanceProps - instance properties of a specific construct factory + * @param geoPolicyFactory - instance of GeoAccessPolicyFactory to generate policyStatements + * @param roleAccessBuilder - permission reader and processor + */ + constructor( + private readonly geoAccessGenerator: GeoAccessGenerator, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoPolicyFactory: GeoAccessPolicyFactory = new GeoAccessPolicyFactory(), + private readonly roleAccessBuilder: GeoAccessBuilder = _roleAccessBuilder, + ) {} + + /** + * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. + * + * The high level steps are: + * 1. Invokes the geoAccessGenerator to produce a storageAccessDefinition + * 3. Organizes the storageAccessDefinition into internally managed maps to facilitate translation into allow / deny rules on IAM policies + * 4. Invokes the policy generator to produce a policy with appropriate allow / deny rules + * 5. Invokes the resourceAccessAcceptors for each entry in the geoAccessDefinition to accept the corresponding IAM policy + */ + orchestrateGeoAccess = (resourceArn: string) => { + // getting access definitions from allow calls + const geoAccessDefinitions = this.geoAccessGenerator( + this.roleAccessBuilder, + ); + + geoAccessDefinitions.forEach((definition) => { + // get all user roles for each definition + const uniqueRoleTokenSet = new Set(); + + definition.uniqueDefinitionValidators.forEach( + ({ uniqueRoleToken, validationErrorOptions }) => { + if (uniqueRoleTokenSet.has(uniqueRoleToken)) { + throw new AmplifyUserError( + 'InvalidGeoAccessDefinitionError', + validationErrorOptions, + ); + } else { + uniqueRoleTokenSet.add(uniqueRoleToken); + } + }, + ); + + definition.userRoles.forEach((user) => { + this.geoPolicyFactory.attachPolicy( + // attaching policy statement to principal policy of each user Role provided within access definition + user(this.getInstanceProps), + this.geoPolicyFactory.createPolicyStatement( + definition.actions, + resourceArn, + ), // creating a policy statement for all actions provided within definition + ); + }); + }); + }; +} + +// needed for test mocking +/** + * Instance Manager for Geo Access Orchestration + */ +export class GeoAccessOrchestratorFactory { + private readonly resourceType: GeoResourceType; + + getInstance = ( + geoAccessGenerator: GeoAccessGenerator, + getInstanceProps: ConstructFactoryGetInstanceProps, + ) => new GeoAccessOrchestrator(geoAccessGenerator, getInstanceProps); +} diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts new file mode 100644 index 00000000000..1e0f081623a --- /dev/null +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -0,0 +1,62 @@ +import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { GeoAction } from './types.js'; +import { AmplifyFault } from '@aws-amplify/platform-core'; + +/** + * Geo Access Policy Factory + * + * Responsible for policy statement generation and policy-role attachment. + */ +export class GeoAccessPolicyFactory { + // creating a singular IAM policy + createPolicyStatement = ( + permissions: GeoAction[], // organize create policy such that one resource type maps to the actions + resourceArn: string, + ): PolicyStatement => { + if (permissions.length === 0) { + throw new AmplifyFault('EmptyPolicyFault', { + message: 'At least one permission must be specified', + }); + } + + // policy statements created for each resource type? + const policyStatement: PolicyStatement = new PolicyStatement(); + + permissions.forEach((action) => { + policyStatement.addActions(...actionDirectory[action]); + }); + + policyStatement.addResources(resourceArn); + + return policyStatement; // returns policy statement with all policies + }; + + attachPolicy = (userRole: IRole, statement: PolicyStatement) => + userRole.addToPrincipalPolicy(statement); +} + +const actionDirectory: Record = { + get: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + autocomplete: ['geo-places:Autocomplete'], + geocode: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], + search: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ], + create: ['geo:CreateGeofenceCollection'], + read: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + update: [ + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + delete: ['geo:BatchDeleteGeofence', 'geo:DeleteGeofenceCollection'], + list: ['geo:ListGeofences', 'geo:ListGeofenceCollections'], +}; diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts new file mode 100644 index 00000000000..82e76679859 --- /dev/null +++ b/packages/backend-geo/src/index.ts @@ -0,0 +1,7 @@ +export { + defineMap, + definePlace, + defineCollection, + AmplifyGeoFactory, +} from './factory.js'; +export * from './types.js'; diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts new file mode 100644 index 00000000000..da8fb917434 --- /dev/null +++ b/packages/backend-geo/src/types.ts @@ -0,0 +1,80 @@ +import { + BackendOutputStorageStrategy, + ConstructFactoryGetInstanceProps, +} from '@aws-amplify/plugin-types'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { + GeofenceCollection, + GeofenceCollectionProps, +} from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; + +// ----------------------------------- factory properties ---------------------------------------------- + +// factory properties include construct properties without output strategy (because that's loaded inside factory) +export type AmplifyGeoFactoryProps = Omit< + AmplifyGeoProps, + 'outputStorageStrategy' +> & { + region: string; + access: GeoAccessGenerator; + resourceIdentifier?: GeoResourceType; +}; + +export type AmplifyGeoProps = { + name: string; + collectionProps?: GeofenceCollectionProps; + + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +export type GeoResources = { + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; + +// ----------------------------------- access definitions ---------------------------------------------- + +export type GeoAccessGenerator = ( + allow: GeoAccessBuilder, +) => GeoAccessDefinition[]; + +export type GeoAccessBuilder = { + authenticated: GeoActionBuilder; + guest: GeoActionBuilder; + groups: (groupNames: string[]) => GeoActionBuilder; +}; + +export type GeoActionBuilder = { + // access builder (within defineX()) + to: (actions: GeoAction[]) => GeoAccessDefinition; +}; + +export type GeoAccessDefinition = { + userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; + actions: GeoAction[]; + uniqueDefinitionValidators: { + uniqueRoleToken: string; + validationErrorOptions: AmplifyUserErrorOptions; + }[]; +}; + +// ----------------------------------- misc. types ---------------------------------------------- + +export type MapAction = 'get'; + +export type IndexAction = 'autocomplete' | 'geocode' | 'search'; + +export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; + +export type GeoAction = MapAction | IndexAction | CollectionAction; + +export const geoCfnResourceTypes = ['collection']; + +export const geoManagedResourceTypes = ['map', 'place']; + +export type GeoResourceType = 'map' | 'place' | 'collection'; diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json new file mode 100644 index 00000000000..2aab102e9b4 --- /dev/null +++ b/packages/backend-geo/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "references": [] +} diff --git a/packages/backend-geo/typedoc.json b/packages/backend-geo/typedoc.json new file mode 100644 index 00000000000..35fed2c958c --- /dev/null +++ b/packages/backend-geo/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} From 6c126daac9470d225b03fcad89a6d7a7a6175c3d Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 18 Jul 2025 16:01:14 -0700 Subject: [PATCH 34/93] updating API and exposed endpoints --- packages/backend-geo/API.md | 23 +++-------------------- packages/backend-geo/src/factory.ts | 12 +++++------- packages/backend-geo/src/index.ts | 7 +------ packages/backend-geo/tsconfig.json | 5 ++++- 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 5c6feff41d0..ad142371e83 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -4,32 +4,18 @@ ```ts -import { AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { Construct } from 'constructs'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { IRole } from 'aws-cdk-lib/aws-iam'; -import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { ResourceProvider } from '@aws-amplify/plugin-types'; -import { Stack } from 'aws-cdk-lib'; import { StackProvider } from '@aws-amplify/plugin-types'; -// @public -export class AmplifyGeoFactory implements ConstructFactory> { - // Warning: (ae-forgotten-export) The symbol "GeoAccessOrchestratorFactory" needs to be exported by the entry point index.d.ts - constructor(props: AmplifyGeoFactoryProps, geoAccessOrchestratorFactory?: GeoAccessOrchestratorFactory); - // Warning: (ae-forgotten-export) The symbol "AmplifyGeo" needs to be exported by the entry point index.d.ts - // - // (undocumented) - getInstance: (getInstanceProps: ConstructFactoryGetInstanceProps) => AmplifyGeo; -} - // @public (undocumented) export type AmplifyGeoFactoryProps = Omit & { region: string; @@ -48,16 +34,13 @@ export type AmplifyGeoProps = { export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; // @public -export const defineCollection: (// returns resources and any stack errors -props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export const defineCollection: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public -export const defineMap: (// doesn't return anything because it only configures access -props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; +export const defineMap: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public -export const definePlace: (// doesn't return anything because it only configures access -props: AmplifyGeoFactoryProps) => AmplifyGeoFactory; +export const definePlace: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; // @public (undocumented) export type GeoAccessBuilder = { diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts index 4bef73ec83c..fe6e0a06582 100644 --- a/packages/backend-geo/src/factory.ts +++ b/packages/backend-geo/src/factory.ts @@ -24,17 +24,15 @@ export class AmplifyGeoFactory implements ConstructFactory> { private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); // define constructor for class /** * Constructs a new AmplifyGeoFactory instance * @param props - properties for the geo factory - * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) */ - constructor( - private readonly props: AmplifyGeoFactoryProps, - private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), - ) {} + constructor(private readonly props: AmplifyGeoFactoryProps) {} // define GetInstance function getInstance = ( @@ -135,7 +133,7 @@ export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { export const defineMap = ( // doesn't return anything because it only configures access props: AmplifyGeoFactoryProps, -) => +): ConstructFactory & StackProvider> => new AmplifyGeoFactory({ ...props, resourceIdentifier: 'map', @@ -147,7 +145,7 @@ export const defineMap = ( export const definePlace = ( // doesn't return anything because it only configures access props: AmplifyGeoFactoryProps, -) => +): ConstructFactory & StackProvider> => new AmplifyGeoFactory({ ...props, resourceIdentifier: 'place', diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 82e76679859..8c575b470e0 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,7 +1,2 @@ -export { - defineMap, - definePlace, - defineCollection, - AmplifyGeoFactory, -} from './factory.js'; +export { defineMap, definePlace, defineCollection } from './factory.js'; export * from './types.js'; diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json index 2aab102e9b4..6dd4b4b566a 100644 --- a/packages/backend-geo/tsconfig.json +++ b/packages/backend-geo/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "src", "outDir": "lib" }, - "references": [] + "references": [ + { "path": "../backend-output-schemas" }, + { "path": "../platform-core" } + ] } From ce664feb0702fc93f43281ae5bf8834192f0a62b Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sun, 20 Jul 2025 12:42:41 -0700 Subject: [PATCH 35/93] adding output definition --- packages/backend-geo/src/construct.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts index 7c0b1cbf986..5b375a7ca3d 100644 --- a/packages/backend-geo/src/construct.ts +++ b/packages/backend-geo/src/construct.ts @@ -7,6 +7,7 @@ import { GeofenceCollectionProps, } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { geoOutputKey } from '@aws-amplify/backend-output-schemas'; /** * Amplify Geo CDK Construct @@ -51,6 +52,15 @@ export class AmplifyGeo ) as CfnGeofenceCollection, // getting L1 child instance }, }; + + props.outputStorageStrategy?.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: { + defaultCollection: this.resources.collection.geofenceCollectionName, + geoRegion: this.stack.region, + collections: `["${this.resources.collection.geofenceCollectionName}"]`, + }, + }); } } } From 432ffbf027bfedae7644f34a09a42cac16142d97 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 23 Jul 2025 10:05:49 -0700 Subject: [PATCH 36/93] split APi definition and outputs aspects defined --- packages/backend-geo/src/access_builder.ts | 54 +++--- .../backend-geo/src/collection_construct.ts | 53 ++++++ .../backend-geo/src/collection_factory.ts | 109 ++++++++++++ packages/backend-geo/src/construct.ts | 66 ------- packages/backend-geo/src/factory.ts | 164 ------------------ .../src/geo_access_orchestrator.ts | 82 ++++++--- .../src/geo_access_policy_factory.ts | 23 +-- .../backend-geo/src/geo_outputs_aspect.ts | 152 ++++++++++++++++ packages/backend-geo/src/index.ts | 2 +- packages/backend-geo/src/map_factory.ts | 115 ++++++++++++ packages/backend-geo/src/map_resource.ts | 33 ++++ packages/backend-geo/src/place_factory.ts | 119 +++++++++++++ packages/backend-geo/src/place_resource.ts | 33 ++++ packages/backend-geo/src/types.ts | 126 +++++++++++--- packages/backend-output-schemas/src/geo/v1.ts | 10 +- 15 files changed, 815 insertions(+), 326 deletions(-) create mode 100644 packages/backend-geo/src/collection_construct.ts create mode 100644 packages/backend-geo/src/collection_factory.ts delete mode 100644 packages/backend-geo/src/construct.ts delete mode 100644 packages/backend-geo/src/factory.ts create mode 100644 packages/backend-geo/src/geo_outputs_aspect.ts create mode 100644 packages/backend-geo/src/map_factory.ts create mode 100644 packages/backend-geo/src/map_resource.ts create mode 100644 packages/backend-geo/src/place_factory.ts create mode 100644 packages/backend-geo/src/place_resource.ts diff --git a/packages/backend-geo/src/access_builder.ts b/packages/backend-geo/src/access_builder.ts index a07d0b95b94..75c3f522003 100644 --- a/packages/backend-geo/src/access_builder.ts +++ b/packages/backend-geo/src/access_builder.ts @@ -1,20 +1,16 @@ import { - AuthResources, AuthRoleName, ConstructFactoryGetInstanceProps, ResourceAccessAcceptorFactory, ResourceProvider, } from '@aws-amplify/plugin-types'; -import { IRole } from 'aws-cdk-lib/aws-iam'; import { GeoAccessBuilder } from './types.js'; -export type Roles = IRole; - export const roleAccessBuilder: GeoAccessBuilder = { authenticated: { // access for authenticated users to: (actions) => ({ - userRoles: [getAuthRole], + getAccessAcceptors: [getAuthRoleAcceptor], actions, uniqueDefinitionValidators: [ { @@ -30,11 +26,11 @@ export const roleAccessBuilder: GeoAccessBuilder = { guest: { // access for guest users to: (actions) => ({ - userRoles: [getUnauthRole], + getAccessAcceptors: [getUnauthRoleAcceptor], actions, uniqueDefinitionValidators: [ { - uniqueRoleToken: `guest`, + uniqueRoleToken: 'guest', validationErrorOptions: { message: `Access definition for guest users specified multiple times.`, resolution: `Combine all access definitions for guest users into one access rule.`, @@ -46,60 +42,52 @@ export const roleAccessBuilder: GeoAccessBuilder = { groups: (groupNames) => ({ // access for user groups to: (actions) => ({ - userRoles: groupNames.map( + getAccessAcceptors: groupNames.map( // for each group in the user groups (groupName) => (getInstanceProps) => - getUserRole(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) + getUserRoleAcceptor(getInstanceProps, groupName), // get role for that group (getting all acceptors from the groupNames specified) ), - actions, uniqueDefinitionValidators: groupNames.map((groupName) => ({ - uniqueRoleToken: `${groupName}`, + uniqueRoleToken: `group-${groupName}`, validationErrorOptions: { message: `Access definition for the group ${groupName} specified multiple times.`, resolution: `Combine all access definitions for the group ${groupName} into one access rule.`, }, })), + actions, }), }), }; -const getAuthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => - getUserRole(getInstanceProps, 'authenticatedUserIamRole'); +const getAuthRoleAcceptor = ( + getInstanceProps: ConstructFactoryGetInstanceProps, +) => getUserRoleAcceptor(getInstanceProps, 'authenticatedUserIamRole'); -const getUnauthRole = (getInstanceProps: ConstructFactoryGetInstanceProps) => - getUserRole(getInstanceProps, 'unauthenticatedUserIamRole'); +const getUnauthRoleAcceptor = ( + getInstanceProps: ConstructFactoryGetInstanceProps, +) => getUserRoleAcceptor(getInstanceProps, 'unauthenticatedUserIamRole'); // getting acceptor objects for different role types (defined in auth) -const getUserRole = ( +const getUserRoleAcceptor = ( getInstanceProps: ConstructFactoryGetInstanceProps, // instance properties of the auth factory? roleName: AuthRoleName | string, // name of role to get acceptors from -): IRole => { - const authResources = getInstanceProps.constructContainer +) => { + const resourceAccessAcceptor = getInstanceProps.constructContainer .getConstructFactory< ResourceProvider & ResourceAccessAcceptorFactory >( // getting construct container to look for a specific construct factory 'AuthResources', ) - ?.getInstance(getInstanceProps).resources as AuthResources; // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors - if (!authResources) { + ?.getInstance(getInstanceProps) + .getResourceAccessAcceptor(roleName); // getting resource access acceptor factory instance (part of AuthResources) // getting resource acceptors + + if (!resourceAccessAcceptor) { throw new Error( `Cannot specify geo resource access for ${ roleName as string } users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.`, ); - } else { - if (roleName === authResources.authenticatedUserIamRole.roleName) { - return authResources.authenticatedUserIamRole; - } else if (roleName === authResources.unauthenticatedUserIamRole.roleName) { - return authResources.unauthenticatedUserIamRole; - } else if (authResources.groups[roleName]) { - return authResources.groups[roleName].role; - } - throw new Error( - `Role: ${ - roleName as string - } does not exist. See https://console.aws.amazon.com/iam/ to define the role.`, - ); } + return resourceAccessAcceptor; }; diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts new file mode 100644 index 00000000000..fe1724eb16a --- /dev/null +++ b/packages/backend-geo/src/collection_construct.ts @@ -0,0 +1,53 @@ +import { Construct } from 'constructs'; +import { AmplifyCollectionProps, CollectionResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; +import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { Policy } from 'aws-cdk-lib/aws-iam'; + +/** + * Amplify Collection CDK Construct + * + * Provisions a Collection through `alpha` L2 Construct + */ +export class AmplifyCollection + extends Construct + implements ResourceProvider, StackProvider +{ + readonly stack: Stack; + readonly resources: CollectionResources; + readonly name: string; + readonly id: string; + readonly isDefault: boolean; + readonly policies: Policy[]; + + /** + * Creates an instance of AmplifyCollection construct + * @param scope - CDK stack where the construct should provision resources + * @param id - CDK ID of Geofence Collection + * @param props - properties of AmplifyCollection + */ + constructor(scope: Construct, id: string, props: AmplifyCollectionProps) { + super(scope, id); + + this.name = props.name; + this.id = id; + this.isDefault = props.isDefault || false; + + const geofenceCollection = new GeofenceCollection( + this, + id, + props.collectionProps, + ); + this.resources = { + collection: geofenceCollection, + policies: this.policies, + cfnResources: { + cfnCollection: geofenceCollection.node.findChild( + 'Resource', + ) as CfnGeofenceCollection, + }, + }; + } +} diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts new file mode 100644 index 00000000000..65c92853a51 --- /dev/null +++ b/packages/backend-geo/src/collection_factory.ts @@ -0,0 +1,109 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyCollectionFactoryProps, CollectionResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyCollection } from './collection_construct.js'; +import { TagName } from '@aws-amplify/platform-core'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct factory for AmplifyCollection + */ +export class AmplifyCollectionFactory + implements ConstructFactory> +{ + private collectionGenerator: AmplifyCollectionGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Creates an instance of AmplifyCollectionFactory + * @param props - collection construct properties + */ + constructor(private readonly props: AmplifyCollectionFactoryProps) {} + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyCollection => { + const { constructContainer, resourceNameValidator } = getInstanceProps; + + resourceNameValidator?.validate(this.props.name); + + if (!this.collectionGenerator) { + this.collectionGenerator = new AmplifyCollectionGenerator( + this.props, + getInstanceProps, + ); + } + + return constructContainer.getOrCompute( + this.collectionGenerator, + ) as AmplifyCollection; + }; +} + +/** + * Construct Container Entry Generator for AmplifyCollection + */ +export class AmplifyCollectionGenerator + implements ConstructContainerEntryGenerator +{ + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of the AmplifyCollectionGenerator + */ + constructor( + private readonly props: AmplifyCollectionFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyCollection = new AmplifyCollection(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyCollection).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyCollection.resources.policies = + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyCollection.resources.collection.geofenceCollectionArn, + 'collection', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyCollection)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyCollection; + }; +} + +/** + * Provision a geofence collection within your Amplify backend. + * @see https://docs.amplify.aws/react/build-a-backend/add-aws-services/geo/configure-geofencing/ + */ +export const defineCollection = ( + props: AmplifyCollectionFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyCollectionFactory(props); diff --git a/packages/backend-geo/src/construct.ts b/packages/backend-geo/src/construct.ts deleted file mode 100644 index 5b375a7ca3d..00000000000 --- a/packages/backend-geo/src/construct.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Construct } from 'constructs'; -import { AmplifyGeoProps, GeoResources } from './types.js'; -import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; -import { Stack } from 'aws-cdk-lib'; -import { - GeofenceCollection, - GeofenceCollectionProps, -} from '@aws-cdk/aws-location-alpha'; -import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { geoOutputKey } from '@aws-amplify/backend-output-schemas'; - -/** - * Amplify Geo CDK Construct - * - * Designed as a wrapper around a Geofence Collection provided by Amazon Location Services. - */ -export class AmplifyGeo - extends Construct - implements ResourceProvider, StackProvider -{ - readonly stack: Stack; // current stack - readonly resources: GeoResources; - readonly name: string; // construct name - readonly id: string; - - /** - * Constructs a new AmplifyGeo instance - */ - constructor(scope: Construct, id: string, props: AmplifyGeoProps) { - super(scope, id); // call to Construct class as part of inheritance - this.name = props.name; - this.id = id; - if (props.collectionProps) { - const geofenceCollectionProps: GeofenceCollectionProps = { - // property mapping - geofenceCollectionName: props.collectionProps.geofenceCollectionName, - description: props.collectionProps.description, - kmsKey: props.collectionProps.kmsKey, - }; - - const geofenceCollection = new GeofenceCollection( - this, - id, - geofenceCollectionProps, - ); // L2 construct call - - this.resources = { - collection: geofenceCollection, - cfnResources: { - cfnCollection: geofenceCollection.node.findChild( - 'Resource', - ) as CfnGeofenceCollection, // getting L1 child instance - }, - }; - - props.outputStorageStrategy?.addBackendOutputEntry(geoOutputKey, { - version: '1', - payload: { - defaultCollection: this.resources.collection.geofenceCollectionName, - geoRegion: this.stack.region, - collections: `["${this.resources.collection.geofenceCollectionName}"]`, - }, - }); - } - } -} diff --git a/packages/backend-geo/src/factory.ts b/packages/backend-geo/src/factory.ts deleted file mode 100644 index fe6e0a06582..00000000000 --- a/packages/backend-geo/src/factory.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - AmplifyResourceGroupName, - ConstructContainerEntryGenerator, - ConstructFactory, - ConstructFactoryGetInstanceProps, - GenerateContainerEntryProps, - ResourceProvider, - StackProvider, -} from '@aws-amplify/plugin-types'; -import { Aws } from 'aws-cdk-lib/core'; -import { AmplifyGeoFactoryProps, GeoResources } from './types.js'; -import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; -import { AmplifyGeo } from './construct.js'; -import { Tags } from 'aws-cdk-lib'; -import { TagName } from '@aws-amplify/platform-core'; - -// define factory class -/** - * Amplify Geo Construct Factory - * - * Designed to manage the initialization of AmplifyGeo construct among other constructs and construct-agnostic resource access orchestration. - */ -export class AmplifyGeoFactory - implements ConstructFactory> -{ - private geoGenerator: ConstructContainerEntryGenerator; - private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = - new GeoAccessOrchestratorFactory(); - - // define constructor for class - /** - * Constructs a new AmplifyGeoFactory instance - * @param props - properties for the geo factory - */ - constructor(private readonly props: AmplifyGeoFactoryProps) {} - - // define GetInstance function - getInstance = ( - getInstanceProps: ConstructFactoryGetInstanceProps, - ): AmplifyGeo => { - // get construct factory instance properties - const { constructContainer, resourceNameValidator } = getInstanceProps; - - // validates the user-entered resource name (according to CDK naming regulations) - resourceNameValidator?.validate(this.props.name); - - // AWS-MANAGED RESOURCE ACCESS ORCHESTRATION HERE - if (this.props.resourceIdentifier !== 'collection') { - // only limited to collections currently - const geoAccessOrchestrator = - this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - getInstanceProps, - ); - - // access orchestration for non-collection resources done here - geoAccessOrchestrator.orchestrateGeoAccess( - `arn:${Aws.PARTITION}:geo-${this.props.resourceIdentifier}:${this.props.region}::provider/default`, - ); - } - - // generates a singleton container entry for this construct factory - if (!this.geoGenerator) { - this.geoGenerator = new AmplifyGeoGenerator(this.props, getInstanceProps); - } - - // this getOrCompute accesses the internal construct container cache - return constructContainer.getOrCompute(this.geoGenerator) as AmplifyGeo; - }; -} - -// define generator class -/** - * Amplify Geo Construct Generator - * - * Designed to manage the generation of the Amplify Geo construct instance along with resource access orchestration. - */ -export class AmplifyGeoGenerator implements ConstructContainerEntryGenerator { - readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; - - /** - * Constructs an AmplifyGeoGenerator instance - * @param props - properties for the Amplify Geo Factory - * @param getInstanceProps - instance properties of a specific construct factory - * @param geoAccessOrchestratorFactory - instance of the access orchestrator factory (lazy initialization) - */ - constructor( - private readonly props: AmplifyGeoFactoryProps, - private readonly getInstanceProps: ConstructFactoryGetInstanceProps, - private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), - ) {} - - /* This function will perform the following actions: - 1. create an instance of the L3 construct created for Geo (AmplifyGeo) - 2. call the defineGeoAccess() function with access permissions for all three resources - */ - generateContainerEntry = ({ - // construct-related activities - scope, - }: GenerateContainerEntryProps) => { - const amplifyGeo = new AmplifyGeo(scope, this.props.name, { - ...this.props, - outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, - }); - - // creating a CDK lookup tag - Tags.of(amplifyGeo).add(TagName.FRIENDLY_NAME, this.props.name); - - // CLOUDFORMATION CONSTRUCT RESOURCES WILL HAVE ACCESS ORCHESTRATION HERE - if (this.props.resourceIdentifier === 'collection') { - // only limited to collections currently - const geoAccessOrchestrator = - this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - ); - - // initializing orchestration process - geoAccessOrchestrator.orchestrateGeoAccess( - amplifyGeo.resources.collection.geofenceCollectionArn, - ); - } - - return amplifyGeo; - }; -} - -// export defineX() functions - -/** - * Include a map within your Amplify backend. - */ -export const defineMap = ( - // doesn't return anything because it only configures access - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'map', - }); - -/** - * Include a place index within your Amplify backend. - */ -export const definePlace = ( - // doesn't return anything because it only configures access - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'place', - }); - -/** - * Include a geofence collection within your Amplify backend. - */ -export const defineCollection = ( - // returns resources and any stack errors - props: AmplifyGeoFactoryProps, -): ConstructFactory & StackProvider> => - new AmplifyGeoFactory({ - ...props, - resourceIdentifier: 'collection', - }); // should this be called AmplifyCollectionFactory? diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 3b019ab5208..5ab49e6c674 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -1,12 +1,17 @@ -import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; +import { + ConstructFactoryGetInstanceProps, + SsmEnvironmentEntry, +} from '@aws-amplify/plugin-types'; import { GeoAccessBuilder, GeoAccessGenerator, - GeoResourceType, + resourceActionRecord, } from './types.js'; import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { Policy } from 'aws-cdk-lib/aws-iam'; +import { Stack } from 'aws-cdk-lib'; // this file is responsible for implementing the following: // 1. access orchestrator for geo @@ -16,30 +21,36 @@ import { AmplifyUserError } from '@aws-amplify/platform-core'; * Configures access permissions to associate them with roles. */ export class GeoAccessOrchestrator { + private resourceStack: Stack; + private policies: Policy[] = []; /** * Constructs an instance of GeoAccessOrchestrator * @param geoAccessGenerator - access permissions defined by user for the resource * @param getInstanceProps - instance properties of a specific construct factory - * @param geoPolicyFactory - instance of GeoAccessPolicyFactory to generate policyStatements - * @param roleAccessBuilder - permission reader and processor + * @param geoStack - instance of GeoAccessPolicyFactory to generate policyStatements + * @param ssmEnvironmentEntries - permission reader and processor + * @param geoPolicyFactory - + * @param roleAccessBuilder - */ constructor( private readonly geoAccessGenerator: GeoAccessGenerator, private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoStack: Stack, + private readonly ssmEnvironmentEntries: SsmEnvironmentEntry[], private readonly geoPolicyFactory: GeoAccessPolicyFactory = new GeoAccessPolicyFactory(), private readonly roleAccessBuilder: GeoAccessBuilder = _roleAccessBuilder, - ) {} + ) { + this.resourceStack = geoStack; + } /** * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. * - * The high level steps are: - * 1. Invokes the geoAccessGenerator to produce a storageAccessDefinition - * 3. Organizes the storageAccessDefinition into internally managed maps to facilitate translation into allow / deny rules on IAM policies - * 4. Invokes the policy generator to produce a policy with appropriate allow / deny rules - * 5. Invokes the resourceAccessAcceptors for each entry in the geoAccessDefinition to accept the corresponding IAM policy */ - orchestrateGeoAccess = (resourceArn: string) => { + orchestrateGeoAccess = ( + resourceArn: string, + resourceIdentifier: string, + ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( this.roleAccessBuilder, @@ -62,17 +73,38 @@ export class GeoAccessOrchestrator { }, ); - definition.userRoles.forEach((user) => { - this.geoPolicyFactory.attachPolicy( - // attaching policy statement to principal policy of each user Role provided within access definition - user(this.getInstanceProps), - this.geoPolicyFactory.createPolicyStatement( - definition.actions, - resourceArn, - ), // creating a policy statement for all actions provided within definition + // checking for valid actions for resource type + definition.actions.forEach((action) => { + if (!resourceActionRecord[resourceIdentifier].includes(action)) { + throw new AmplifyUserError('ActionNotFoundError', { + message: `Desired access action not found for the specific ${resourceIdentifier} resource.`, + resolution: `Please refer to specific ${resourceIdentifier} access actions for more information.`, + }); + } + }); + + const roleTokens = Array.from(uniqueRoleTokenSet); + + let roleIndex: number = 0; // need respective roleToken for policy generation + definition.getAccessAcceptors.forEach((acceptor) => { + // for each acceptor within auth, guest, or user groups + + const policy: Policy = this.geoPolicyFactory.createPolicy( + definition.actions, + resourceArn, + roleTokens[roleIndex], + this.resourceStack, ); + acceptor(this.getInstanceProps).acceptResourceAccess( + policy, + this.ssmEnvironmentEntries, + ); + this.policies.push(policy); + roleIndex += 1; }); }); + + return this.policies; }; } @@ -81,10 +113,16 @@ export class GeoAccessOrchestrator { * Instance Manager for Geo Access Orchestration */ export class GeoAccessOrchestratorFactory { - private readonly resourceType: GeoResourceType; - getInstance = ( geoAccessGenerator: GeoAccessGenerator, getInstanceProps: ConstructFactoryGetInstanceProps, - ) => new GeoAccessOrchestrator(geoAccessGenerator, getInstanceProps); + stack: Stack, + ssmEnvironmentEntries: SsmEnvironmentEntry[], + ) => + new GeoAccessOrchestrator( + geoAccessGenerator, + getInstanceProps, + stack, + ssmEnvironmentEntries, + ); } diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 1e0f081623a..5cc305395b1 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -1,6 +1,6 @@ -import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { GeoAction } from './types.js'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { AmplifyFault } from '@aws-amplify/platform-core'; +import { Stack } from 'aws-cdk-lib'; /** * Geo Access Policy Factory @@ -8,11 +8,12 @@ import { AmplifyFault } from '@aws-amplify/platform-core'; * Responsible for policy statement generation and policy-role attachment. */ export class GeoAccessPolicyFactory { - // creating a singular IAM policy - createPolicyStatement = ( - permissions: GeoAction[], // organize create policy such that one resource type maps to the actions + createPolicy = ( + permissions: string[], // organize create policy such that one resource type maps to the actions resourceArn: string, - ): PolicyStatement => { + roleToken: string, + stack: Stack, + ) => { if (permissions.length === 0) { throw new AmplifyFault('EmptyPolicyFault', { message: 'At least one permission must be specified', @@ -28,14 +29,14 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - return policyStatement; // returns policy statement with all policies + return new Policy(stack, `geo-access-policy`, { + policyName: `geo-${roleToken}-access-policy`, + statements: [policyStatement], + }); // returns policy with policy statement of all actions }; - - attachPolicy = (userRole: IRole, statement: PolicyStatement) => - userRole.addToPrincipalPolicy(statement); } -const actionDirectory: Record = { +const actionDirectory: Record = { get: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], autocomplete: ['geo-places:Autocomplete'], geocode: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts new file mode 100644 index 00000000000..4a4275e8403 --- /dev/null +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -0,0 +1,152 @@ +import { IAspect, Stack } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; +import { AmplifyCollection } from './collection_construct.js'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyPlace } from './place_resource.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; + +/** + * Aspect Implementation for Geo Resources + */ +export class AmplifyGeoOutputsAspect implements IAspect { + /** + * Steps to be accomplished within this class: + * 1. constructor setup (receives output strategy -> schema) + * 2. default collection processing (multiplicity error handling) + * 3. store the outputs for all collections within outputStorageStrategy + */ + isGeoOutputProcessed: boolean = false; + defaultCollectionName: string | undefined = undefined; + private readonly geoOutputStorageStrategy: BackendOutputStorageStrategy; + /** + * Constructs an instance of the AmplifyGeoOutputsAspect + * @param outputStorageStrategy - storage schema for Geo outputs + */ + constructor(outputStorageStrategy: BackendOutputStorageStrategy) { + this.geoOutputStorageStrategy = outputStorageStrategy; + } + + /** + * Interface requirement of IAspect that is called during CDK synthesis time + * @param node - current construct + */ + public visit(node: IConstruct): void { + if ( + !(node instanceof AmplifyMap) || + !(node instanceof AmplifyPlace) || + !(node instanceof AmplifyCollection) || + this.isGeoOutputProcessed + ) { + return; + } + + this.isGeoOutputProcessed = true; // once this is visited, this no longer remains false + + const mapInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyMap, + ) as AmplifyMap[]; + + const placeInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyPlace, + ) as AmplifyPlace[]; + + const collectionInstances = Stack.of(node).node.children.filter( + (el) => el instanceof AmplifyCollection, + ) as AmplifyCollection[]; + + if ( + mapInstances.length > 0 || + placeInstances.length > 0 || + collectionInstances.length > 0 + ) { + this.addBackendOutput( + collectionInstances, + this.geoOutputStorageStrategy, + Stack.of(node).region, + ); + } + } + + private findDefaultCollectionName = ( + nodes: AmplifyCollection[], + currentNode: AmplifyCollection, + ): string | undefined => { + const geoCount = nodes.length; + + let defaultCollectionName: string | undefined = undefined; + + // go through all children and find the default (make duplicity check on defaults) + nodes.forEach((instance) => { + if (!defaultCollectionName && instance.isDefault) { + // if no default exists and instance is default, mark it + defaultCollectionName = + instance.resources.collection?.geofenceCollectionName; + } else if (instance.isDefault && defaultCollectionName) { + // if default exists and instance is default (throw multiple defaults error) + throw new AmplifyUserError('MultipleDefaultCollectionError', { + message: + 'Multiple instances of geofence collections have been marked as default.', + resolution: + 'Remove `isDefault: true` from all but one `defineCollection` call.', + }); + } + }); + + if (geoCount === 1 && !defaultCollectionName) { + // if no defaults and only one construct, instance assumed to be default + defaultCollectionName = + currentNode.resources.collection?.geofenceCollectionName; + } else if (geoCount > 1 && !defaultCollectionName) { + // if multiple constructs with default collection, throw error + throw new AmplifyUserError('NoDefaultCollectionError', { + message: + 'No instances of geofence collections have been marked as default.', + resolution: + 'Add `isDefault: true` to one of the `defineCollection` calls.', + }); + } + + return defaultCollectionName; + }; + + /** + * Function responsible for add all collection outputs (with defaults) + * @param collections - all construct instances of AmplifyGeo + * @param outputStorageStrategy - backend output schema of type GeoOutput + * @param region - + */ + private addBackendOutput( + collections: AmplifyCollection[], + outputStorageStrategy: BackendOutputStorageStrategy, + region: string, + ) { + const defaultCollectionName: string | undefined = + this.findDefaultCollectionName(collections, collections[0]); + + outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: { + aws_region: region, + geofence_collections: JSON.stringify({ + default: defaultCollectionName, + items: defaultCollectionName, + }), + }, + }); + + collections.forEach((collection) => { + if (!collection.isDefault) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofence_collections: JSON.stringify({ + items: collection.resources.collection.geofenceCollectionName, + }), + }, + }); + } + }); + } +} diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 8c575b470e0..9237d106c6d 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,2 +1,2 @@ -export { defineMap, definePlace, defineCollection } from './factory.js'; +export { defineCollection } from './collection_factory.js'; export * from './types.js'; diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts new file mode 100644 index 00000000000..3d374d22cd9 --- /dev/null +++ b/packages/backend-geo/src/map_factory.ts @@ -0,0 +1,115 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyMapFactoryProps, MapResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct Factory for AmplifyMap + */ +export class AmplifyMapFactory + implements ConstructFactory> +{ + static mapCount: number = 0; + + private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Constructs a new AmplifyMapFactory instance + * @param props - map resource properties + */ + constructor(private readonly props: AmplifyMapFactoryProps) { + if (AmplifyMapFactory.mapCount > 0) { + throw new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `defineMap` calls not permitted within an Amplify backend', + resolution: 'Maintain one `defineMap` call', + }); + } + AmplifyMapFactory.mapCount++; + } + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyMap => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyMapGenerator(this.props, getInstanceProps); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyMap; + }; +} + +/** + * Construct Container Entry Generator for AmplifyMap + */ +export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of AmplifyMapGenerator + */ + constructor( + private readonly props: AmplifyMapFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyMap = new AmplifyMap(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyMap).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( + amplifyMap.getResourceArn(), + 'map', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyMap)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyMap; + }; +} + +/** + * Integrate access for an AWS-managed map within your backend. + */ +export const defineMap = ( + props: AmplifyMapFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyMapFactory(props); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts new file mode 100644 index 00000000000..7fa416e2fdc --- /dev/null +++ b/packages/backend-geo/src/map_resource.ts @@ -0,0 +1,33 @@ +import { AmplifyMapProps, MapResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Aws, Resource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +/** + * Resource for AWS-managed Maps + */ +export class AmplifyMap + extends Resource + implements ResourceProvider, StackProvider +{ + readonly resources: MapResources; + readonly id: string; + readonly name: string; + + /** + * Creates an instance of AmplifyMap + */ + constructor(scope: Construct, id: string, props: AmplifyMapProps) { + super(scope, id); + + this.name = props.name; + } + + getResourceArn = (): string => { + return `arn:${Aws.PARTITION}:geo-maps:${this.stack.region}::provider/default`; + }; + + getResourceName = (): string => { + return this.name; + }; +} diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts new file mode 100644 index 00000000000..9b5bb3ea0b4 --- /dev/null +++ b/packages/backend-geo/src/place_factory.ts @@ -0,0 +1,119 @@ +import { + AmplifyResourceGroupName, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, + StackProvider, +} from '@aws-amplify/plugin-types'; +import { Aspects, Stack, Tags } from 'aws-cdk-lib/core'; +import { AmplifyPlaceFactoryProps, PlaceResources } from './types.js'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { AmplifyUserError, TagName } from '@aws-amplify/platform-core'; +import { AmplifyPlace } from './place_resource.js'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; + +/** + * Construct Factory for AmplifyPlace + */ +export class AmplifyPlaceFactory + implements ConstructFactory> +{ + static mapCount: number = 0; + + private geoGenerator: ConstructContainerEntryGenerator; + private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = + new GeoAccessOrchestratorFactory(); + + /** + * Constructs a new AmplifyPlaceFactory instance + * @param props - place resource properties + */ + constructor(private readonly props: AmplifyPlaceFactoryProps) { + if (AmplifyPlaceFactory.mapCount > 0) { + throw new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `definePlace` calls not permitted within an Amplify backend', + resolution: 'Maintain one `definePlace` call', + }); + } + AmplifyPlaceFactory.mapCount++; + } + + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps, + ): AmplifyPlace => { + // get construct factory instance properties + const { constructContainer, resourceNameValidator } = getInstanceProps; + + // validates the user-entered resource name (according to CDK naming regulations) + resourceNameValidator?.validate(this.props.name); + + // generates a singleton container entry for this construct factory + if (!this.geoGenerator) { + this.geoGenerator = new AmplifyPlaceGenerator( + this.props, + getInstanceProps, + ); + } + + // this getOrCompute accesses the internal construct container cache + return constructContainer.getOrCompute(this.geoGenerator) as AmplifyPlace; + }; +} + +/** + * Construct Container Entry Generator for AmplifyPlace + */ +export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { + readonly resourceGroupName: AmplifyResourceGroupName = 'geo'; + + /** + * Creates an instance of AmplifyPlaceGenerator + */ + constructor( + private readonly props: AmplifyPlaceFactoryProps, + private readonly getInstanceProps: ConstructFactoryGetInstanceProps, + private readonly geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = new GeoAccessOrchestratorFactory(), + ) {} + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + + const amplifyPlace = new AmplifyPlace(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); + + amplifyPlace.resources.policies = + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyPlace.getResourceArn(), + 'map', + ); + + const geoAspects = Aspects.of(Stack.of(amplifyPlace)); + if (!geoAspects.all.length) { + new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + } + + return amplifyPlace; + }; +} + +/** + * Integrate access for an AWS-managed place index within your backend. + */ +export const definePlace = ( + props: AmplifyPlaceFactoryProps, +): ConstructFactory & StackProvider> => + new AmplifyPlaceFactory(props); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts new file mode 100644 index 00000000000..7dcd13dca65 --- /dev/null +++ b/packages/backend-geo/src/place_resource.ts @@ -0,0 +1,33 @@ +import { AmplifyPlaceProps, PlaceResources } from './types.js'; +import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; +import { Aws, Resource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +/** + * Resource for AWS-managed Place Indices + */ +export class AmplifyPlace + extends Resource + implements ResourceProvider, StackProvider +{ + readonly resources: PlaceResources; + readonly id: string; + readonly name: string; + + /** + * Creates an instance of AmplifyPlace + */ + constructor(scope: Construct, id: string, props: AmplifyPlaceProps) { + super(scope, id); + + this.name = props.name; + } + + getResourceArn = (): string => { + return `arn:${Aws.PARTITION}:geo-places:${this.stack.region}::provider/default`; + }; + + getResourceName = (): string => { + return this.name; + }; +} diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index da8fb917434..34decc54b0f 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -1,6 +1,7 @@ import { BackendOutputStorageStrategy, ConstructFactoryGetInstanceProps, + ResourceAccessAcceptor, } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { @@ -8,30 +9,110 @@ import { GeofenceCollectionProps, } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { IRole } from 'aws-cdk-lib/aws-iam'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; +import { Policy } from 'aws-cdk-lib/aws-iam'; // ----------------------------------- factory properties ---------------------------------------------- -// factory properties include construct properties without output strategy (because that's loaded inside factory) -export type AmplifyGeoFactoryProps = Omit< - AmplifyGeoProps, +/** + * Properties of AmplifyMap + */ +export type AmplifyMapFactoryProps = Omit< + AmplifyMapProps, 'outputStorageStrategy' > & { - region: string; + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const map = defineMap({ + * access: (allow) => ( + * allow.authenticated.to(["get"]) + * ) + * }) + */ access: GeoAccessGenerator; - resourceIdentifier?: GeoResourceType; }; -export type AmplifyGeoProps = { +/** + * Properties of AmplifyPlace + */ +export type AmplifyPlaceFactoryProps = Omit< + AmplifyPlaceProps, + 'outputStorageStrategy' +> & { + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const index = definePlace({ + * access: (allow) => ( + * allow.authenticated.to(["geocode"]) + * ) + * }) + */ + access: GeoAccessGenerator; +}; + +/** + * Properties of AmplifyCollection + */ +export type AmplifyCollectionFactoryProps = Omit< + AmplifyCollectionProps, + 'outputStorageStrategy' +> & { + /** + * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) + * @example + * const collection = defineCollection({ + * access: (allow) => ( + * allow.authenticated.to(["create"]) + * ) + * }) + */ + access: GeoAccessGenerator; +}; + +export type AmplifyMapProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; + +export type AmplifyPlaceProps = { name: string; - collectionProps?: GeofenceCollectionProps; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; +export type AmplifyCollectionProps = { + name: string; + collectionProps: GeofenceCollectionProps; + isDefault: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; -export type GeoResources = { +/** + * Backend-accessible resources from AmplifyMap + * @param policies - access policies of the frontend-accessible map resource + */ +export type MapResources = { + policies: Policy[]; +}; + +/** + * Backend-accessible resources from AmplifyPlace + * @param policies - access policies of the frontend-accessible place resource + */ +export type PlaceResources = { + policies: Policy[]; +}; + +/** + * Backend-accessible resources from AmplifyCollection + * @param collection - provisioned geofence collection resource + * @param policies - access policies of the provisioned collection resource + * @param cfnResources - cloudformation resources exposed from the abstracted collection provisioned from collection + */ +export type CollectionResources = { collection: GeofenceCollection; + policies: Policy[]; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -50,13 +131,14 @@ export type GeoAccessBuilder = { }; export type GeoActionBuilder = { - // access builder (within defineX()) - to: (actions: GeoAction[]) => GeoAccessDefinition; + to: (actions: string[]) => GeoAccessDefinition; }; export type GeoAccessDefinition = { - userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; - actions: GeoAction[]; + getAccessAcceptors: (( + getInstanceProps: ConstructFactoryGetInstanceProps, + ) => ResourceAccessAcceptor)[]; + actions: string[]; uniqueDefinitionValidators: { uniqueRoleToken: string; validationErrorOptions: AmplifyUserErrorOptions; @@ -65,16 +147,8 @@ export type GeoAccessDefinition = { // ----------------------------------- misc. types ---------------------------------------------- -export type MapAction = 'get'; - -export type IndexAction = 'autocomplete' | 'geocode' | 'search'; - -export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; - -export type GeoAction = MapAction | IndexAction | CollectionAction; - -export const geoCfnResourceTypes = ['collection']; - -export const geoManagedResourceTypes = ['map', 'place']; - -export type GeoResourceType = 'map' | 'place' | 'collection'; +export const resourceActionRecord: Record = { + map: ['get'], + place: ['autocomplete', 'geocode', 'search'], + collection: ['create', 'read', 'update', 'delete', 'list'], +}; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 2c74fc45cab..4a569fb9343 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -1,10 +1,14 @@ import { z } from 'zod'; +const collectionSchema = z.object({ + default: z.string(), + items: z.string(z.array(z.string())).optional(), +}); + export const geoOutputSchema = z.object({ version: z.literal('1'), payload: z.object({ - defaultCollection: z.string(), - geoRegion: z.string(), - collections: z.string(z.array(z.string()).optional()), // JSON serialized array of collection names + aws_region: z.string(), + geofence_collections: z.string(collectionSchema).optional(), }), }); From e031f1fcc4e5ec162eb06a8f0969ea8dda894d44 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:29:00 -0700 Subject: [PATCH 37/93] unit testing v1 and debugging --- package-lock.json | 1 + packages/backend-geo/package.json | 1 + .../backend-geo/src/access_builder.test.ts | 290 +++++++++ .../src/collection_construct.test.ts | 284 +++++++++ .../backend-geo/src/collection_construct.ts | 11 + .../src/collection_factory.test.ts | 161 +++++ .../backend-geo/src/collection_factory.ts | 18 +- .../src/geo_access_orchestrator.test.ts | 557 ++++++++++++++++++ .../src/geo_access_orchestrator.ts | 19 +- .../src/geo_access_policy_factory.test.ts | 482 +++++++++++++++ .../src/geo_access_policy_factory.ts | 7 +- .../src/geo_outputs_aspect.test.ts | 233 ++++++++ .../backend-geo/src/geo_outputs_aspect.ts | 37 +- packages/backend-geo/src/map_factory.test.ts | 172 ++++++ packages/backend-geo/src/map_factory.ts | 14 +- packages/backend-geo/src/map_resource.test.ts | 107 ++++ packages/backend-geo/src/map_resource.ts | 11 +- .../backend-geo/src/place_factory.test.ts | 179 ++++++ packages/backend-geo/src/place_factory.ts | 26 +- .../backend-geo/src/place_resource.test.ts | 111 ++++ packages/backend-geo/src/place_resource.ts | 6 + packages/backend-geo/src/types.ts | 30 +- 22 files changed, 2685 insertions(+), 72 deletions(-) create mode 100644 packages/backend-geo/src/access_builder.test.ts create mode 100644 packages/backend-geo/src/collection_construct.test.ts create mode 100644 packages/backend-geo/src/collection_factory.test.ts create mode 100644 packages/backend-geo/src/geo_access_orchestrator.test.ts create mode 100644 packages/backend-geo/src/geo_access_policy_factory.test.ts create mode 100644 packages/backend-geo/src/geo_outputs_aspect.test.ts create mode 100644 packages/backend-geo/src/map_factory.test.ts create mode 100644 packages/backend-geo/src/map_resource.test.ts create mode 100644 packages/backend-geo/src/place_factory.test.ts create mode 100644 packages/backend-geo/src/place_resource.test.ts diff --git a/package-lock.json b/package-lock.json index 36c10a13041..fe5b726d89f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49773,6 +49773,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" }, diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index 06c6879d8af..bb3219ca9f5 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } diff --git a/packages/backend-geo/src/access_builder.test.ts b/packages/backend-geo/src/access_builder.test.ts new file mode 100644 index 00000000000..88ef3387565 --- /dev/null +++ b/packages/backend-geo/src/access_builder.test.ts @@ -0,0 +1,290 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import assert from 'node:assert'; +import { roleAccessBuilder } from './access_builder.js'; +import { + ConstructContainer, + ConstructFactoryGetInstanceProps, + ResourceProvider, +} from '@aws-amplify/plugin-types'; + +void describe('GeoAccessBuilder', () => { + const resourceAccessAcceptorMock = mock.fn(); + const group1AccessAcceptorMock = mock.fn(); + const group2AccessAcceptorMock = mock.fn(); + + const getResourceAccessAcceptorMock = mock.fn((roleName: string) => { + switch (roleName) { + case 'group1Name': + return group1AccessAcceptorMock; + case 'group2Name': + return group2AccessAcceptorMock; + default: + return resourceAccessAcceptorMock; + } + }); + + const getConstructFactoryMock = mock.fn( + // this lets us get proper typing on the mock args + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_: string) => ({ + getInstance: () => + ({ + getResourceAccessAcceptor: getResourceAccessAcceptorMock, + }) as unknown as T, + }), + ); + + const mockGetInstanceProps: ConstructFactoryGetInstanceProps = { + constructContainer: { + getConstructFactory: getConstructFactoryMock, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + beforeEach(() => { + getResourceAccessAcceptorMock.mock.resetCalls(); + getConstructFactoryMock.mock.resetCalls(); + resourceAccessAcceptorMock.mock.resetCalls(); + group1AccessAcceptorMock.mock.resetCalls(); + group2AccessAcceptorMock.mock.resetCalls(); + }); + + void it('builds geo access definition for authenticated role', () => { + const accessDefinition = roleAccessBuilder.authenticated.to([ + 'get', + 'search', + ]); + + assert.deepStrictEqual(accessDefinition.actions, ['get', 'search']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [resourceAccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'authenticatedUserIamRole', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'authenticated', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for authenticated users specified multiple times.', + ); + }); + + void it('builds geo access definition for guest role', () => { + const accessDefinition = roleAccessBuilder.guest.to(['get']); + + assert.deepStrictEqual(accessDefinition.actions, ['get']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [resourceAccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'unauthenticatedUserIamRole', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'guest', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for guest users specified multiple times.', + ); + }); + + void it('builds geo access definition for single user pool group', () => { + const accessDefinition = roleAccessBuilder + .groups(['group1Name']) + .to(['create', 'read']); + + assert.deepStrictEqual(accessDefinition.actions, ['create', 'read']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [group1AccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'group1Name', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'group-group1Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for the group group1Name specified multiple times.', + ); + }); + + void it('builds geo access definition for multiple user pool groups', () => { + const accessDefinition = roleAccessBuilder + .groups(['group1Name', 'group2Name']) + .to(['update', 'delete']); + + assert.deepStrictEqual(accessDefinition.actions, ['update', 'delete']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [group1AccessAcceptorMock, group2AccessAcceptorMock], + ); + assert.equal( + getConstructFactoryMock.mock.calls[0].arguments[0], + 'AuthResources', + ); + assert.equal( + getConstructFactoryMock.mock.calls[1].arguments[0], + 'AuthResources', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[0].arguments[0], + 'group1Name', + ); + assert.equal( + getResourceAccessAcceptorMock.mock.calls[1].arguments[0], + 'group2Name', + ); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 2); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'group-group1Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[1].uniqueRoleToken, + 'group-group2Name', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for the group group1Name specified multiple times.', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[1].validationErrorOptions + .message, + 'Access definition for the group group2Name specified multiple times.', + ); + }); + + void it('throws error when auth construct factory is not found', () => { + const getConstructFactoryMockReturnsNull = mock.fn(() => null); + const stubGetInstancePropsWithNullFactory: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockReturnsNull, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder.authenticated.to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullFactory, + ); + }, + { + message: + 'Cannot specify geo resource access for authenticatedUserIamRole users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('throws error when auth construct factory getInstance returns null resource access acceptor', () => { + const getResourceAccessAcceptorMockReturnsNull = mock.fn(() => null); + const getConstructFactoryMockWithNullAcceptor = mock.fn(() => ({ + getInstance: () => ({ + getResourceAccessAcceptor: getResourceAccessAcceptorMockReturnsNull, + }), + })); + + const stubGetInstancePropsWithNullAcceptor: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockWithNullAcceptor, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder.guest.to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullAcceptor, + ); + }, + { + message: + 'Cannot specify geo resource access for unauthenticatedUserIamRole users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('throws error for group access when auth is not defined', () => { + const getConstructFactoryMockReturnsNull = mock.fn(() => null); + const stubGetInstancePropsWithNullFactory: ConstructFactoryGetInstanceProps = + { + constructContainer: { + getConstructFactory: getConstructFactoryMockReturnsNull, + } as unknown as ConstructContainer, + } as unknown as ConstructFactoryGetInstanceProps; + + const accessDefinition = roleAccessBuilder + .groups(['testGroup']) + .to(['get']); + + assert.throws( + () => { + accessDefinition.getAccessAcceptors[0]( + stubGetInstancePropsWithNullFactory, + ); + }, + { + message: + 'Cannot specify geo resource access for testGroup users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.', + }, + ); + }); + + void it('handles empty group array', () => { + const accessDefinition = roleAccessBuilder.groups([]).to(['get']); + + assert.deepStrictEqual(accessDefinition.actions, ['get']); + assert.equal(accessDefinition.getAccessAcceptors.length, 0); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 0); + }); + + void it('handles empty actions array', () => { + const accessDefinition = roleAccessBuilder.authenticated.to([]); + + assert.deepStrictEqual(accessDefinition.actions, []); + assert.equal(accessDefinition.getAccessAcceptors.length, 1); + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + }); +}); diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts new file mode 100644 index 00000000000..3d30b3a1c77 --- /dev/null +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -0,0 +1,284 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyCollection } from './collection_construct.js'; +import { App, Stack } from 'aws-cdk-lib'; +import { Match, Template } from 'aws-cdk-lib/assertions'; +import assert from 'node:assert'; +import * as kms from 'aws-cdk-lib/aws-kms'; + +void describe('AmplifyCollection', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + void it('creates a geofence collection', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + }); + + void it('sets collection name correctly', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'myTestCollection', + collectionProps: { + geofenceCollectionName: 'myTestCollection', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'myTestCollection', + }); + }); + + void it('sets isDefault property correctly when true', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: true, + }); + + assert.equal(collection.isDefault, true); + assert.equal(collection.name, 'testCollectionName'); + assert.equal(collection.id, 'testCollection'); + }); + + void it('sets isDefault property correctly when false', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + assert.equal(collection.isDefault, false); + }); + + void it('defaults isDefault to false when not specified', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + }); + + assert.equal(collection.isDefault, false); + }); + + void it('stores attribution data in stack', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + const template = Template.fromStack(stack); + assert.equal( + JSON.parse(template.toJSON().Description).stackType, + 'geo-GeofenceCollection', + ); + }); + + void it('sets collection description when provided', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + description: 'Test geofence collection for unit testing', + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Description: 'Test geofence collection for unit testing', + }); + }); + + void it('sets KMS key when provided', () => { + new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + kmsKey: new kms.Key(stack, 'testKey', {}), + }, + isDefault: false, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + KmsKeyId: { + 'Fn::GetAtt': ['testKey1CDDDD5E', 'Arn'], + }, + }); + }); + + void it('exposes collection resource correctly', () => { + const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + assert.ok(amplifyCollection.resources.collection); + assert.ok(amplifyCollection.resources.cfnResources.cfnCollection); + }); + + void it('exposes CFN resources for overrides', () => { + const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Test that CFN resource is accessible for overrides + assert.ok(amplifyCollection.resources.cfnResources.cfnCollection); + assert.equal( + amplifyCollection.resources.cfnResources.cfnCollection.collectionName, + 'testCollectionName', + ); + }); + + void it('sets tags when provided via CFN resource', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Set tags via the exposed CFN resource + collection.resources.cfnResources.cfnCollection.tags = [ + { + key: 'Environment', + value: 'test', + }, + { + key: 'Project', + value: 'amplify-geo', + }, + ]; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Tags: [ + { + Key: 'Environment', + Value: 'test', + }, + { + Key: 'Project', + Value: 'amplify-geo', + }, + ], + }); + }); + + void describe('collection overrides', () => { + void it('can override collection properties', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + // Override the description via CFN resource + collection.resources.cfnResources.cfnCollection.description = + 'Overridden description'; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + Description: 'Overridden description', + }); + }); + + void it('can override KMS key via CFN resource', () => { + const collection = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: { + geofenceCollectionName: 'testCollectionName', + }, + isDefault: false, + }); + + collection.resources.cfnResources.cfnCollection.kmsKeyId = + 'arn:aws:kms:us-west-2:123456789012:key/87654321-4321-4321-4321-210987654321'; + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'testCollectionName', + KmsKeyId: + 'arn:aws:kms:us-west-2:123456789012:key/87654321-4321-4321-4321-210987654321', + }); + }); + }); + + void describe('resource properties validation', () => { + void it('creates collection with minimal required properties', () => { + const collection = new AmplifyCollection(stack, 'minimalCollection', { + name: 'minimal', + collectionProps: {}, + isDefault: false, + }); + + assert.equal(collection.name, 'minimal'); + assert.equal(collection.id, 'minimalCollection'); + assert.equal(collection.isDefault, false); + assert.ok(collection.resources.collection); + assert.ok(collection.resources.cfnResources.cfnCollection); + + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: Match.stringLikeRegexp('.*minimal.*'), + }); + }); + + void it('creates collection with all optional properties', () => { + const collection = new AmplifyCollection(stack, 'fullCollection', { + name: 'fullFeatureCollection', + collectionProps: { + geofenceCollectionName: 'fullFeatureCollection', + description: 'A fully configured geofence collection', + kmsKey: new kms.Key(stack, 'testKey', {}), + }, + isDefault: true, + }); + + assert.equal(collection.name, 'fullFeatureCollection'); + assert.equal(collection.id, 'fullCollection'); + assert.equal(collection.isDefault, true); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'fullFeatureCollection', + Description: 'A fully configured geofence collection', + KmsKeyId: { + 'Fn::GetAtt': ['testKey1CDDDD5E', 'Arn'], + }, + }); + }); + }); +}); diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index fe1724eb16a..564497e63bb 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -5,6 +5,10 @@ import { Stack } from 'aws-cdk-lib'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { Policy } from 'aws-cdk-lib/aws-iam'; +import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage'; +import { fileURLToPath } from 'node:url'; + +const geoStackType = 'geo-GeofenceCollection'; /** * Amplify Collection CDK Construct @@ -34,6 +38,7 @@ export class AmplifyCollection this.name = props.name; this.id = id; this.isDefault = props.isDefault || false; + this.stack = Stack.of(scope); const geofenceCollection = new GeofenceCollection( this, @@ -49,5 +54,11 @@ export class AmplifyCollection ) as CfnGeofenceCollection, }, }; + + new AttributionMetadataStorage().storeAttributionMetadata( + Stack.of(this), + geoStackType, + fileURLToPath(new URL('../package.json', import.meta.url)), + ); } } diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts new file mode 100644 index 00000000000..d718505caf7 --- /dev/null +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -0,0 +1,161 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { defineCollection } from './collection_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { CollectionResources } from './types.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let collectionFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyCollectionFactory', () => { + beforeEach(() => { + collectionFactory = defineCollection({ + name: 'testCollection', + collectionProps: { + geofenceCollectionName: 'testCollection', + }, + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = collectionFactory.getInstance(getInstanceProps); + const instance2 = collectionFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('adds construct to stack', () => { + const collectionConstruct = collectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + + template.resourceCountIs('AWS::Location::GeofenceCollection', 1); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const collectionFactory = defineCollection({ + name: '|$%#86430resource', + collectionProps: {}, + }); + assert.throws( + () => + collectionFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + + void it('applies friendly name tag', () => { + const collectionConstruct = collectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + + // Check that the friendly name tag is applied + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + Tags: [ + { + Key: 'amplify:friendly-name', + Value: 'testCollection', + }, + ], + }); + }); + + void it('creates collection with custom collection properties', () => { + const customCollectionFactory = defineCollection({ + name: 'customCollection', + collectionProps: { + geofenceCollectionName: 'customCollection', + description: 'Custom test collection', + }, + }); + + const collectionConstruct = + customCollectionFactory.getInstance(getInstanceProps); + + const template = Template.fromStack( + Stack.of(collectionConstruct.resources.collection), + ); + template.hasResourceProperties('AWS::Location::GeofenceCollection', { + CollectionName: 'customCollection', + Description: 'Custom test collection', + }); + }); + + void it('verifies stack property exists and is equal to collection stack', () => { + const collectionConstructFactory = defineCollection({ + name: 'testCollection', + collectionProps: { + geofenceCollectionName: 'testCollection', + }, + }).getInstance(getInstanceProps); + + assert.equal( + collectionConstructFactory.stack, + Stack.of(collectionConstructFactory.resources.collection), + ); + }); +}); diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index 65c92853a51..cb9e1cb6fc0 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -70,13 +70,6 @@ export class AmplifyCollectionGenerator generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { - const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - Stack.of(scope), - [], - ); - const amplifyCollection = new AmplifyCollection(scope, this.props.name, { ...this.props, outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, @@ -84,6 +77,17 @@ export class AmplifyCollectionGenerator Tags.of(amplifyCollection).add(TagName.FRIENDLY_NAME, this.props.name); + if (!this.props.access) { + return amplifyCollection; + } + + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + amplifyCollection.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyCollection.resources.collection.geofenceCollectionArn, diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts new file mode 100644 index 00000000000..c9402da07d6 --- /dev/null +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -0,0 +1,557 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { GeoAccessOrchestratorFactory } from './geo_access_orchestrator.js'; +import { + ConstructFactoryGetInstanceProps, + SsmEnvironmentEntry, +} from '@aws-amplify/plugin-types'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +void describe('GeoAccessOrchestrator', () => { + void describe('orchestrateGeoAccess', () => { + let stack: Stack; + + const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = [ + { name: 'TEST_GEO_RESOURCE_NAME', path: 'test/ssm/path/to/geo/resource' }, + ]; + + const testResourceArn = + 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + + beforeEach(() => { + stack = createStackAndSetContext(); + }); + + void it('throws if invalid actions are provided for resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['invalidAction'], // Invalid action for collection + getAccessAcceptors: [ + () => ({ + identifier: 'testAcceptor', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + new AmplifyUserError('ActionNotFoundError', { + message: + 'Desired access action not found for the specific collection resource.', + resolution: + 'Please refer to specific collection access actions for more information.', + }), + ); + }); + + void it('throws if duplicate role tokens are provided', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'testAcceptor', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }, + }, + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + new AmplifyUserError('InvalidGeoAccessDefinitionError', { + message: 'Duplicate authenticated access definition', + resolution: 'Combine access definitions', + }), + ); + }); + + void it('handles multiple actions for single access definition', () => { + const acceptResourceAccessMock = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read', 'create', 'update'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + const policies = geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.equal(policies.length, 1); + }); + + void it('handles multiple access acceptors for single definition', () => { + const acceptResourceAccessMock1 = mock.fn(); + const acceptResourceAccessMock2 = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'group-admin', + acceptResourceAccess: acceptResourceAccessMock1, + }), + () => ({ + identifier: 'group-user', + acceptResourceAccess: acceptResourceAccessMock2, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'group-admin', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + { + uniqueRoleToken: 'group-user', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + + assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); + assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); + + assert.deepStrictEqual( + acceptResourceAccessMock1.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.deepStrictEqual( + acceptResourceAccessMock2.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('handles multiple access definitions', () => { + const acceptResourceAccessMock1 = mock.fn(); + const acceptResourceAccessMock2 = mock.fn(); + + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['read'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock1, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + { + actions: ['create', 'update'], + getAccessAcceptors: [ + () => ({ + identifier: 'group-admin', + acceptResourceAccess: acceptResourceAccessMock2, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'group-admin', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + + assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); + assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock1.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + + assert.deepStrictEqual( + acceptResourceAccessMock2.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Effect: 'Allow', + Resource: testResourceArn, + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('validates actions for map resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get'], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + // Should not throw for valid map action + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo-maps:us-east-1::provider/default', + 'map', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Effect: 'Allow', + Resource: 'arn:aws:geo-maps:us-east-1::provider/default', + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('validates actions for place resource type', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['search', 'geocode'], // Valid for place + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + // Should not throw for valid place actions + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo-places:us-east-1::provider/default', + 'place', + ); + assert.equal(acceptResourceAccessMock.mock.callCount(), 1); + assert.deepStrictEqual( + acceptResourceAccessMock.mock.calls[0].arguments[0].document.toJSON(), + { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + ], + Effect: 'Allow', + Resource: 'arn:aws:geo-places:us-east-1::provider/default', + }, + ], + Version: '2012-10-17', + }, + ); + }); + + void it('throws for invalid action on map resource', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['create'], // Invalid for map (valid for collection) + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo:us-east-1:123456789012:map/test-map', + 'map', + ), + new AmplifyUserError('ActionNotFoundError', { + message: + 'Desired access action not found for the specific map resource.', + resolution: + 'Please refer to specific map access actions for more information.', + }), + ); + }); + + void it('handles empty actions array', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: [], + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + ), + { message: 'At least one permission must be specified' }, + ); + }); + }); +}); + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 5ab49e6c674..704cc8a646e 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -5,6 +5,7 @@ import { import { GeoAccessBuilder, GeoAccessGenerator, + GeoResourceType, resourceActionRecord, } from './types.js'; import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; @@ -29,8 +30,8 @@ export class GeoAccessOrchestrator { * @param getInstanceProps - instance properties of a specific construct factory * @param geoStack - instance of GeoAccessPolicyFactory to generate policyStatements * @param ssmEnvironmentEntries - permission reader and processor - * @param geoPolicyFactory - - * @param roleAccessBuilder - + * @param geoPolicyFactory - instance of the GeoAccessPolicyFactory for policy generation + * @param roleAccessBuilder - instance of the GeoAccessBuilder for access definition transformation */ constructor( private readonly geoAccessGenerator: GeoAccessGenerator, @@ -45,11 +46,12 @@ export class GeoAccessOrchestrator { /** * Orchestrates the process of translating the customer-provided storage access rules into IAM policies and attaching those policies to the appropriate roles. - * + * @param resourceArn - Amazon Resource Name (ARN) for the resource with access permissions + * @param resourceIdentifier - type of resource being defined */ orchestrateGeoAccess = ( resourceArn: string, - resourceIdentifier: string, + resourceIdentifier: GeoResourceType, ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( @@ -57,7 +59,6 @@ export class GeoAccessOrchestrator { ); geoAccessDefinitions.forEach((definition) => { - // get all user roles for each definition const uniqueRoleTokenSet = new Set(); definition.uniqueDefinitionValidators.forEach( @@ -83,16 +84,12 @@ export class GeoAccessOrchestrator { } }); - const roleTokens = Array.from(uniqueRoleTokenSet); - - let roleIndex: number = 0; // need respective roleToken for policy generation definition.getAccessAcceptors.forEach((acceptor) => { // for each acceptor within auth, guest, or user groups - const policy: Policy = this.geoPolicyFactory.createPolicy( definition.actions, resourceArn, - roleTokens[roleIndex], + acceptor(this.getInstanceProps).identifier, this.resourceStack, ); acceptor(this.getInstanceProps).acceptResourceAccess( @@ -100,7 +97,6 @@ export class GeoAccessOrchestrator { this.ssmEnvironmentEntries, ); this.policies.push(policy); - roleIndex += 1; }); }); @@ -108,7 +104,6 @@ export class GeoAccessOrchestrator { }; } -// needed for test mocking /** * Instance Manager for Geo Access Orchestration */ diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts new file mode 100644 index 00000000000..76b46b218f2 --- /dev/null +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -0,0 +1,482 @@ +import { App, Stack } from 'aws-cdk-lib'; +import { beforeEach, describe, it } from 'node:test'; +import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; +import assert from 'node:assert'; +import { Template } from 'aws-cdk-lib/assertions'; +import { AccountPrincipal, Policy, Role } from 'aws-cdk-lib/aws-iam'; + +void describe('GeoAccessPolicyFactory', () => { + let stack: Stack; + let geoAccessPolicyFactory: GeoAccessPolicyFactory; + const testResourceArn = + 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + + beforeEach(() => { + const app = new App(); + stack = new Stack(app); + geoAccessPolicyFactory = new GeoAccessPolicyFactory(); + }); + + void it('throws if no permissions are specified', () => { + assert.throws(() => + geoAccessPolicyFactory.createPolicy( + [], + testResourceArn, + 'test-role', + stack, + ), + ); + }); + + void it('returns policy with get actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['get'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with autocomplete actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['autocomplete'], + testResourceArn, + 'guest', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-guest-access-policy', + PolicyDocument: { + Statement: [ + { + Action: 'geo-places:Autocomplete', + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with geocode actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['geocode'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-places:Geocode', 'geo-places:ReverseGeocode'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with search actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['search'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with create actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['create'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: 'geo:CreateGeofenceCollection', + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with read actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with update actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['update'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with delete actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['delete'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo:BatchDeleteGeofence', 'geo:DeleteGeofenceCollection'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('returns policy with list actions', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['list'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo:ListGeofences', 'geo:ListGeofenceCollections'], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('handles multiple actions in single policy', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read', 'create', 'update'], + testResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:CreateGeofenceCollection', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('creates policy with custom role token', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read'], + testResourceArn, + 'custom-role-token', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-custom-role-token-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); + + void it('handles different resource ARNs', () => { + const mapResourceArn = 'arn:aws:geo:us-east-1:123456789012:map/test-map'; + const policy = geoAccessPolicyFactory.createPolicy( + ['get'], + mapResourceArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: ['geo-maps:GetStaticMap', 'geo-maps:GetTile'], + Resource: mapResourceArn, + }, + ], + }, + }); + }); + + void it('creates policy with place index resource for search actions', () => { + const placeIndexArn = + 'arn:aws:geo:us-east-1:123456789012:place-index/test-place-index'; + const policy = geoAccessPolicyFactory.createPolicy( + ['search', 'geocode'], + placeIndexArn, + 'authenticated', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-authenticated-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo-places:GetPlace', + 'geo-places:SearchNearby', + 'geo-places:SearchText', + 'geo-places:Suggest', + 'geo-places:Geocode', + 'geo-places:ReverseGeocode', + ], + Resource: placeIndexArn, + }, + ], + }, + }); + }); + + void it('handles group role tokens', () => { + const policy = geoAccessPolicyFactory.createPolicy( + ['read', 'update'], + testResourceArn, + 'group-admin', + stack, + ); + + // we have to attach the policy to a role, otherwise CDK erases the policy from the stack + policy.attachToRole( + new Role(stack, 'testRole', { assumedBy: new AccountPrincipal('1234') }), + ); + + assert.ok(policy instanceof Policy); + + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'geo-group-admin-access-policy', + PolicyDocument: { + Statement: [ + { + Action: [ + 'geo:DescribeGeofenceCollection', + 'geo:BatchEvaluateGeofences', + 'geo:ForecastGeofenceEvents', + 'geo:GetGeofence', + 'geo:BatchPutGeofence', + 'geo:PutGeofence', + 'geo:UpdateGeofenceCollection', + ], + Resource: testResourceArn, + }, + ], + }, + }); + }); +}); diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 5cc305395b1..a0efbc69b9c 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -29,10 +29,11 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - return new Policy(stack, `geo-access-policy`, { - policyName: `geo-${roleToken}-access-policy`, + const policyIDName: string = `geo-${roleToken}-access-policy`; + return new Policy(stack, policyIDName, { + policyName: policyIDName, statements: [policyStatement], - }); // returns policy with policy statement of all actions + }); }; } diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts new file mode 100644 index 00000000000..bd1e0dec9d1 --- /dev/null +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -0,0 +1,233 @@ +import { afterEach, beforeEach, describe, it, mock } from 'node:test'; +import assert from 'node:assert'; +import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; +import { AmplifyCollection } from './collection_construct.js'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyPlace } from './place_resource.js'; +import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; +import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import { App, Stack } from 'aws-cdk-lib'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; + +void describe('AmplifyGeoOutputsAspect', () => { + let app: App; + let stack: Stack; + let outputStorageStrategy: BackendOutputStorageStrategy; + let aspect: AmplifyGeoOutputsAspect; + + const addBackendOutputEntryMock = mock.fn(); + const appendToBackendOutputListMock = mock.fn(); + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + + outputStorageStrategy = { + addBackendOutputEntry: addBackendOutputEntryMock, + appendToBackendOutputList: appendToBackendOutputListMock, + }; + }); + + afterEach(() => { + addBackendOutputEntryMock.mock.resetCalls(); + appendToBackendOutputListMock.mock.resetCalls(); + }); + + void describe('visit', () => { + void it('output storage invoked with AmplifyMap node', () => { + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + }); + + void it('only backend output entry invoked with AmplifyMap node', () => { + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + }); + + void it('output storage invoked with AmplifyPlace node', () => { + const placeNode = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(placeNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + }); + + void it('only backend output entry invoked with AmplifyPlace node', () => { + const placeNode = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceResourceName', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(placeNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + }); + + void it('both backend output entry and append list invoked with AmplifyCollection node', () => { + const collectionNode = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: {}, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(collectionNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); + }); + + void it('output entry called once with multiple collections created', () => { + new AmplifyCollection(stack, 'testCollection_1', { + name: 'testCollection1', + collectionProps: {}, + isDefault: true, + }); // set as default collection + new AmplifyCollection(stack, 'testCollection_2', { + name: 'testCollection2', + collectionProps: {}, + }); + const mapNode = new AmplifyMap(stack, 'testMap', { + name: 'testMapResourceName', + }); + + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(mapNode); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + }); + }); + + void describe('resource validation for outputs', () => { + void it('throws if no collection set to default', () => { + const noDuplicateStack = new Stack(app, 'noDuplicateStack'); + const newNode = new AmplifyCollection( + noDuplicateStack, + 'testCollection2', + { name: 'testCollection_2', collectionProps: {} }, + ); + new AmplifyCollection(noDuplicateStack, 'testCollection3', { + name: 'testCollection_3', + collectionProps: {}, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + aspect.visit(newNode); + }, + new AmplifyUserError('NoDefaultCollectionError', { + message: + 'No instances of geofence collections have been marked as default.', + resolution: + 'Add `isDefault: true` to one of the `defineCollection` calls.', + }), + ); + }); + + void it('throws if multiple default collections', () => { + const node = new AmplifyCollection(stack, 'testCollection', { + name: 'defaultCollection', + collectionProps: {}, + isDefault: true, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + new AmplifyCollection(stack, 'defaultCollection', { + name: 'default_collection', + collectionProps: {}, + isDefault: true, + }); + aspect.visit(node); + }, + new AmplifyUserError('MultipleDefaultCollectionError', { + message: + 'Multiple instances of geofence collections have been marked as default.', + resolution: + 'Remove `isDefault: true` from all but one `defineCollection` call.', + }), + ); + }); + }); + + void describe('output validation', () => { + void it('output without collection', () => { + const node = new AmplifyMap(stack, 'mapResource', { + name: 'testMapResource', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(node); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + + assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[0], + 'AWS::Amplify::Geo', + ); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.aws_region, + Stack.of(node).region, + ); + assert.deepStrictEqual( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofence_collections, + undefined, + ); + }); + + void it('output with multiple collections and all resources', () => { + const node = new AmplifyMap(stack, 'mapResource', { + name: 'testMapResource', + }); + new AmplifyPlace(stack, 'placeResource', { name: 'testPlaceIndex' }); + new AmplifyCollection(stack, 'defaultCollection', { + name: 'default_collection', + collectionProps: { geofenceCollectionName: 'newCollection' }, + isDefault: true, + }); + new AmplifyCollection(stack, 'testCollection', { + name: 'default_collection', + collectionProps: {}, + }); + + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + aspect.visit(node); + + assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + + assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); + assert.equal( + addBackendOutputEntryMock.mock.calls[0].arguments[0], + 'AWS::Amplify::Geo', + ); + + const parsedEntry1 = JSON.parse( + appendToBackendOutputListMock.mock.calls[0].arguments[1].payload + .geofence_collections, + ); + assert.ok(parsedEntry1.default.includes('TOKEN')); + assert.ok(parsedEntry1.items.includes('TOKEN')); + + const parsedEntry2 = JSON.parse( + appendToBackendOutputListMock.mock.calls[0].arguments[1].payload + .geofence_collections, + ); + assert.ok(parsedEntry2.items.includes('TOKEN')); + }); + }); +}); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 4a4275e8403..94d24f247c2 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -34,9 +34,9 @@ export class AmplifyGeoOutputsAspect implements IAspect { */ public visit(node: IConstruct): void { if ( - !(node instanceof AmplifyMap) || - !(node instanceof AmplifyPlace) || - !(node instanceof AmplifyCollection) || + !(node instanceof AmplifyMap) && + !(node instanceof AmplifyPlace) && + !(node instanceof AmplifyCollection) && this.isGeoOutputProcessed ) { return; @@ -69,11 +69,11 @@ export class AmplifyGeoOutputsAspect implements IAspect { } } - private findDefaultCollectionName = ( + private validateDefaultCollection = ( nodes: AmplifyCollection[], currentNode: AmplifyCollection, - ): string | undefined => { - const geoCount = nodes.length; + ) => { + const collectionCount = nodes.length; let defaultCollectionName: string | undefined = undefined; @@ -94,11 +94,11 @@ export class AmplifyGeoOutputsAspect implements IAspect { } }); - if (geoCount === 1 && !defaultCollectionName) { + if (collectionCount === 1 && !defaultCollectionName) { // if no defaults and only one construct, instance assumed to be default defaultCollectionName = currentNode.resources.collection?.geofenceCollectionName; - } else if (geoCount > 1 && !defaultCollectionName) { + } else if (collectionCount > 1 && !defaultCollectionName) { // if multiple constructs with default collection, throw error throw new AmplifyUserError('NoDefaultCollectionError', { message: @@ -115,29 +115,34 @@ export class AmplifyGeoOutputsAspect implements IAspect { * Function responsible for add all collection outputs (with defaults) * @param collections - all construct instances of AmplifyGeo * @param outputStorageStrategy - backend output schema of type GeoOutput - * @param region - + * @param region - region of geo resources */ private addBackendOutput( collections: AmplifyCollection[], outputStorageStrategy: BackendOutputStorageStrategy, region: string, ) { - const defaultCollectionName: string | undefined = - this.findDefaultCollectionName(collections, collections[0]); + this.validateDefaultCollection(collections, collections[0]); outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { version: '1', payload: { aws_region: region, - geofence_collections: JSON.stringify({ - default: defaultCollectionName, - items: defaultCollectionName, - }), }, }); collections.forEach((collection) => { - if (!collection.isDefault) { + if (collection.isDefault) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofence_collections: JSON.stringify({ + default: collection.resources.collection.geofenceCollectionName, + items: collection.resources.collection.geofenceCollectionName, + }), + }, + }); + } else { outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { version: '1', payload: { diff --git a/packages/backend-geo/src/map_factory.test.ts b/packages/backend-geo/src/map_factory.test.ts new file mode 100644 index 00000000000..de92746c905 --- /dev/null +++ b/packages/backend-geo/src/map_factory.test.ts @@ -0,0 +1,172 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { AmplifyMapFactory, defineMap } from './map_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { MapResources } from './types.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { AmplifyMap } from './map_resource.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let mapFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyMapFactory', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyMapFactory.mapCount = 0; + + mapFactory = defineMap({ + name: 'testMap', + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void describe('singleton validation', () => { + beforeEach(() => { + AmplifyMapFactory.mapCount = 0; + + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = mapFactory.getInstance(getInstanceProps); + const instance2 = mapFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('allows single map creation', () => { + const mapFactory = defineMap({ + name: 'singleMap', + }); + + const mapConstruct = mapFactory.getInstance( + getInstanceProps, + ) as AmplifyMap; + assert.equal(mapConstruct.name, 'singleMap'); + }); + + void it('prevents multiple map factory creation', () => { + defineMap({ + name: 'firstMap', + }); + + assert.throws( + () => + defineMap({ + name: 'secondMap', + }), + new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `defineMap` calls not permitted within an Amplify backend', + resolution: 'Maintain one `defineMap` call', + }), + ); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const mapInvalidFactory = defineMap({ + name: '|$%#86430resource', + }); + assert.throws( + () => + mapInvalidFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + }); + + void it('adds construct to stack', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + // Maps don't create CloudFormation resources, but the construct + assert.ok(mapConstruct.stack); + assert.equal(mapConstruct.name, 'testMap'); + }); + + void it('creates map with proper name and properties', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + assert.equal(mapConstruct.name, 'testMap'); + assert.ok(mapConstruct.resources); + }); + + void it('verifies stack property exists and is equal to map stack', () => { + const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; + + assert.equal(mapConstruct.stack, Stack.of(mapConstruct)); + }); +}); diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 3d374d22cd9..17bd75ff587 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -78,6 +78,15 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { + const amplifyMap = new AmplifyMap(scope, this.props.name, { + ...this.props, + outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, + }); + + if (!this.props.access) { + return amplifyMap; + } + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( this.props.access, this.getInstanceProps, @@ -85,11 +94,6 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { [], ); - const amplifyMap = new AmplifyMap(scope, this.props.name, { - ...this.props, - outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, - }); - Tags.of(amplifyMap).add(TagName.FRIENDLY_NAME, this.props.name); amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts new file mode 100644 index 00000000000..a1f7be3aebd --- /dev/null +++ b/packages/backend-geo/src/map_resource.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyMap } from './map_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyMap', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a map resource', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map); + assert.equal(map.name, 'testMapName'); + assert.equal(map.id, 'testMap'); + }); + + void it('sets name property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'myTestMap', + }); + + assert.equal(map.name, 'myTestMap'); + }); + + void it('exposes map resources correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map.resources); + assert.ok(Array.isArray(map.resources.policies)); + assert.equal(typeof map.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-maps')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.equal(map.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const map = new AmplifyMap(stackWithRegion, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:*::provider\/default$/, + ), + ); + }); + + void it('handles multiple map resources', () => { + const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; + + mapNames.forEach((mapName, index) => { + const map = new AmplifyMap(stack, `testMap${index}`, { + name: mapName, + }); + + assert.equal(map.name, mapName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates map with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const map = new AmplifyMap(stackWithRegion, 'minimalMap', { + name: 'minimal', + }); + + assert.equal(map.name, 'minimal'); + assert.equal(map.id, 'minimalMap'); + assert.ok(map.resources); + assert.equal(map.resources.region, 'us-west-2'); + assert.ok(map.stack); + }); + }); +}); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 7fa416e2fdc..0f6b0933ccb 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -19,15 +19,16 @@ export class AmplifyMap */ constructor(scope: Construct, id: string, props: AmplifyMapProps) { super(scope, id); - this.name = props.name; + this.id = id; + + this.resources = { + region: this.stack.region, + policies: [], + }; } getResourceArn = (): string => { return `arn:${Aws.PARTITION}:geo-maps:${this.stack.region}::provider/default`; }; - - getResourceName = (): string => { - return this.name; - }; } diff --git a/packages/backend-geo/src/place_factory.test.ts b/packages/backend-geo/src/place_factory.test.ts new file mode 100644 index 00000000000..dc81c67df90 --- /dev/null +++ b/packages/backend-geo/src/place_factory.test.ts @@ -0,0 +1,179 @@ +import { beforeEach, describe, it, mock } from 'node:test'; +import { AmplifyPlaceFactory, definePlace } from './place_factory.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; +import { + BackendOutputEntry, + BackendOutputStorageStrategy, + ConstructContainer, + ConstructFactory, + ConstructFactoryGetInstanceProps, + ResourceNameValidator, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { + ConstructContainerStub, + ResourceNameValidatorStub, + StackResolverStub, +} from '@aws-amplify/backend-platform-test-stubs'; +import { PlaceResources } from './types.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { AmplifyPlace } from './place_resource.js'; + +const createStackAndSetContext = (): Stack => { + const app = new App(); + app.node.setContext('amplify-backend-name', 'testEnvName'); + app.node.setContext('amplify-backend-namespace', 'testBackendId'); + app.node.setContext('amplify-backend-type', 'branch'); + const stack = new Stack(app); + return stack; +}; + +let placeFactory: ConstructFactory>; +let constructContainer: ConstructContainer; +let outputStorageStrategy: BackendOutputStorageStrategy; +let resourceNameValidator: ResourceNameValidator; + +let getInstanceProps: ConstructFactoryGetInstanceProps; + +void describe('AmplifyPlaceFactory', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyPlaceFactory.placeCount = 0; + + placeFactory = definePlace({ + name: 'testPlace', + }); + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void describe('singleton validation', () => { + beforeEach(() => { + // Reset the static counter before each test + AmplifyPlaceFactory.placeCount = 0; + + const stack = createStackAndSetContext(); + + constructContainer = new ConstructContainerStub( + new StackResolverStub(stack), + ); + + outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack, + ); + + resourceNameValidator = new ResourceNameValidatorStub(); + + getInstanceProps = { + constructContainer, + outputStorageStrategy, + resourceNameValidator, + }; + }); + + void it('returns singleton instance', () => { + const instance1 = placeFactory.getInstance(getInstanceProps); + const instance2 = placeFactory.getInstance(getInstanceProps); + + assert.strictEqual(instance1, instance2); + }); + + void it('allows single place creation', () => { + const placeFactory = definePlace({ + name: 'singlePlace', + }); + + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + assert.equal(placeConstruct.name, 'singlePlace'); + }); + + void it('prevents multiple place factory creation', () => { + definePlace({ + name: 'firstPlace', + }); + + assert.throws( + () => + definePlace({ + name: 'secondPlace', + }), + new AmplifyUserError('MultipleSingletonResourcesError', { + message: + 'Multiple `definePlace` calls not permitted within an Amplify backend', + resolution: 'Maintain one `definePlace` call', + }), + ); + }); + + void it('throws on invalid name', () => { + mock + .method(resourceNameValidator, 'validate') + .mock.mockImplementationOnce(() => { + throw new Error( + 'Resource name verification failed, please set an appropriate resource name.', + ); + }); + + const placeInvalidFactory = definePlace({ + name: '|$%#86430resource', + }); + assert.throws( + () => + placeInvalidFactory.getInstance({ + ...getInstanceProps, + resourceNameValidator, + }), + { + message: + 'Resource name verification failed, please set an appropriate resource name.', + }, + ); + }); + }); + + void it('adds construct to stack', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + // Places don't create CloudFormation resources, but the construct + assert.ok(placeConstruct.stack); + assert.equal(placeConstruct.name, 'testPlace'); + }); + + void it('creates place with proper name and properties', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + assert.equal(placeConstruct.name, 'testPlace'); + assert.ok(placeConstruct.resources); + }); + + void it('verifies stack property exists and is equal to place stack', () => { + const placeConstruct = placeFactory.getInstance( + getInstanceProps, + ) as AmplifyPlace; + + assert.equal(placeConstruct.stack, Stack.of(placeConstruct)); + }); +}); diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 9b5bb3ea0b4..01229555445 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -20,7 +20,7 @@ import { AmplifyGeoOutputsAspect } from './geo_outputs_aspect.js'; export class AmplifyPlaceFactory implements ConstructFactory> { - static mapCount: number = 0; + static placeCount: number = 0; private geoGenerator: ConstructContainerEntryGenerator; private geoAccessOrchestratorFactory: GeoAccessOrchestratorFactory = @@ -31,14 +31,14 @@ export class AmplifyPlaceFactory * @param props - place resource properties */ constructor(private readonly props: AmplifyPlaceFactoryProps) { - if (AmplifyPlaceFactory.mapCount > 0) { + if (AmplifyPlaceFactory.placeCount > 0) { throw new AmplifyUserError('MultipleSingletonResourcesError', { message: 'Multiple `definePlace` calls not permitted within an Amplify backend', resolution: 'Maintain one `definePlace` call', }); } - AmplifyPlaceFactory.mapCount++; + AmplifyPlaceFactory.placeCount++; } getInstance = ( @@ -81,13 +81,6 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { generateContainerEntry = ({ scope, }: GenerateContainerEntryProps): ResourceProvider => { - const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( - this.props.access, - this.getInstanceProps, - Stack.of(scope), - [], - ); - const amplifyPlace = new AmplifyPlace(scope, this.props.name, { ...this.props, outputStorageStrategy: this.getInstanceProps.outputStorageStrategy, @@ -95,10 +88,21 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { Tags.of(amplifyPlace).add(TagName.FRIENDLY_NAME, this.props.name); + if (!this.props.access) { + return amplifyPlace; + } + + const geoAccessOrchestrator = this.geoAccessOrchestratorFactory.getInstance( + this.props.access, + this.getInstanceProps, + Stack.of(scope), + [], + ); + amplifyPlace.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyPlace.getResourceArn(), - 'map', + 'place', ); const geoAspects = Aspects.of(Stack.of(amplifyPlace)); diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts new file mode 100644 index 00000000000..633c506e390 --- /dev/null +++ b/packages/backend-geo/src/place_resource.test.ts @@ -0,0 +1,111 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyPlace } from './place_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyPlace', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a place resource', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.ok(place); + assert.equal(place.name, 'testPlaceName'); + assert.equal(place.id, 'testPlace'); + }); + + void it('sets name property correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'myTestPlace', + }); + + assert.equal(place.name, 'myTestPlace'); + }); + + void it('exposes place resources correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.ok(place.resources); + assert.ok(Array.isArray(place.resources.policies)); + assert.equal(typeof place.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + const arn = place.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-places')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const place = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceName', + }); + + assert.equal(place.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const place = new AmplifyPlace(stackWithRegion, 'testPlace', { + name: 'testPlaceName', + }); + + const arn = place.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:*::provider\/default$/, + ), + ); + }); + + void it('handles multiple place resources', () => { + const placeNames = [ + 'simple-place', + 'complex_place_name', + 'PlaceWithCamelCase', + ]; + + placeNames.forEach((placeName, index) => { + const place = new AmplifyPlace(stack, `testPlace${index}`, { + name: placeName, + }); + + assert.equal(place.name, placeName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates place with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const place = new AmplifyPlace(stackWithRegion, 'minimalPlace', { + name: 'minimal', + }); + + assert.equal(place.name, 'minimal'); + assert.equal(place.id, 'minimalPlace'); + assert.ok(place.resources); + assert.equal(place.resources.region, 'us-west-2'); + assert.ok(place.stack); + }); + }); +}); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index 7dcd13dca65..f928cc304cd 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -21,6 +21,12 @@ export class AmplifyPlace super(scope, id); this.name = props.name; + this.id = id; + + this.resources = { + region: this.stack.region, + policies: [], + }; } getResourceArn = (): string => { diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 34decc54b0f..736383b539e 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -30,7 +30,7 @@ export type AmplifyMapFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; /** @@ -49,7 +49,7 @@ export type AmplifyPlaceFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; /** @@ -68,23 +68,23 @@ export type AmplifyCollectionFactoryProps = Omit< * ) * }) */ - access: GeoAccessGenerator; + access?: GeoAccessGenerator; }; -export type AmplifyMapProps = { - name: string; - outputStorageStrategy?: BackendOutputStorageStrategy; -}; +export type AmplifyMapProps = Omit< + AmplifyCollectionProps, + 'collectionProps' | 'isDefault' +>; -export type AmplifyPlaceProps = { - name: string; - outputStorageStrategy?: BackendOutputStorageStrategy; -}; +export type AmplifyPlaceProps = Omit< + AmplifyCollectionProps, + 'collectionProps' | 'isDefault' +>; export type AmplifyCollectionProps = { name: string; collectionProps: GeofenceCollectionProps; - isDefault: boolean; + isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; @@ -94,6 +94,7 @@ export type AmplifyCollectionProps = { */ export type MapResources = { policies: Policy[]; + region: string; }; /** @@ -102,6 +103,7 @@ export type MapResources = { */ export type PlaceResources = { policies: Policy[]; + region: string; }; /** @@ -111,8 +113,8 @@ export type PlaceResources = { * @param cfnResources - cloudformation resources exposed from the abstracted collection provisioned from collection */ export type CollectionResources = { - collection: GeofenceCollection; policies: Policy[]; + collection: GeofenceCollection; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -152,3 +154,5 @@ export const resourceActionRecord: Record = { place: ['autocomplete', 'geocode', 'search'], collection: ['create', 'read', 'update', 'delete', 'list'], }; + +export type GeoResourceType = 'map' | 'place' | 'collection'; From ae95b0af27bae16cbf3adc06959965d3fa8939ad Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:29:44 -0700 Subject: [PATCH 38/93] updating tsconfig --- packages/backend-geo/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-geo/tsconfig.json b/packages/backend-geo/tsconfig.json index 6dd4b4b566a..1a3b4eaa39e 100644 --- a/packages/backend-geo/tsconfig.json +++ b/packages/backend-geo/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib" }, "references": [ { "path": "../backend-output-schemas" }, + { "path": "../backend-output-storage" }, { "path": "../platform-core" } ] } From e0ec17477218d55b5110d73821da55592522737c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:37:14 -0700 Subject: [PATCH 39/93] fixing some expressions --- packages/backend-geo/src/geo_access_orchestrator.test.ts | 4 +--- packages/backend-geo/src/map_resource.test.ts | 2 +- packages/backend-geo/src/place_resource.test.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index c9402da07d6..de2148293a2 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -12,9 +12,7 @@ void describe('GeoAccessOrchestrator', () => { void describe('orchestrateGeoAccess', () => { let stack: Stack; - const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = [ - { name: 'TEST_GEO_RESOURCE_NAME', path: 'test/ssm/path/to/geo/resource' }, - ]; + const ssmEnvironmentEntriesStub: SsmEnvironmentEntry[] = []; const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts index a1f7be3aebd..713a69f282a 100644 --- a/packages/backend-geo/src/map_resource.test.ts +++ b/packages/backend-geo/src/map_resource.test.ts @@ -71,7 +71,7 @@ void describe('AmplifyMap', () => { const arn = map.getResourceArn(); assert.ok( arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:*::provider\/default$/, + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, ), ); }); diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts index 633c506e390..bc135096b18 100644 --- a/packages/backend-geo/src/place_resource.test.ts +++ b/packages/backend-geo/src/place_resource.test.ts @@ -71,7 +71,7 @@ void describe('AmplifyPlace', () => { const arn = place.getResourceArn(); assert.ok( arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:*::provider\/default$/, + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-places:[^:]*::provider\/default$/, ), ); }); From 7166ac05d35c5de3351dede88652b87dea008081 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:42:27 -0700 Subject: [PATCH 40/93] updating API from recent commits --- packages/backend-geo/API.md | 82 +++++++++++++++----------- packages/backend-geo/src/index.ts | 2 + packages/backend-output-schemas/API.md | 60 ++++++++----------- 3 files changed, 74 insertions(+), 70 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index ad142371e83..947948b9c3f 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -12,35 +12,57 @@ import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; -import { IRole } from 'aws-cdk-lib/aws-iam'; +import { Policy } from 'aws-cdk-lib/aws-iam'; +import { ResourceAccessAcceptor } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; import { StackProvider } from '@aws-amplify/plugin-types'; -// @public (undocumented) -export type AmplifyGeoFactoryProps = Omit & { - region: string; - access: GeoAccessGenerator; - resourceIdentifier?: GeoResourceType; +// @public +export type AmplifyCollectionFactoryProps = Omit & { + access?: GeoAccessGenerator; }; // @public (undocumented) -export type AmplifyGeoProps = { +export type AmplifyCollectionProps = { name: string; - collectionProps?: GeofenceCollectionProps; + collectionProps: GeofenceCollectionProps; + isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; +// @public +export type AmplifyMapFactoryProps = Omit & { + access?: GeoAccessGenerator; +}; + // @public (undocumented) -export type CollectionAction = 'create' | 'read' | 'update' | 'delete' | 'list'; +export type AmplifyMapProps = Omit; // @public -export const defineCollection: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export type AmplifyPlaceFactoryProps = Omit & { + access?: GeoAccessGenerator; +}; + +// @public (undocumented) +export type AmplifyPlaceProps = Omit; // @public -export const defineMap: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export type CollectionResources = { + policies: Policy[]; + collection: GeofenceCollection; + cfnResources: { + cfnCollection: CfnGeofenceCollection; + }; +}; // @public -export const definePlace: (props: AmplifyGeoFactoryProps) => ConstructFactory & StackProvider>; +export const defineCollection: (props: AmplifyCollectionFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const defineMap: (props: AmplifyMapFactoryProps) => ConstructFactory & StackProvider>; + +// @public +export const definePlace: (props: AmplifyPlaceFactoryProps) => ConstructFactory & StackProvider>; // @public (undocumented) export type GeoAccessBuilder = { @@ -51,8 +73,8 @@ export type GeoAccessBuilder = { // @public (undocumented) export type GeoAccessDefinition = { - userRoles: ((getInstanceProps: ConstructFactoryGetInstanceProps) => IRole)[]; - actions: GeoAction[]; + getAccessAcceptors: ((getInstanceProps: ConstructFactoryGetInstanceProps) => ResourceAccessAcceptor)[]; + actions: string[]; uniqueDefinitionValidators: { uniqueRoleToken: string; validationErrorOptions: AmplifyUserErrorOptions; @@ -62,36 +84,28 @@ export type GeoAccessDefinition = { // @public (undocumented) export type GeoAccessGenerator = (allow: GeoAccessBuilder) => GeoAccessDefinition[]; -// @public (undocumented) -export type GeoAction = MapAction | IndexAction | CollectionAction; - // @public (undocumented) export type GeoActionBuilder = { - to: (actions: GeoAction[]) => GeoAccessDefinition; + to: (actions: string[]) => GeoAccessDefinition; }; // @public (undocumented) -export const geoCfnResourceTypes: string[]; - -// @public (undocumented) -export const geoManagedResourceTypes: string[]; +export type GeoResourceType = 'map' | 'place' | 'collection'; -// @public (undocumented) -export type GeoResources = { - collection: GeofenceCollection; - cfnResources: { - cfnCollection: CfnGeofenceCollection; - }; +// @public +export type MapResources = { + policies: Policy[]; + region: string; }; -// @public (undocumented) -export type GeoResourceType = 'map' | 'place' | 'collection'; - -// @public (undocumented) -export type IndexAction = 'autocomplete' | 'geocode' | 'search'; +// @public +export type PlaceResources = { + policies: Policy[]; + region: string; +}; // @public (undocumented) -export type MapAction = 'get'; +export const resourceActionRecord: Record; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-geo/src/index.ts b/packages/backend-geo/src/index.ts index 9237d106c6d..bfc07f0e61a 100644 --- a/packages/backend-geo/src/index.ts +++ b/packages/backend-geo/src/index.ts @@ -1,2 +1,4 @@ export { defineCollection } from './collection_factory.js'; +export { defineMap } from './map_factory.js'; +export { definePlace } from './place_factory.js'; export * from './types.js'; diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 706b7ab13dd..705af068c4e 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -380,31 +380,26 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo": z.ZodOptional; payload: z.ZodObject<{ - defaultCollection: z.ZodString; - geoRegion: z.ZodString; - collections: z.ZodString; + aws_region: z.ZodString; + geofence_collections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }>]>>; }, "strip", z.ZodTypeAny, { @@ -482,9 +477,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; } | undefined; }, { @@ -562,9 +556,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; } | undefined; }>; @@ -756,31 +749,26 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ - defaultCollection: z.ZodString; - geoRegion: z.ZodString; - collections: z.ZodString; + aws_region: z.ZodString; + geofence_collections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }, { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }, { version: "1"; payload: { - defaultCollection: string; - geoRegion: string; - collections: string; + aws_region: string; + geofence_collections?: string | undefined; }; }>]>; From 7b69fccf8d5bfc5874b58500ecf09f747ec0675b Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:46:52 -0700 Subject: [PATCH 41/93] removing all previous changesets --- .changeset/README.md | 8 -------- .changeset/config.json | 11 ----------- .changeset/empty-trains-cut.md | 5 ----- .changeset/warm-garlics-flow.md | 5 ----- 4 files changed, 29 deletions(-) delete mode 100644 .changeset/README.md delete mode 100644 .changeset/config.json delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6a67..00000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 6d2119a4592..00000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. From 8aa509304f0d328fefdb8cdf0401035460507269 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:49:21 -0700 Subject: [PATCH 42/93] Revert "removing all previous changesets" This reverts commit e2af23b38347f48f992ce361193a142bb1928f8e. --- .changeset/README.md | 8 ++++++++ .changeset/config.json | 11 +++++++++++ .changeset/empty-trains-cut.md | 5 +++++ .changeset/warm-garlics-flow.md | 5 +++++ 4 files changed, 29 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/empty-trains-cut.md create mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..6d2119a4592 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. From 37c0c0ffaabc48b47d4f82b1949a89e83b3d0fdf Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:49:59 -0700 Subject: [PATCH 43/93] removing changeset files instead of directory --- .changeset/empty-trains-cut.md | 5 - .changeset/warm-garlics-flow.md | 5 - packages/backend-geo/src/map_resource.test.ts | 107 ------------------ 3 files changed, 117 deletions(-) delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md delete mode 100644 packages/backend-geo/src/map_resource.test.ts diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts deleted file mode 100644 index 713a69f282a..00000000000 --- a/packages/backend-geo/src/map_resource.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { beforeEach, describe, it } from 'node:test'; -import { AmplifyMap } from './map_resource.js'; -import { App, Stack } from 'aws-cdk-lib'; -import assert from 'node:assert'; - -void describe('AmplifyMap', () => { - let app: App; - let stack: Stack; - - beforeEach(() => { - app = new App(); - stack = new Stack(app); - }); - - void it('creates a map resource', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.ok(map); - assert.equal(map.name, 'testMapName'); - assert.equal(map.id, 'testMap'); - }); - - void it('sets name property correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'myTestMap', - }); - - assert.equal(map.name, 'myTestMap'); - }); - - void it('exposes map resources correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.ok(map.resources); - assert.ok(Array.isArray(map.resources.policies)); - assert.equal(typeof map.resources.region, 'string'); - }); - - void it('returns correct resource ARN', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - const arn = map.getResourceArn(); - assert.ok(arn.includes('arn:')); - assert.ok(arn.includes('geo-maps')); - assert.ok(arn.includes('provider/default')); - }); - - void it('sets stack property correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.equal(map.stack, stack); - }); - - void it('generates ARN with correct partition and region', () => { - const stackWithRegion = new Stack(app, 'TestStack', { - env: { region: 'us-west-2' }, - }); - - const map = new AmplifyMap(stackWithRegion, 'testMap', { - name: 'testMapName', - }); - - const arn = map.getResourceArn(); - assert.ok( - arn.match( - /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, - ), - ); - }); - - void it('handles multiple map resources', () => { - const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; - - mapNames.forEach((mapName, index) => { - const map = new AmplifyMap(stack, `testMap${index}`, { - name: mapName, - }); - - assert.equal(map.name, mapName); - }); - }); - - void describe('resource properties validation', () => { - void it('creates map with minimal required properties', () => { - const stackWithRegion = new Stack(app, 'TestStack', { - env: { region: 'us-west-2' }, - }); - const map = new AmplifyMap(stackWithRegion, 'minimalMap', { - name: 'minimal', - }); - - assert.equal(map.name, 'minimal'); - assert.equal(map.id, 'minimalMap'); - assert.ok(map.resources); - assert.equal(map.resources.region, 'us-west-2'); - assert.ok(map.stack); - }); - }); -}); From 22ddc4a06354c26b06af83791db6c0c768c38018 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:52:56 -0700 Subject: [PATCH 44/93] Revert "removing changeset files instead of directory" This reverts commit 93e024f7a2519a9a8218088cc19a6c310a217dcf. --- .changeset/empty-trains-cut.md | 5 + .changeset/warm-garlics-flow.md | 5 + packages/backend-geo/src/map_resource.test.ts | 107 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 .changeset/empty-trains-cut.md create mode 100644 .changeset/warm-garlics-flow.md create mode 100644 packages/backend-geo/src/map_resource.test.ts diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md new file mode 100644 index 00000000000..ea7bf507401 --- /dev/null +++ b/.changeset/empty-trains-cut.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-output-schemas': minor +--- + +Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md new file mode 100644 index 00000000000..c42275524e1 --- /dev/null +++ b/.changeset/warm-garlics-flow.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-geo': minor +--- + +Initial version of working construct and API. diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts new file mode 100644 index 00000000000..713a69f282a --- /dev/null +++ b/packages/backend-geo/src/map_resource.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, it } from 'node:test'; +import { AmplifyMap } from './map_resource.js'; +import { App, Stack } from 'aws-cdk-lib'; +import assert from 'node:assert'; + +void describe('AmplifyMap', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + void it('creates a map resource', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map); + assert.equal(map.name, 'testMapName'); + assert.equal(map.id, 'testMap'); + }); + + void it('sets name property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'myTestMap', + }); + + assert.equal(map.name, 'myTestMap'); + }); + + void it('exposes map resources correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.ok(map.resources); + assert.ok(Array.isArray(map.resources.policies)); + assert.equal(typeof map.resources.region, 'string'); + }); + + void it('returns correct resource ARN', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok(arn.includes('arn:')); + assert.ok(arn.includes('geo-maps')); + assert.ok(arn.includes('provider/default')); + }); + + void it('sets stack property correctly', () => { + const map = new AmplifyMap(stack, 'testMap', { + name: 'testMapName', + }); + + assert.equal(map.stack, stack); + }); + + void it('generates ARN with correct partition and region', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + + const map = new AmplifyMap(stackWithRegion, 'testMap', { + name: 'testMapName', + }); + + const arn = map.getResourceArn(); + assert.ok( + arn.match( + /^arn:\$\{Token\[AWS\.Partition\.[^\]]+\]\}:geo-maps:[^:]*::provider\/default$/, + ), + ); + }); + + void it('handles multiple map resources', () => { + const mapNames = ['simple-map', 'complex_map_name', 'MapWithCamelCase']; + + mapNames.forEach((mapName, index) => { + const map = new AmplifyMap(stack, `testMap${index}`, { + name: mapName, + }); + + assert.equal(map.name, mapName); + }); + }); + + void describe('resource properties validation', () => { + void it('creates map with minimal required properties', () => { + const stackWithRegion = new Stack(app, 'TestStack', { + env: { region: 'us-west-2' }, + }); + const map = new AmplifyMap(stackWithRegion, 'minimalMap', { + name: 'minimal', + }); + + assert.equal(map.name, 'minimal'); + assert.equal(map.id, 'minimalMap'); + assert.ok(map.resources); + assert.equal(map.resources.region, 'us-west-2'); + assert.ok(map.stack); + }); + }); +}); From 25fd347813d02fd085b77d679ef6164159e3d5b5 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 01:54:04 -0700 Subject: [PATCH 45/93] removing changeset files --- .changeset/empty-trains-cut.md | 5 ----- .changeset/warm-garlics-flow.md | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 .changeset/empty-trains-cut.md delete mode 100644 .changeset/warm-garlics-flow.md diff --git a/.changeset/empty-trains-cut.md b/.changeset/empty-trains-cut.md deleted file mode 100644 index ea7bf507401..00000000000 --- a/.changeset/empty-trains-cut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-output-schemas': minor ---- - -Adding output schemas for geo construct diff --git a/.changeset/warm-garlics-flow.md b/.changeset/warm-garlics-flow.md deleted file mode 100644 index c42275524e1..00000000000 --- a/.changeset/warm-garlics-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor ---- - -Initial version of working construct and API. From 5206ca11622bb581ea0ae0a6cd96b368e554cb2c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 02:00:03 -0700 Subject: [PATCH 46/93] adding changeset --- .changeset/puny-hornets-brush.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/puny-hornets-brush.md diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md new file mode 100644 index 00000000000..9486bd36484 --- /dev/null +++ b/.changeset/puny-hornets-brush.md @@ -0,0 +1,6 @@ +--- +'@aws-amplify/backend-geo': minor +'@aws-amplify/backend-output-schemas': patch +--- + +This changeset involves the introduction of a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. From 2ad70f7c854df5a49db1f1b5d275bc6dc9c1782c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 24 Jul 2025 09:39:03 -0700 Subject: [PATCH 47/93] clean up --- packages/backend-geo/src/geo_access_orchestrator.ts | 2 -- packages/backend-geo/src/geo_outputs_aspect.ts | 2 +- packages/backend-geo/src/map_factory.ts | 4 ---- packages/backend-geo/src/place_factory.ts | 4 ---- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 704cc8a646e..594040b213f 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -14,8 +14,6 @@ import { AmplifyUserError } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; import { Stack } from 'aws-cdk-lib'; -// this file is responsible for implementing the following: -// 1. access orchestrator for geo /** * Access Orchestrator for Amplify Geo * diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 94d24f247c2..c5e25acdd70 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -42,7 +42,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { return; } - this.isGeoOutputProcessed = true; // once this is visited, this no longer remains false + this.isGeoOutputProcessed = true; // once this is visited, shouldn't process geo outputs again const mapInstances = Stack.of(node).node.children.filter( (el) => el instanceof AmplifyMap, diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 17bd75ff587..ea5fc4105f7 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -44,18 +44,14 @@ export class AmplifyMapFactory getInstance = ( getInstanceProps: ConstructFactoryGetInstanceProps, ): AmplifyMap => { - // get construct factory instance properties const { constructContainer, resourceNameValidator } = getInstanceProps; - // validates the user-entered resource name (according to CDK naming regulations) resourceNameValidator?.validate(this.props.name); - // generates a singleton container entry for this construct factory if (!this.geoGenerator) { this.geoGenerator = new AmplifyMapGenerator(this.props, getInstanceProps); } - // this getOrCompute accesses the internal construct container cache return constructContainer.getOrCompute(this.geoGenerator) as AmplifyMap; }; } diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 01229555445..ac87796d543 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -44,13 +44,10 @@ export class AmplifyPlaceFactory getInstance = ( getInstanceProps: ConstructFactoryGetInstanceProps, ): AmplifyPlace => { - // get construct factory instance properties const { constructContainer, resourceNameValidator } = getInstanceProps; - // validates the user-entered resource name (according to CDK naming regulations) resourceNameValidator?.validate(this.props.name); - // generates a singleton container entry for this construct factory if (!this.geoGenerator) { this.geoGenerator = new AmplifyPlaceGenerator( this.props, @@ -58,7 +55,6 @@ export class AmplifyPlaceFactory ); } - // this getOrCompute accesses the internal construct container cache return constructContainer.getOrCompute(this.geoGenerator) as AmplifyPlace; }; } From e140488441535f75b7ca6ae512a206b956f960f5 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sat, 26 Jul 2025 13:45:25 -0700 Subject: [PATCH 48/93] geo client-output bug fixed --- .../backend-geo/src/collection_factory.ts | 6 +- .../src/geo_outputs_aspect.test.ts | 21 +---- .../backend-geo/src/geo_outputs_aspect.ts | 53 ++++++------- packages/backend-geo/src/map_factory.ts | 6 +- packages/backend-geo/src/place_factory.ts | 6 +- packages/backend-output-schemas/src/geo/v1.ts | 8 +- .../client_config_contributor_factory.ts | 5 ++ .../client_config_contributor_v1.test.ts | 55 +++++++++++++ .../client_config_contributor_v1.ts | 46 +++++++++++ .../unified_client_config_generator.test.ts | 78 +++++++++++++++++++ 10 files changed, 235 insertions(+), 49 deletions(-) diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index cb9e1cb6fc0..e770c55e9cd 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -96,7 +96,11 @@ export class AmplifyCollectionGenerator const geoAspects = Aspects.of(Stack.of(amplifyCollection)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyCollection; diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index bd1e0dec9d1..a512260ff80 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -106,7 +106,7 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); }); @@ -179,12 +179,12 @@ void describe('AmplifyGeoOutputsAspect', () => { 'AWS::Amplify::Geo', ); assert.equal( - addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.aws_region, + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.geoRegion, Stack.of(node).region, ); assert.deepStrictEqual( addBackendOutputEntryMock.mock.calls[0].arguments[1].payload - .geofence_collections, + .geofenceCollections, undefined, ); }); @@ -208,26 +208,13 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 2); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( addBackendOutputEntryMock.mock.calls[0].arguments[0], 'AWS::Amplify::Geo', ); - - const parsedEntry1 = JSON.parse( - appendToBackendOutputListMock.mock.calls[0].arguments[1].payload - .geofence_collections, - ); - assert.ok(parsedEntry1.default.includes('TOKEN')); - assert.ok(parsedEntry1.items.includes('TOKEN')); - - const parsedEntry2 = JSON.parse( - appendToBackendOutputListMock.mock.calls[0].arguments[1].payload - .geofence_collections, - ); - assert.ok(parsedEntry2.items.includes('TOKEN')); }); }); }); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index c5e25acdd70..b58d83eae0d 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -36,12 +36,13 @@ export class AmplifyGeoOutputsAspect implements IAspect { if ( !(node instanceof AmplifyMap) && !(node instanceof AmplifyPlace) && - !(node instanceof AmplifyCollection) && - this.isGeoOutputProcessed + !(node instanceof AmplifyCollection) ) { return; } + if (this.isGeoOutputProcessed) return; + this.isGeoOutputProcessed = true; // once this is visited, shouldn't process geo outputs again const mapInstances = Stack.of(node).node.children.filter( @@ -122,36 +123,36 @@ export class AmplifyGeoOutputsAspect implements IAspect { outputStorageStrategy: BackendOutputStorageStrategy, region: string, ) { - this.validateDefaultCollection(collections, collections[0]); + const defaultCollectionName = this.validateDefaultCollection( + collections, + collections[0], + ); + // Add the main geo output entry with aws_region (snake_case to match schema) outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { version: '1', payload: { - aws_region: region, + geoRegion: region, }, }); - collections.forEach((collection) => { - if (collection.isDefault) { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - geofence_collections: JSON.stringify({ - default: collection.resources.collection.geofenceCollectionName, - items: collection.resources.collection.geofenceCollectionName, - }), - }, - }); - } else { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - geofence_collections: JSON.stringify({ - items: collection.resources.collection.geofenceCollectionName, - }), - }, - }); - } - }); + // Collect all collection names for the items array + const collectionNames = collections.map( + (collection) => collection.resources.collection.geofenceCollectionName, + ); + + // Add geofence_collections as a single entry with all collections + if (collections.length > 0 && defaultCollectionName) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + geofenceCollections: JSON.stringify({ + // Changed from geofenceCollections to geofence_collections + default: defaultCollectionName, + items: collectionNames, // Array of all collection names + }), + }, + }); + } } } diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index ea5fc4105f7..3419cf8bb5f 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -99,7 +99,11 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { const geoAspects = Aspects.of(Stack.of(amplifyMap)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyMap; diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index ac87796d543..1620e915c87 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -103,7 +103,11 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { const geoAspects = Aspects.of(Stack.of(amplifyPlace)); if (!geoAspects.all.length) { - new AmplifyGeoOutputsAspect(this.getInstanceProps.outputStorageStrategy); + geoAspects.add( + new AmplifyGeoOutputsAspect( + this.getInstanceProps.outputStorageStrategy, + ), + ); } return amplifyPlace; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 4a569fb9343..9be0b349d14 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -2,13 +2,15 @@ import { z } from 'zod'; const collectionSchema = z.object({ default: z.string(), - items: z.string(z.array(z.string())).optional(), + items: z.array(z.string()), }); export const geoOutputSchema = z.object({ version: z.literal('1'), payload: z.object({ - aws_region: z.string(), - geofence_collections: z.string(collectionSchema).optional(), + geoRegion: z.string(), + maps: z.string().optional(), // JSON serialized string + searchIndices: z.string().optional(), // JSON serialized string + geofenceCollections: z.string(collectionSchema).optional(), // JSON serialized string }), }); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts index 6a0aba105f4..6aeb34dab86 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts @@ -4,6 +4,7 @@ import { AuthClientConfigContributor as Auth1_3, CustomClientConfigContributor as Custom1_1, DataClientConfigContributor as Data1_1, + GeoClientConfigContributor as Geo1, StorageClientConfigContributorV1 as Storage1, StorageClientConfigContributorV1_1 as Storage1_1, StorageClientConfigContributor as Storage1_2, @@ -39,6 +40,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_4]: [ new Auth1_3(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributor1_4(), new Custom1_1(), @@ -47,6 +49,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_3]: [ new Auth1_3(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributorV1_3(), new Custom1_1(), @@ -55,6 +58,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_2]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_2(), new VersionContributorV1_2(), new Custom1_1(), @@ -63,6 +67,7 @@ export class ClientConfigContributorFactory { [ClientConfigVersionOption.V1_1]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1(), new Storage1_1(), new VersionContributorV1_1(), new Custom1_1(), diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 7a934558721..706268e636b 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -3,6 +3,7 @@ import { AuthClientConfigContributor, CustomClientConfigContributor, DataClientConfigContributor, + GeoClientConfigContributor, StorageClientConfigContributor, VersionContributor, } from './client_config_contributor_v1.js'; @@ -15,6 +16,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, storageOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -586,6 +588,59 @@ void describe('data client config contributor v1', () => { }); }); +void describe('geo client config contributor v1', () => { + void it('empty outputs if no geo output provided', () => { + const contributor = new GeoClientConfigContributor(); + assert.deepStrictEqual( + contributor.contribute({ + [graphqlOutputKey]: { + version: '1', + payload: { + awsAppsyncApiEndpoint: 'testApiEndpoint', + awsAppsyncRegion: 'us-east-1', + awsAppsyncAuthenticationType: 'API_KEY', + awsAppsyncAdditionalAuthenticationTypes: 'API_KEY', + awsAppsyncConflictResolutionMode: undefined, + awsAppsyncApiKey: 'testApiKey', + awsAppsyncApiId: 'testApiId', + amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', + }, + }, + }), + {}, + ); + }); + + void it('returns correct config when geo collections exist', () => { + const contributor = new GeoClientConfigContributor(); + assert.deepStrictEqual( + contributor.contribute({ + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-west-2', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, + }), + { + geo: { + aws_region: 'us-west-2', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, + }, + ); + }); +}); + void describe('storage client config contributor v1', () => { void it('returns an empty object if output has no storage output', () => { const contributor = new StorageClientConfigContributor(); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index 72e5505dbb9..e1838d3833b 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -3,6 +3,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, storageOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -468,6 +469,51 @@ export class DataClientConfigContributor implements ClientConfigContributor { }; } +/** + * Transformer for Geo segment of ClientConfig (V1.1 or later) + */ +export class GeoClientConfigContributor implements ClientConfigContributor { + contribute = ({ + [geoOutputKey]: geoOutput, + }: UnifiedBackendOutput): Partial | Record => { + if (geoOutput === undefined) { + return {}; + } + + const config: Partial = {}; + + config.geo = { + aws_region: geoOutput.payload.geoRegion, + }; + + let geofenceCollectionsObj; + + if (geoOutput.payload.geofenceCollections) { + const firstParse = JSON.parse( + JSON.parse(geoOutput.payload.geofenceCollections), + ); + + if ( + firstParse && + typeof firstParse === 'object' && + !Array.isArray(firstParse) && + firstParse.default + ) { + geofenceCollectionsObj = firstParse; + } + + if (geofenceCollectionsObj && geofenceCollectionsObj.default) { + config.geo!.geofence_collections = { + default: geofenceCollectionsObj.default, + items: geofenceCollectionsObj.items || [], + }; + } + } + + return config; + }; +} + /** * Translator for the Storage portion of ClientConfig in V1.2 */ diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index e39e5301342..b2d984b49a2 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -5,6 +5,7 @@ import { UnifiedBackendOutput, authOutputKey, customOutputKey, + geoOutputKey, graphqlOutputKey, platformOutputKey, } from '@aws-amplify/backend-output-schemas'; @@ -79,6 +80,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -91,6 +104,7 @@ void describe('UnifiedClientConfigGenerator', () => { }, }, }; + const outputRetrieval = mock.fn(async () => stubOutput); const modelSchemaAdapter = new ModelIntrospectionSchemaAdapter( stubClientProvider, @@ -150,6 +164,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', @@ -213,6 +234,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -277,6 +310,13 @@ void describe('UnifiedClientConfigGenerator', () => { }, ], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, data: { url: 'testApiEndpoint', aws_region: 'us-east-1', @@ -334,6 +374,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -393,6 +445,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', @@ -443,6 +502,18 @@ void describe('UnifiedClientConfigGenerator', () => { amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', }, }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, [customOutputKey]: { version: '1', payload: { @@ -502,6 +573,13 @@ void describe('UnifiedClientConfigGenerator', () => { default_authorization_type: 'API_KEY', authorization_types: ['API_KEY'], }, + geo: { + aws_region: 'us-east-1', + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, custom: { output1: 'val1', output2: 'val2', From 7a3106c7b84b3920650c52392a79f17d17176856 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Sat, 26 Jul 2025 14:52:57 -0700 Subject: [PATCH 49/93] updating API and fixing policy name duplication bug --- .../backend-geo/src/collection_factory.ts | 1 + .../src/geo_access_orchestrator.test.ts | 21 +++++- .../src/geo_access_orchestrator.ts | 13 +++- .../src/geo_access_policy_factory.test.ts | 44 ++++++++---- .../src/geo_access_policy_factory.ts | 3 +- packages/backend-geo/src/map_factory.ts | 1 + packages/backend-geo/src/place_factory.ts | 1 + packages/backend-output-schemas/API.md | 72 ++++++++++++------- 8 files changed, 114 insertions(+), 42 deletions(-) diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index e770c55e9cd..0a5e8a154c0 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -92,6 +92,7 @@ export class AmplifyCollectionGenerator geoAccessOrchestrator.orchestrateGeoAccess( amplifyCollection.resources.collection.geofenceCollectionArn, 'collection', + amplifyCollection.name, ); const geoAspects = Aspects.of(Stack.of(amplifyCollection)); diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index de2148293a2..263a18bb956 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -17,6 +17,8 @@ void describe('GeoAccessOrchestrator', () => { const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + const testResourceName = 'testResource'; + beforeEach(() => { stack = createStackAndSetContext(); }); @@ -55,6 +57,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), new AmplifyUserError('ActionNotFoundError', { message: @@ -106,6 +109,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), new AmplifyUserError('InvalidGeoAccessDefinitionError', { message: 'Duplicate authenticated access definition', @@ -147,6 +151,7 @@ void describe('GeoAccessOrchestrator', () => { const policies = geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -217,7 +222,11 @@ void describe('GeoAccessOrchestrator', () => { ssmEnvironmentEntriesStub, ); - geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + testResourceName, + ); assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); @@ -310,7 +319,11 @@ void describe('GeoAccessOrchestrator', () => { ssmEnvironmentEntriesStub, ); - geoAccessOrchestrator.orchestrateGeoAccess(testResourceArn, 'collection'); + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'collection', + testResourceName, + ); assert.equal(acceptResourceAccessMock1.mock.callCount(), 1); assert.equal(acceptResourceAccessMock2.mock.callCount(), 1); @@ -386,6 +399,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo-maps:us-east-1::provider/default', 'map', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -436,6 +450,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo-places:us-east-1::provider/default', 'place', + testResourceName, ); assert.equal(acceptResourceAccessMock.mock.callCount(), 1); assert.deepStrictEqual( @@ -494,6 +509,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( 'arn:aws:geo:us-east-1:123456789012:map/test-map', 'map', + testResourceName, ), new AmplifyUserError('ActionNotFoundError', { message: @@ -538,6 +554,7 @@ void describe('GeoAccessOrchestrator', () => { geoAccessOrchestrator.orchestrateGeoAccess( testResourceArn, 'collection', + testResourceName, ), { message: 'At least one permission must be specified' }, ); diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 594040b213f..4e643c76322 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -50,14 +50,17 @@ export class GeoAccessOrchestrator { orchestrateGeoAccess = ( resourceArn: string, resourceIdentifier: GeoResourceType, + resourceName: string, ): Policy[] => { // getting access definitions from allow calls const geoAccessDefinitions = this.geoAccessGenerator( this.roleAccessBuilder, ); + const uniqueRoleTokenSet = new Set(); + geoAccessDefinitions.forEach((definition) => { - const uniqueRoleTokenSet = new Set(); + const uniqueActionSet = new Set(); definition.uniqueDefinitionValidators.forEach( ({ uniqueRoleToken, validationErrorOptions }) => { @@ -80,6 +83,13 @@ export class GeoAccessOrchestrator { resolution: `Please refer to specific ${resourceIdentifier} access actions for more information.`, }); } + if (uniqueActionSet.has(action)) { + throw new AmplifyUserError('DuplicateActionFoundError', { + message: `Desired access action is duplicated for the specific ${resourceIdentifier} resource.`, + resolution: `Remove all but one mentions of the ${action} action for the specific ${resourceIdentifier} resource.`, + }); + } + uniqueActionSet.add(action); }); definition.getAccessAcceptors.forEach((acceptor) => { @@ -88,6 +98,7 @@ export class GeoAccessOrchestrator { definition.actions, resourceArn, acceptor(this.getInstanceProps).identifier, + resourceName, this.resourceStack, ); acceptor(this.getInstanceProps).acceptResourceAccess( diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts index 76b46b218f2..9d0879d3d5b 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.test.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -10,6 +10,7 @@ void describe('GeoAccessPolicyFactory', () => { let geoAccessPolicyFactory: GeoAccessPolicyFactory; const testResourceArn = 'arn:aws:geo:us-east-1:123456789012:geofence-collection/test-collection'; + const testResourceName = 'testResource'; beforeEach(() => { const app = new App(); @@ -23,6 +24,7 @@ void describe('GeoAccessPolicyFactory', () => { [], testResourceArn, 'test-role', + testResourceName, stack, ), ); @@ -33,6 +35,7 @@ void describe('GeoAccessPolicyFactory', () => { ['get'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -45,7 +48,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -62,6 +65,7 @@ void describe('GeoAccessPolicyFactory', () => { ['autocomplete'], testResourceArn, 'guest', + testResourceName, stack, ); @@ -74,7 +78,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-guest-access-policy', + PolicyName: 'geo-testResource-guest-access-policy', PolicyDocument: { Statement: [ { @@ -91,6 +95,7 @@ void describe('GeoAccessPolicyFactory', () => { ['geocode'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -103,7 +108,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -120,6 +125,7 @@ void describe('GeoAccessPolicyFactory', () => { ['search'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -132,7 +138,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -154,6 +160,7 @@ void describe('GeoAccessPolicyFactory', () => { ['create'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -166,7 +173,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -183,6 +190,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -195,7 +203,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -217,6 +225,7 @@ void describe('GeoAccessPolicyFactory', () => { ['update'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -229,7 +238,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -250,6 +259,7 @@ void describe('GeoAccessPolicyFactory', () => { ['delete'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -262,7 +272,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -279,6 +289,7 @@ void describe('GeoAccessPolicyFactory', () => { ['list'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -291,7 +302,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -308,6 +319,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read', 'create', 'update'], testResourceArn, 'authenticated', + testResourceName, stack, ); @@ -320,7 +332,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -346,6 +358,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read'], testResourceArn, 'custom-role-token', + testResourceName, stack, ); @@ -358,7 +371,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-custom-role-token-access-policy', + PolicyName: 'geo-testResource-custom-role-token-access-policy', PolicyDocument: { Statement: [ { @@ -381,6 +394,7 @@ void describe('GeoAccessPolicyFactory', () => { ['get'], mapResourceArn, 'authenticated', + testResourceName, stack, ); @@ -393,7 +407,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -412,6 +426,7 @@ void describe('GeoAccessPolicyFactory', () => { ['search', 'geocode'], placeIndexArn, 'authenticated', + testResourceName, stack, ); @@ -424,7 +439,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-authenticated-access-policy', + PolicyName: 'geo-testResource-authenticated-access-policy', PolicyDocument: { Statement: [ { @@ -448,6 +463,7 @@ void describe('GeoAccessPolicyFactory', () => { ['read', 'update'], testResourceArn, 'group-admin', + testResourceName, stack, ); @@ -460,7 +476,7 @@ void describe('GeoAccessPolicyFactory', () => { const template = Template.fromStack(stack); template.hasResourceProperties('AWS::IAM::Policy', { - PolicyName: 'geo-group-admin-access-policy', + PolicyName: 'geo-testResource-group-admin-access-policy', PolicyDocument: { Statement: [ { diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index a0efbc69b9c..5c394860091 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -12,6 +12,7 @@ export class GeoAccessPolicyFactory { permissions: string[], // organize create policy such that one resource type maps to the actions resourceArn: string, roleToken: string, + resourceName: string, stack: Stack, ) => { if (permissions.length === 0) { @@ -29,7 +30,7 @@ export class GeoAccessPolicyFactory { policyStatement.addResources(resourceArn); - const policyIDName: string = `geo-${roleToken}-access-policy`; + const policyIDName: string = `geo-${resourceName}-${roleToken}-access-policy`; return new Policy(stack, policyIDName, { policyName: policyIDName, statements: [policyStatement], diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 3419cf8bb5f..858a27b381a 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -95,6 +95,7 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( amplifyMap.getResourceArn(), 'map', + amplifyMap.name, ); const geoAspects = Aspects.of(Stack.of(amplifyMap)); diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 1620e915c87..9c8de2a9f63 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -99,6 +99,7 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { geoAccessOrchestrator.orchestrateGeoAccess( amplifyPlace.getResourceArn(), 'place', + amplifyPlace.name, ); const geoAspects = Aspects.of(Stack.of(amplifyPlace)); diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 705af068c4e..b27ed5d7bfe 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -380,26 +380,36 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo": z.ZodOptional; payload: z.ZodObject<{ - aws_region: z.ZodString; - geofence_collections: z.ZodOptional; + geoRegion: z.ZodString; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; + geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }>]>>; }, "strip", z.ZodTypeAny, { @@ -477,8 +487,10 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; } | undefined; }, { @@ -556,8 +568,10 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ "AWS::Amplify::Geo"?: { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; } | undefined; }>; @@ -749,26 +763,36 @@ export const versionedFunctionOutputSchema: z.ZodDiscriminatedUnion<"version", [ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ - aws_region: z.ZodString; - geofence_collections: z.ZodOptional; + geoRegion: z.ZodString; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; + geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }, { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { - aws_region: string; - geofence_collections?: string | undefined; + geoRegion: string; + maps?: string | undefined; + searchIndices?: string | undefined; + geofenceCollections?: string | undefined; }; }>]>; From 133799a0fbebf3a5e98d14f6f560e1c379482fb4 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 09:39:46 -0700 Subject: [PATCH 50/93] adding changeset for changed packages and updated README --- .changeset/old-dodos-create.md | 7 +++++++ packages/backend-geo/README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/old-dodos-create.md diff --git a/.changeset/old-dodos-create.md b/.changeset/old-dodos-create.md new file mode 100644 index 00000000000..7cc98260461 --- /dev/null +++ b/.changeset/old-dodos-create.md @@ -0,0 +1,7 @@ +--- +'@aws-amplify/client-config': patch +'@aws-amplify/backend-geo': patch +'create-amplify': patch +--- + +Adding client configuration for auto-generated backend geo resource outputs. diff --git a/packages/backend-geo/README.md b/packages/backend-geo/README.md index 793417be040..9ad14260ef6 100644 --- a/packages/backend-geo/README.md +++ b/packages/backend-geo/README.md @@ -1,3 +1,3 @@ # Description -Replace with a description of this package +This package defines an L3 construct for the Amplify Geo category. It includes the L3 CDK constructs and resources along with 3 exposed endpoints `defineMap`, `definePlace`, and `defineCollection` to provision those resources. From 92512cbdef2adc3fad3cba6fa13b938f655b4765 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 15:51:57 -0700 Subject: [PATCH 51/93] small fixes --- .changeset/puny-hornets-brush.md | 2 +- .eslint_dictionary.json | 3 +++ package.json | 5 ----- packages/backend-geo/package.json | 3 ++- scripts/check_package_versions.ts | 1 + 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md index 9486bd36484..23aae824a41 100644 --- a/.changeset/puny-hornets-brush.md +++ b/.changeset/puny-hornets-brush.md @@ -3,4 +3,4 @@ '@aws-amplify/backend-output-schemas': patch --- -This changeset involves the introduction of a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. +Introduces a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. diff --git a/.eslint_dictionary.json b/.eslint_dictionary.json index 946c5570d81..fa02bbf53af 100644 --- a/.eslint_dictionary.json +++ b/.eslint_dictionary.json @@ -76,7 +76,9 @@ "frontmatter", "fullname", "func", + "geo", "geofence", + "geofences", "getaddrinfo", "gitignore", "gitignored", @@ -213,6 +215,7 @@ "userpool", "utf", "utimes", + "validators", "verdaccio", "verifier", "versioned", diff --git a/package.json b/package.json index 99dca9415b0..29cc9161332 100644 --- a/package.json +++ b/package.json @@ -117,10 +117,5 @@ "overrides": { "minimatch": "10.0.1", "@graphql-tools/merge": "9.0.22" - }, - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index bb3219ca9f5..e8f82ea102e 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -12,6 +12,7 @@ "require": "./lib/index.js" } }, + "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { "update:api": "api-extractor run --local" @@ -25,6 +26,6 @@ "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" } } diff --git a/scripts/check_package_versions.ts b/scripts/check_package_versions.ts index 31b5208a0d7..b2ea5d0d62f 100644 --- a/scripts/check_package_versions.ts +++ b/scripts/check_package_versions.ts @@ -14,6 +14,7 @@ const getExpectedMajorVersion = (packageName: string) => { return '0.'; case '@aws-amplify/backend-deployer': case '@aws-amplify/cli-core': + case '@aws-amplify/backend-geo': case '@aws-amplify/sandbox': return '2.'; default: From f6f05ca1b544acb3b10b5b76e83312914beec948 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 28 Jul 2025 16:07:43 -0700 Subject: [PATCH 52/93] bumping packages --- packages/backend-geo/package.json | 2 +- scripts/check_package_versions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index e8f82ea102e..85fe77c11e8 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -19,7 +19,7 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" }, "dependencies": { diff --git a/scripts/check_package_versions.ts b/scripts/check_package_versions.ts index b2ea5d0d62f..8b01802bf0a 100644 --- a/scripts/check_package_versions.ts +++ b/scripts/check_package_versions.ts @@ -11,10 +11,10 @@ const packagePaths = await glob('./packages/*'); const getExpectedMajorVersion = (packageName: string) => { switch (packageName) { case 'ampx': + case '@aws-amplify/backend-geo': return '0.'; case '@aws-amplify/backend-deployer': case '@aws-amplify/cli-core': - case '@aws-amplify/backend-geo': case '@aws-amplify/sandbox': return '2.'; default: From 3c1435d8c3a1076e6aa63844de2d6df768e3fcb8 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Tue, 29 Jul 2025 16:09:24 -0700 Subject: [PATCH 53/93] regenerating package-lock with new cdk versions --- package-lock.json | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe5b726d89f..164f869649d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,6 @@ "workspaces": [ "packages/*" ], - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" - }, "devDependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", @@ -583,10 +578,6 @@ "resolved": "packages/backend-function", "link": true }, - "node_modules/@aws-amplify/backend-geo": { - "resolved": "packages/backend-geo", - "link": true - }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12819,19 +12810,6 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, - "node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.206.0-alpha.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", - "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.206.0", - "constructs": "^10.0.0" - } - }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -49767,21 +49745,6 @@ "constructs": "^10.0.0" } }, - "packages/backend-geo": { - "name": "@aws-amplify/backend-geo", - "version": "0.1.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/backend-output-storage": "^1.3.1", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.158.0", - "constructs": "^10.0.0" - } - }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", From e68ce7eb43f73a8a945073c3699523571d104876 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 09:17:24 -0700 Subject: [PATCH 54/93] updating packages and adding v1.5 schema --- package-lock.json | 32 ++ .../client_config_v1.4.ts | 2 +- .../client_config_v1.5.ts | 337 ++++++++++++ .../src/client-config-schema/schema_v1.5.json | 480 ++++++++++++++++++ 4 files changed, 850 insertions(+), 1 deletion(-) create mode 100644 packages/client-config/src/client-config-schema/client_config_v1.5.ts create mode 100644 packages/client-config/src/client-config-schema/schema_v1.5.json diff --git a/package-lock.json b/package-lock.json index 164f869649d..e1ba37c407a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -578,6 +578,10 @@ "resolved": "packages/backend-function", "link": true }, + "node_modules/@aws-amplify/backend-geo": { + "resolved": "packages/backend-geo", + "link": true + }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12810,6 +12814,19 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, + "node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.207.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", + "integrity": "sha512-kuV/iUstJwxuGxALLY4W/pxMsPxMrc67iC+vGN+XlretlFayPGIoK12RB+kV5ZsHPL5QK12V31e7s1LE9WH4/A==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.207.0", + "constructs": "^10.0.0" + } + }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -49745,6 +49762,21 @@ "constructs": "^10.0.0" } }, + "packages/backend-geo": { + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.207.0", + "constructs": "^10.0.0" + } + }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", diff --git a/packages/client-config/src/client-config-schema/client_config_v1.4.ts b/packages/client-config/src/client-config-schema/client_config_v1.4.ts index e84598d88c6..d44b7b52b38 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.4.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.4.ts @@ -74,7 +74,7 @@ export type AmplifyStorageAccessRule = { resource?: AmplifyStorageAccessActions[]; }; /** - * run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated + * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated * * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema * via the `definition` "amplify_storage_access_actions". diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts new file mode 100644 index 00000000000..b1ba572da25 --- /dev/null +++ b/packages/client-config/src/client-config-schema/client_config_v1.5.ts @@ -0,0 +1,337 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_cognito_standard_attributes". + */ +export type AmazonCognitoStandardAttributes = + | 'address' + | 'birthdate' + | 'email' + | 'family_name' + | 'gender' + | 'given_name' + | 'locale' + | 'middle_name' + | 'name' + | 'nickname' + | 'phone_number' + | 'picture' + | 'preferred_username' + | 'profile' + | 'sub' + | 'updated_at' + | 'website' + | 'zoneinfo'; +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "aws_region". + */ +export type AwsRegion = string; +/** + * List of supported auth types for AWS AppSync + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "aws_appsync_authorization_type". + */ +export type AwsAppsyncAuthorizationType = + | 'AMAZON_COGNITO_USER_POOLS' + | 'API_KEY' + | 'AWS_IAM' + | 'AWS_LAMBDA' + | 'OPENID_CONNECT'; +/** + * supported channels for Amazon Pinpoint + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_pinpoint_channels". + */ +export type AmazonPinpointChannels = + | 'IN_APP_MESSAGING' + | 'FCM' + | 'APNS' + | 'EMAIL' + | 'SMS'; +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_access_rule". + */ +export type AmplifyStorageAccessRule = { + guest?: AmplifyStorageAccessActions[]; + authenticated?: AmplifyStorageAccessActions[]; + [groups: `group${string}`]: AmplifyStorageAccessActions[]; + [entity: `entity${string}`]: AmplifyStorageAccessActions[]; + resource?: AmplifyStorageAccessActions[]; +}; +/** + * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_access_actions". + */ +export type AmplifyStorageAccessActions = + | 'read' + | 'get' + | 'list' + | 'write' + | 'delete'; + +/** + * Config format for Amplify Gen 2 client libraries to communicate with backend services. + */ +export interface AWSAmplifyBackendOutputs { + /** + * JSON schema + */ + $schema?: string; + /** + * Version of this schema + */ + version: '1.4'; + /** + * Outputs manually specified by developers for use with frontend library + */ + analytics?: { + amazon_pinpoint?: { + /** + * AWS Region of Amazon Pinpoint resources + */ + aws_region: string; + app_id: string; + }; + }; + /** + * Outputs generated from defineAuth + */ + auth?: { + /** + * AWS Region of Amazon Cognito resources + */ + aws_region: string; + /** + * Cognito User Pool ID + */ + user_pool_id: string; + /** + * Cognito User Pool Client ID + */ + user_pool_client_id: string; + /** + * Cognito Identity Pool ID + */ + identity_pool_id?: string; + /** + * Cognito User Pool password policy + */ + password_policy?: { + min_length: number; + require_numbers: boolean; + require_lowercase: boolean; + require_uppercase: boolean; + require_symbols: boolean; + }; + oauth?: { + /** + * Identity providers set on Cognito User Pool + * + * @minItems 0 + */ + identity_providers: ( + | 'GOOGLE' + | 'FACEBOOK' + | 'LOGIN_WITH_AMAZON' + | 'SIGN_IN_WITH_APPLE' + )[]; + /** + * Domain used for identity providers + */ + domain: string; + /** + * @minItems 0 + */ + scopes: string[]; + /** + * URIs used to redirect after signing in using an identity provider + * + * @minItems 1 + */ + redirect_sign_in_uri: [string, ...string[]]; + /** + * URIs used to redirect after signing out + * + * @minItems 1 + */ + redirect_sign_out_uri: [string, ...string[]]; + response_type: 'code' | 'token'; + }; + /** + * Cognito User Pool standard attributes required for signup + * + * @minItems 0 + */ + standard_required_attributes?: AmazonCognitoStandardAttributes[]; + /** + * Cognito User Pool username attributes + * + * @minItems 1 + */ + username_attributes?: [ + 'email' | 'phone_number' | 'username', + ...('email' | 'phone_number' | 'username')[], + ]; + user_verification_types?: ('email' | 'phone_number')[]; + unauthenticated_identities_enabled?: boolean; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; + groups?: { + [k: string]: AmplifyUserGroupConfig; + }[]; + }; + /** + * Outputs generated from defineData + */ + data?: { + aws_region: AwsRegion; + /** + * AppSync endpoint URL + */ + url: string; + /** + * generated model introspection schema for use with generateClient + */ + model_introspection?: { + [k: string]: unknown; + }; + api_key?: string; + default_authorization_type: AwsAppsyncAuthorizationType; + authorization_types: AwsAppsyncAuthorizationType[]; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + geo?: { + /** + * AWS Region of Amazon Location Service resources + */ + aws_region: string; + /** + * Maps from Amazon Location Service + */ + maps?: { + /** + * @minItems 1 + */ + keys?: [ + { + name?: string; + api_key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + api_key?: string; + [k: string]: unknown; + }[], + ]; + required?: ['keys', 'default']; + }; + /** + * Location search (search by places, addresses, coordinates) + */ + search_indices?: { + /** + * @minItems 1 + */ + keys?: [ + { + name?: string; + api_key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + api_key?: string; + [k: string]: unknown; + }[], + ]; + required?: ['keys', 'default']; + }; + /** + * Geofencing (visualize virtual perimeters) + */ + geofence_collections?: { + /** + * @minItems 1 + */ + items: [string, ...string[]]; + default: string; + }; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + notifications?: { + aws_region: AwsRegion; + amazon_pinpoint_app_id: string; + /** + * @minItems 1 + */ + channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; + }; + /** + * Outputs generated from defineStorage + */ + storage?: { + aws_region: AwsRegion; + bucket_name: string; + buckets?: AmplifyStorageBucket[]; + }; + /** + * Outputs generated from backend.addOutput({ custom: }) + */ + custom?: { + [k: string]: unknown; + }; +} +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_user_group_config". + */ +export interface AmplifyUserGroupConfig { + precedence?: number; +} +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_bucket". + */ +export interface AmplifyStorageBucket { + name: string; + bucket_name: string; + aws_region: string; + paths?: { + [k: string]: AmplifyStorageAccessRule; + }; +} +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_location_service_config". + */ +export interface AmazonLocationServiceConfig { + /** + * Map style + */ + style?: string; +} diff --git a/packages/client-config/src/client-config-schema/schema_v1.5.json b/packages/client-config/src/client-config-schema/schema_v1.5.json new file mode 100644 index 00000000000..01e546605ec --- /dev/null +++ b/packages/client-config/src/client-config-schema/schema_v1.5.json @@ -0,0 +1,480 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amplify.aws/2024-02/outputs-schema.json", + "title": "AWS Amplify Backend Outputs", + "description": "Config format for Amplify Gen 2 client libraries to communicate with backend services.", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "description": "JSON schema", + "type": "string" + }, + "version": { + "description": "Version of this schema", + "const": "1.4" + }, + "analytics": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "amazon_pinpoint": { + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Pinpoint resources", + "$ref": "#/$defs/aws_region" + }, + "app_id": { + "type": "string" + } + }, + "required": ["aws_region", "app_id"] + } + } + }, + "auth": { + "description": "Outputs generated from defineAuth", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Cognito resources", + "$ref": "#/$defs/aws_region" + }, + "user_pool_id": { + "description": "Cognito User Pool ID", + "type": "string" + }, + "user_pool_client_id": { + "description": "Cognito User Pool Client ID", + "type": "string" + }, + "identity_pool_id": { + "description": "Cognito Identity Pool ID", + "type": "string" + }, + "password_policy": { + "description": "Cognito User Pool password policy", + "type": "object", + "additionalProperties": false, + "properties": { + "min_length": { + "type": "integer", + "minimum": 6, + "maximum": 99 + }, + "require_numbers": { + "type": "boolean" + }, + "require_lowercase": { + "type": "boolean" + }, + "require_uppercase": { + "type": "boolean" + }, + "require_symbols": { + "type": "boolean" + } + }, + "required": [ + "min_length", + "require_numbers", + "require_lowercase", + "require_uppercase", + "require_symbols" + ] + }, + "oauth": { + "type": "object", + "additionalProperties": false, + "properties": { + "identity_providers": { + "description": "Identity providers set on Cognito User Pool", + "type": "array", + "items": { + "type": "string", + "enum": [ + "GOOGLE", + "FACEBOOK", + "LOGIN_WITH_AMAZON", + "SIGN_IN_WITH_APPLE" + ] + }, + "minItems": 0, + "uniqueItems": true + }, + "domain": { + "description": "Domain used for identity providers", + "type": "string" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "uniqueItems": true + }, + "redirect_sign_in_uri": { + "description": "URIs used to redirect after signing in using an identity provider", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "redirect_sign_out_uri": { + "description": "URIs used to redirect after signing out", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "response_type": { + "type": "string", + "enum": ["code", "token"] + } + }, + "required": [ + "identity_providers", + "domain", + "scopes", + "redirect_sign_in_uri", + "redirect_sign_out_uri", + "response_type" + ] + }, + "standard_required_attributes": { + "description": "Cognito User Pool standard attributes required for signup", + "type": "array", + "items": { + "$ref": "#/$defs/amazon_cognito_standard_attributes" + }, + "minItems": 0, + "uniqueItems": true + }, + "username_attributes": { + "description": "Cognito User Pool username attributes", + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number", "username"] + }, + "minItems": 1, + "uniqueItems": true + }, + "user_verification_types": { + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number"] + } + }, + "unauthenticated_identities_enabled": { + "type": "boolean", + "default": true + }, + "mfa_configuration": { + "type": "string", + "enum": ["NONE", "OPTIONAL", "REQUIRED"] + }, + "mfa_methods": { + "type": "array", + "items": { + "enum": ["SMS", "TOTP"] + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "propertyNames": { + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_user_group_config" + } + } + } + } + }, + "required": ["aws_region", "user_pool_id", "user_pool_client_id"] + }, + "data": { + "description": "Outputs generated from defineData", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "url": { + "description": "AppSync endpoint URL", + "type": "string" + }, + "model_introspection": { + "description": "generated model introspection schema for use with generateClient", + "type": "object" + }, + "api_key": { + "type": "string" + }, + "default_authorization_type": { + "$ref": "#/$defs/aws_appsync_authorization_type" + }, + "authorization_types": { + "type": "array", + "items": { + "$ref": "#/$defs/aws_appsync_authorization_type" + } + } + }, + "required": [ + "aws_region", + "url", + "default_authorization_type", + "authorization_types" + ] + }, + "geo": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Location Service resources", + "$ref": "#/$defs/aws_region" + }, + "maps": { + "description": "Maps from Amazon Location Service", + "type": "object", + "additionalProperties": false, + "properties": { + "keys": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Actual map name", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "api_key": { + "type": "string" + } + } + }, + "default": { + "type": "string" + } + }, + "required": ["keys", "default"] + } + }, + "search_indices": { + "description": "Location search (search by places, addresses, coordinates)", + "type": "object", + "additionalProperties": false, + "properties": { + "keys": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Actual index name", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "api_key": { + "type": "string" + } + } + }, + "default": { + "type": "string" + } + }, + "required": ["keys", "default"] + } + }, + "geofence_collections": { + "description": "Geofencing (visualize virtual perimeters)", + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Geofence name", + "type": "string" + } + }, + "default": { + "type": "string" + } + }, + "required": ["items", "default"] + } + }, + "required": ["aws_region"] + }, + "notifications": { + "type": "object", + "description": "Outputs manually specified by developers for use with frontend library", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "amazon_pinpoint_app_id": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/$defs/amazon_pinpoint_channels" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["aws_region", "amazon_pinpoint_app_id", "channels"] + }, + "storage": { + "type": "object", + "description": "Outputs generated from defineStorage", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "bucket_name": { + "type": "string" + }, + "buckets": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_bucket" + } + } + }, + "required": ["aws_region", "bucket_name"] + }, + "custom": { + "description": "Outputs generated from backend.addOutput({ custom: })", + "type": "object" + } + }, + "required": ["version"], + "$defs": { + "amplify_storage_access_actions": { + "description": "run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated", + "type": "string", + "enum": ["read", "get", "list", "write", "delete"] + }, + "amplify_storage_access_rule": { + "tsType": "{ guest?: AmplifyStorageAccessActions[]; authenticated?: AmplifyStorageAccessActions[]; [groups: `group${string}`]: AmplifyStorageAccessActions[]; [entity: `entity${string}`]: AmplifyStorageAccessActions[]; resource?: AmplifyStorageAccessActions[]; }" + }, + "amplify_storage_bucket": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "aws_region": { + "type": "string" + }, + "paths": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_storage_access_rule" + } + } + } + }, + "required": ["bucket_name", "aws_region", "name"] + }, + "aws_region": { + "type": "string" + }, + "amazon_cognito_standard_attributes": { + "description": "Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html", + "type": "string", + "enum": [ + "address", + "birthdate", + "email", + "family_name", + "gender", + "given_name", + "locale", + "middle_name", + "name", + "nickname", + "phone_number", + "picture", + "preferred_username", + "profile", + "sub", + "updated_at", + "website", + "zoneinfo" + ] + }, + "aws_appsync_authorization_type": { + "description": "List of supported auth types for AWS AppSync", + "type": "string", + "enum": [ + "AMAZON_COGNITO_USER_POOLS", + "API_KEY", + "AWS_IAM", + "AWS_LAMBDA", + "OPENID_CONNECT" + ] + }, + "amazon_location_service_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "style": { + "description": "Map style", + "type": "string" + } + } + }, + "amazon_pinpoint_channels": { + "description": "supported channels for Amazon Pinpoint", + "type": "string", + "enum": ["IN_APP_MESSAGING", "FCM", "APNS", "EMAIL", "SMS"] + }, + "amplify_user_group_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "precedence": { + "type": "integer" + } + } + } + } +} From 4e5b9078d6ca9509f766e56547d96b78fca167ef Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 09:20:17 -0700 Subject: [PATCH 55/93] stepping back, reverting last commit --- package-lock.json | 32 -- .../client_config_v1.4.ts | 2 +- .../client_config_v1.5.ts | 337 ------------ .../src/client-config-schema/schema_v1.5.json | 480 ------------------ 4 files changed, 1 insertion(+), 850 deletions(-) delete mode 100644 packages/client-config/src/client-config-schema/client_config_v1.5.ts delete mode 100644 packages/client-config/src/client-config-schema/schema_v1.5.json diff --git a/package-lock.json b/package-lock.json index e1ba37c407a..164f869649d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -578,10 +578,6 @@ "resolved": "packages/backend-function", "link": true }, - "node_modules/@aws-amplify/backend-geo": { - "resolved": "packages/backend-geo", - "link": true - }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12814,19 +12810,6 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, - "node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.207.0-alpha.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", - "integrity": "sha512-kuV/iUstJwxuGxALLY4W/pxMsPxMrc67iC+vGN+XlretlFayPGIoK12RB+kV5ZsHPL5QK12V31e7s1LE9WH4/A==", - "license": "Apache-2.0", - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.207.0", - "constructs": "^10.0.0" - } - }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -49762,21 +49745,6 @@ "constructs": "^10.0.0" } }, - "packages/backend-geo": { - "name": "@aws-amplify/backend-geo", - "version": "0.1.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/backend-output-storage": "^1.3.1", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.207.0", - "constructs": "^10.0.0" - } - }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", diff --git a/packages/client-config/src/client-config-schema/client_config_v1.4.ts b/packages/client-config/src/client-config-schema/client_config_v1.4.ts index d44b7b52b38..e84598d88c6 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.4.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.4.ts @@ -74,7 +74,7 @@ export type AmplifyStorageAccessRule = { resource?: AmplifyStorageAccessActions[]; }; /** - * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated + * run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated * * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema * via the `definition` "amplify_storage_access_actions". diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts deleted file mode 100644 index b1ba572da25..00000000000 --- a/packages/client-config/src/client-config-schema/client_config_v1.5.ts +++ /dev/null @@ -1,337 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -/** - * Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amazon_cognito_standard_attributes". - */ -export type AmazonCognitoStandardAttributes = - | 'address' - | 'birthdate' - | 'email' - | 'family_name' - | 'gender' - | 'given_name' - | 'locale' - | 'middle_name' - | 'name' - | 'nickname' - | 'phone_number' - | 'picture' - | 'preferred_username' - | 'profile' - | 'sub' - | 'updated_at' - | 'website' - | 'zoneinfo'; -/** - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "aws_region". - */ -export type AwsRegion = string; -/** - * List of supported auth types for AWS AppSync - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "aws_appsync_authorization_type". - */ -export type AwsAppsyncAuthorizationType = - | 'AMAZON_COGNITO_USER_POOLS' - | 'API_KEY' - | 'AWS_IAM' - | 'AWS_LAMBDA' - | 'OPENID_CONNECT'; -/** - * supported channels for Amazon Pinpoint - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amazon_pinpoint_channels". - */ -export type AmazonPinpointChannels = - | 'IN_APP_MESSAGING' - | 'FCM' - | 'APNS' - | 'EMAIL' - | 'SMS'; -/** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` ".*". - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amplify_storage_access_rule". - */ -export type AmplifyStorageAccessRule = { - guest?: AmplifyStorageAccessActions[]; - authenticated?: AmplifyStorageAccessActions[]; - [groups: `group${string}`]: AmplifyStorageAccessActions[]; - [entity: `entity${string}`]: AmplifyStorageAccessActions[]; - resource?: AmplifyStorageAccessActions[]; -}; -/** - * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amplify_storage_access_actions". - */ -export type AmplifyStorageAccessActions = - | 'read' - | 'get' - | 'list' - | 'write' - | 'delete'; - -/** - * Config format for Amplify Gen 2 client libraries to communicate with backend services. - */ -export interface AWSAmplifyBackendOutputs { - /** - * JSON schema - */ - $schema?: string; - /** - * Version of this schema - */ - version: '1.4'; - /** - * Outputs manually specified by developers for use with frontend library - */ - analytics?: { - amazon_pinpoint?: { - /** - * AWS Region of Amazon Pinpoint resources - */ - aws_region: string; - app_id: string; - }; - }; - /** - * Outputs generated from defineAuth - */ - auth?: { - /** - * AWS Region of Amazon Cognito resources - */ - aws_region: string; - /** - * Cognito User Pool ID - */ - user_pool_id: string; - /** - * Cognito User Pool Client ID - */ - user_pool_client_id: string; - /** - * Cognito Identity Pool ID - */ - identity_pool_id?: string; - /** - * Cognito User Pool password policy - */ - password_policy?: { - min_length: number; - require_numbers: boolean; - require_lowercase: boolean; - require_uppercase: boolean; - require_symbols: boolean; - }; - oauth?: { - /** - * Identity providers set on Cognito User Pool - * - * @minItems 0 - */ - identity_providers: ( - | 'GOOGLE' - | 'FACEBOOK' - | 'LOGIN_WITH_AMAZON' - | 'SIGN_IN_WITH_APPLE' - )[]; - /** - * Domain used for identity providers - */ - domain: string; - /** - * @minItems 0 - */ - scopes: string[]; - /** - * URIs used to redirect after signing in using an identity provider - * - * @minItems 1 - */ - redirect_sign_in_uri: [string, ...string[]]; - /** - * URIs used to redirect after signing out - * - * @minItems 1 - */ - redirect_sign_out_uri: [string, ...string[]]; - response_type: 'code' | 'token'; - }; - /** - * Cognito User Pool standard attributes required for signup - * - * @minItems 0 - */ - standard_required_attributes?: AmazonCognitoStandardAttributes[]; - /** - * Cognito User Pool username attributes - * - * @minItems 1 - */ - username_attributes?: [ - 'email' | 'phone_number' | 'username', - ...('email' | 'phone_number' | 'username')[], - ]; - user_verification_types?: ('email' | 'phone_number')[]; - unauthenticated_identities_enabled?: boolean; - mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; - mfa_methods?: ('SMS' | 'TOTP')[]; - groups?: { - [k: string]: AmplifyUserGroupConfig; - }[]; - }; - /** - * Outputs generated from defineData - */ - data?: { - aws_region: AwsRegion; - /** - * AppSync endpoint URL - */ - url: string; - /** - * generated model introspection schema for use with generateClient - */ - model_introspection?: { - [k: string]: unknown; - }; - api_key?: string; - default_authorization_type: AwsAppsyncAuthorizationType; - authorization_types: AwsAppsyncAuthorizationType[]; - }; - /** - * Outputs manually specified by developers for use with frontend library - */ - geo?: { - /** - * AWS Region of Amazon Location Service resources - */ - aws_region: string; - /** - * Maps from Amazon Location Service - */ - maps?: { - /** - * @minItems 1 - */ - keys?: [ - { - name?: string; - api_key?: string; - [k: string]: unknown; - }, - ...{ - name?: string; - api_key?: string; - [k: string]: unknown; - }[], - ]; - required?: ['keys', 'default']; - }; - /** - * Location search (search by places, addresses, coordinates) - */ - search_indices?: { - /** - * @minItems 1 - */ - keys?: [ - { - name?: string; - api_key?: string; - [k: string]: unknown; - }, - ...{ - name?: string; - api_key?: string; - [k: string]: unknown; - }[], - ]; - required?: ['keys', 'default']; - }; - /** - * Geofencing (visualize virtual perimeters) - */ - geofence_collections?: { - /** - * @minItems 1 - */ - items: [string, ...string[]]; - default: string; - }; - }; - /** - * Outputs manually specified by developers for use with frontend library - */ - notifications?: { - aws_region: AwsRegion; - amazon_pinpoint_app_id: string; - /** - * @minItems 1 - */ - channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; - }; - /** - * Outputs generated from defineStorage - */ - storage?: { - aws_region: AwsRegion; - bucket_name: string; - buckets?: AmplifyStorageBucket[]; - }; - /** - * Outputs generated from backend.addOutput({ custom: }) - */ - custom?: { - [k: string]: unknown; - }; -} -/** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` ".*". - * - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amplify_user_group_config". - */ -export interface AmplifyUserGroupConfig { - precedence?: number; -} -/** - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amplify_storage_bucket". - */ -export interface AmplifyStorageBucket { - name: string; - bucket_name: string; - aws_region: string; - paths?: { - [k: string]: AmplifyStorageAccessRule; - }; -} -/** - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amazon_location_service_config". - */ -export interface AmazonLocationServiceConfig { - /** - * Map style - */ - style?: string; -} diff --git a/packages/client-config/src/client-config-schema/schema_v1.5.json b/packages/client-config/src/client-config-schema/schema_v1.5.json deleted file mode 100644 index 01e546605ec..00000000000 --- a/packages/client-config/src/client-config-schema/schema_v1.5.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://amplify.aws/2024-02/outputs-schema.json", - "title": "AWS Amplify Backend Outputs", - "description": "Config format for Amplify Gen 2 client libraries to communicate with backend services.", - "type": "object", - "additionalProperties": false, - "properties": { - "$schema": { - "description": "JSON schema", - "type": "string" - }, - "version": { - "description": "Version of this schema", - "const": "1.4" - }, - "analytics": { - "description": "Outputs manually specified by developers for use with frontend library", - "type": "object", - "additionalProperties": false, - "properties": { - "amazon_pinpoint": { - "type": "object", - "additionalProperties": false, - "properties": { - "aws_region": { - "description": "AWS Region of Amazon Pinpoint resources", - "$ref": "#/$defs/aws_region" - }, - "app_id": { - "type": "string" - } - }, - "required": ["aws_region", "app_id"] - } - } - }, - "auth": { - "description": "Outputs generated from defineAuth", - "type": "object", - "additionalProperties": false, - "properties": { - "aws_region": { - "description": "AWS Region of Amazon Cognito resources", - "$ref": "#/$defs/aws_region" - }, - "user_pool_id": { - "description": "Cognito User Pool ID", - "type": "string" - }, - "user_pool_client_id": { - "description": "Cognito User Pool Client ID", - "type": "string" - }, - "identity_pool_id": { - "description": "Cognito Identity Pool ID", - "type": "string" - }, - "password_policy": { - "description": "Cognito User Pool password policy", - "type": "object", - "additionalProperties": false, - "properties": { - "min_length": { - "type": "integer", - "minimum": 6, - "maximum": 99 - }, - "require_numbers": { - "type": "boolean" - }, - "require_lowercase": { - "type": "boolean" - }, - "require_uppercase": { - "type": "boolean" - }, - "require_symbols": { - "type": "boolean" - } - }, - "required": [ - "min_length", - "require_numbers", - "require_lowercase", - "require_uppercase", - "require_symbols" - ] - }, - "oauth": { - "type": "object", - "additionalProperties": false, - "properties": { - "identity_providers": { - "description": "Identity providers set on Cognito User Pool", - "type": "array", - "items": { - "type": "string", - "enum": [ - "GOOGLE", - "FACEBOOK", - "LOGIN_WITH_AMAZON", - "SIGN_IN_WITH_APPLE" - ] - }, - "minItems": 0, - "uniqueItems": true - }, - "domain": { - "description": "Domain used for identity providers", - "type": "string" - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 0, - "uniqueItems": true - }, - "redirect_sign_in_uri": { - "description": "URIs used to redirect after signing in using an identity provider", - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - }, - "redirect_sign_out_uri": { - "description": "URIs used to redirect after signing out", - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - }, - "response_type": { - "type": "string", - "enum": ["code", "token"] - } - }, - "required": [ - "identity_providers", - "domain", - "scopes", - "redirect_sign_in_uri", - "redirect_sign_out_uri", - "response_type" - ] - }, - "standard_required_attributes": { - "description": "Cognito User Pool standard attributes required for signup", - "type": "array", - "items": { - "$ref": "#/$defs/amazon_cognito_standard_attributes" - }, - "minItems": 0, - "uniqueItems": true - }, - "username_attributes": { - "description": "Cognito User Pool username attributes", - "type": "array", - "items": { - "type": "string", - "enum": ["email", "phone_number", "username"] - }, - "minItems": 1, - "uniqueItems": true - }, - "user_verification_types": { - "type": "array", - "items": { - "type": "string", - "enum": ["email", "phone_number"] - } - }, - "unauthenticated_identities_enabled": { - "type": "boolean", - "default": true - }, - "mfa_configuration": { - "type": "string", - "enum": ["NONE", "OPTIONAL", "REQUIRED"] - }, - "mfa_methods": { - "type": "array", - "items": { - "enum": ["SMS", "TOTP"] - } - }, - "groups": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "propertyNames": { - "type": "string" - }, - "patternProperties": { - ".*": { - "$ref": "#/$defs/amplify_user_group_config" - } - } - } - } - }, - "required": ["aws_region", "user_pool_id", "user_pool_client_id"] - }, - "data": { - "description": "Outputs generated from defineData", - "type": "object", - "additionalProperties": false, - "properties": { - "aws_region": { - "$ref": "#/$defs/aws_region" - }, - "url": { - "description": "AppSync endpoint URL", - "type": "string" - }, - "model_introspection": { - "description": "generated model introspection schema for use with generateClient", - "type": "object" - }, - "api_key": { - "type": "string" - }, - "default_authorization_type": { - "$ref": "#/$defs/aws_appsync_authorization_type" - }, - "authorization_types": { - "type": "array", - "items": { - "$ref": "#/$defs/aws_appsync_authorization_type" - } - } - }, - "required": [ - "aws_region", - "url", - "default_authorization_type", - "authorization_types" - ] - }, - "geo": { - "description": "Outputs manually specified by developers for use with frontend library", - "type": "object", - "additionalProperties": false, - "properties": { - "aws_region": { - "description": "AWS Region of Amazon Location Service resources", - "$ref": "#/$defs/aws_region" - }, - "maps": { - "description": "Maps from Amazon Location Service", - "type": "object", - "additionalProperties": false, - "properties": { - "keys": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "description": "Actual map name", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "api_key": { - "type": "string" - } - } - }, - "default": { - "type": "string" - } - }, - "required": ["keys", "default"] - } - }, - "search_indices": { - "description": "Location search (search by places, addresses, coordinates)", - "type": "object", - "additionalProperties": false, - "properties": { - "keys": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "description": "Actual index name", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "api_key": { - "type": "string" - } - } - }, - "default": { - "type": "string" - } - }, - "required": ["keys", "default"] - } - }, - "geofence_collections": { - "description": "Geofencing (visualize virtual perimeters)", - "type": "object", - "additionalProperties": false, - "properties": { - "items": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "description": "Geofence name", - "type": "string" - } - }, - "default": { - "type": "string" - } - }, - "required": ["items", "default"] - } - }, - "required": ["aws_region"] - }, - "notifications": { - "type": "object", - "description": "Outputs manually specified by developers for use with frontend library", - "additionalProperties": false, - "properties": { - "aws_region": { - "$ref": "#/$defs/aws_region" - }, - "amazon_pinpoint_app_id": { - "type": "string" - }, - "channels": { - "type": "array", - "items": { - "$ref": "#/$defs/amazon_pinpoint_channels" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "required": ["aws_region", "amazon_pinpoint_app_id", "channels"] - }, - "storage": { - "type": "object", - "description": "Outputs generated from defineStorage", - "additionalProperties": false, - "properties": { - "aws_region": { - "$ref": "#/$defs/aws_region" - }, - "bucket_name": { - "type": "string" - }, - "buckets": { - "type": "array", - "items": { - "$ref": "#/$defs/amplify_storage_bucket" - } - } - }, - "required": ["aws_region", "bucket_name"] - }, - "custom": { - "description": "Outputs generated from backend.addOutput({ custom: })", - "type": "object" - } - }, - "required": ["version"], - "$defs": { - "amplify_storage_access_actions": { - "description": "run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated", - "type": "string", - "enum": ["read", "get", "list", "write", "delete"] - }, - "amplify_storage_access_rule": { - "tsType": "{ guest?: AmplifyStorageAccessActions[]; authenticated?: AmplifyStorageAccessActions[]; [groups: `group${string}`]: AmplifyStorageAccessActions[]; [entity: `entity${string}`]: AmplifyStorageAccessActions[]; resource?: AmplifyStorageAccessActions[]; }" - }, - "amplify_storage_bucket": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "bucket_name": { - "type": "string" - }, - "aws_region": { - "type": "string" - }, - "paths": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - ".*": { - "$ref": "#/$defs/amplify_storage_access_rule" - } - } - } - }, - "required": ["bucket_name", "aws_region", "name"] - }, - "aws_region": { - "type": "string" - }, - "amazon_cognito_standard_attributes": { - "description": "Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html", - "type": "string", - "enum": [ - "address", - "birthdate", - "email", - "family_name", - "gender", - "given_name", - "locale", - "middle_name", - "name", - "nickname", - "phone_number", - "picture", - "preferred_username", - "profile", - "sub", - "updated_at", - "website", - "zoneinfo" - ] - }, - "aws_appsync_authorization_type": { - "description": "List of supported auth types for AWS AppSync", - "type": "string", - "enum": [ - "AMAZON_COGNITO_USER_POOLS", - "API_KEY", - "AWS_IAM", - "AWS_LAMBDA", - "OPENID_CONNECT" - ] - }, - "amazon_location_service_config": { - "type": "object", - "additionalProperties": false, - "properties": { - "style": { - "description": "Map style", - "type": "string" - } - } - }, - "amazon_pinpoint_channels": { - "description": "supported channels for Amazon Pinpoint", - "type": "string", - "enum": ["IN_APP_MESSAGING", "FCM", "APNS", "EMAIL", "SMS"] - }, - "amplify_user_group_config": { - "type": "object", - "additionalProperties": false, - "properties": { - "precedence": { - "type": "integer" - } - } - } - } -} From 3604e2ee866612eabc0d8ebcb8b24b290afba7bf Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 09:21:30 -0700 Subject: [PATCH 56/93] new package-lock --- package-lock.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/package-lock.json b/package-lock.json index 164f869649d..e1ba37c407a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -578,6 +578,10 @@ "resolved": "packages/backend-function", "link": true }, + "node_modules/@aws-amplify/backend-geo": { + "resolved": "packages/backend-geo", + "link": true + }, "node_modules/@aws-amplify/backend-output-schemas": { "resolved": "packages/backend-output-schemas", "link": true @@ -12810,6 +12814,19 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, + "node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.207.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", + "integrity": "sha512-kuV/iUstJwxuGxALLY4W/pxMsPxMrc67iC+vGN+XlretlFayPGIoK12RB+kV5ZsHPL5QK12V31e7s1LE9WH4/A==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.207.0", + "constructs": "^10.0.0" + } + }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -49745,6 +49762,21 @@ "constructs": "^10.0.0" } }, + "packages/backend-geo": { + "name": "@aws-amplify/backend-geo", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/backend-output-schemas": "^1.7.0", + "@aws-amplify/backend-output-storage": "^1.3.1", + "@aws-amplify/platform-core": "^1.10.0", + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.207.0", + "constructs": "^10.0.0" + } + }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", From 7e9f821202744096e7c09cef7a4725ce6e0af717 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 10:02:00 -0700 Subject: [PATCH 57/93] small fixes v3 --- packages/backend-geo/src/access_builder.test.ts | 16 ---------------- .../backend-geo/src/geo_access_policy_factory.ts | 2 +- packages/backend-geo/src/geo_outputs_aspect.ts | 9 ++++----- packages/backend-geo/src/map_factory.ts | 4 ++-- packages/backend-geo/src/place_factory.ts | 4 ++-- packages/backend-geo/src/types.ts | 3 +++ .../client_config_contributor_v1.ts | 2 +- 7 files changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/backend-geo/src/access_builder.test.ts b/packages/backend-geo/src/access_builder.test.ts index 88ef3387565..0a8489d9898 100644 --- a/packages/backend-geo/src/access_builder.test.ts +++ b/packages/backend-geo/src/access_builder.test.ts @@ -271,20 +271,4 @@ void describe('GeoAccessBuilder', () => { }, ); }); - - void it('handles empty group array', () => { - const accessDefinition = roleAccessBuilder.groups([]).to(['get']); - - assert.deepStrictEqual(accessDefinition.actions, ['get']); - assert.equal(accessDefinition.getAccessAcceptors.length, 0); - assert.equal(accessDefinition.uniqueDefinitionValidators.length, 0); - }); - - void it('handles empty actions array', () => { - const accessDefinition = roleAccessBuilder.authenticated.to([]); - - assert.deepStrictEqual(accessDefinition.actions, []); - assert.equal(accessDefinition.getAccessAcceptors.length, 1); - assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); - }); }); diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 5c394860091..c8e1d3b831c 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -21,7 +21,7 @@ export class GeoAccessPolicyFactory { }); } - // policy statements created for each resource type? + // policy statements created for each resource type const policyStatement: PolicyStatement = new PolicyStatement(); permissions.forEach((action) => { diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index b58d83eae0d..812496e1da9 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -88,9 +88,9 @@ export class AmplifyGeoOutputsAspect implements IAspect { // if default exists and instance is default (throw multiple defaults error) throw new AmplifyUserError('MultipleDefaultCollectionError', { message: - 'Multiple instances of geofence collections have been marked as default.', + 'More than one default geofence collection set in the Amplify project', resolution: - 'Remove `isDefault: true` from all but one `defineCollection` call.', + 'Remove `isDefault: true` from all but one `defineCollection` calls except for one in your Amplify project', }); } }); @@ -102,10 +102,9 @@ export class AmplifyGeoOutputsAspect implements IAspect { } else if (collectionCount > 1 && !defaultCollectionName) { // if multiple constructs with default collection, throw error throw new AmplifyUserError('NoDefaultCollectionError', { - message: - 'No instances of geofence collections have been marked as default.', + message: 'No default geofence collection set in the Amplify project', resolution: - 'Add `isDefault: true` to one of the `defineCollection` calls.', + 'Add `isDefault: true` to one of the `defineCollection` calls in your Amplify project', }); } diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 858a27b381a..52f35582c71 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -34,8 +34,8 @@ export class AmplifyMapFactory if (AmplifyMapFactory.mapCount > 0) { throw new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `defineMap` calls not permitted within an Amplify backend', - resolution: 'Maintain one `defineMap` call', + 'Multiple `defineMap` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `defineMap` call', }); } AmplifyMapFactory.mapCount++; diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 9c8de2a9f63..ce80d5f5d65 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -34,8 +34,8 @@ export class AmplifyPlaceFactory if (AmplifyPlaceFactory.placeCount > 0) { throw new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `definePlace` calls not permitted within an Amplify backend', - resolution: 'Maintain one `definePlace` call', + 'Multiple `definePlace` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `definePlace` call', }); } AmplifyPlaceFactory.placeCount++; diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 736383b539e..ca2a7def351 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -22,6 +22,7 @@ export type AmplifyMapFactoryProps = Omit< 'outputStorageStrategy' > & { /** + * @todo update link with geo documentation * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) * @example * const map = defineMap({ @@ -41,6 +42,7 @@ export type AmplifyPlaceFactoryProps = Omit< 'outputStorageStrategy' > & { /** + * @todo update link with geo documentation * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) * @example * const index = definePlace({ @@ -60,6 +62,7 @@ export type AmplifyCollectionFactoryProps = Omit< 'outputStorageStrategy' > & { /** + * @todo update link with geo documentation * access definition for maps (@see https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/ for more information) * @example * const collection = defineCollection({ diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index e1838d3833b..a778c66dcaa 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -503,7 +503,7 @@ export class GeoClientConfigContributor implements ClientConfigContributor { } if (geofenceCollectionsObj && geofenceCollectionsObj.default) { - config.geo!.geofence_collections = { + config.geo.geofence_collections = { default: geofenceCollectionsObj.default, items: geofenceCollectionsObj.items || [], }; From 668d5fc624040f0b14189c07bbe204ef8667f725 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 10:51:29 -0700 Subject: [PATCH 58/93] adding new schema and api key integration v1 --- .../backend-geo/src/collection_construct.ts | 2 +- .../backend-geo/src/geo_outputs_aspect.ts | 122 ++++- packages/backend-geo/src/map_resource.ts | 16 +- packages/backend-geo/src/place_resource.ts | 17 +- packages/backend-geo/src/types.ts | 16 +- packages/backend-output-schemas/src/geo/v1.ts | 18 +- .../client_config_contributor_factory.ts | 12 +- .../client_config_contributor_v1.ts | 45 ++ .../client_config_v1.4.ts | 2 +- .../client_config_v1.5.ts | 337 ++++++++++++ .../src/client-config-schema/schema_v1.5.json | 480 ++++++++++++++++++ .../src/client-config-types/client_config.ts | 7 +- 12 files changed, 1043 insertions(+), 31 deletions(-) create mode 100644 packages/client-config/src/client-config-schema/client_config_v1.5.ts create mode 100644 packages/client-config/src/client-config-schema/schema_v1.5.json diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index 564497e63bb..600151d3d0e 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -8,7 +8,7 @@ import { Policy } from 'aws-cdk-lib/aws-iam'; import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage'; import { fileURLToPath } from 'node:url'; -const geoStackType = 'geo-GeofenceCollection'; +const geoStackType = 'geo-Location'; /** * Amplify Collection CDK Construct diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 6b548370e3c..1460717ded6 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -6,6 +6,8 @@ import { AmplifyPlace } from './place_resource.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; +import { GeoResource, ResourceOutputs } from './types.js'; +import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; /** * Aspect Implementation for Geo Resources @@ -64,27 +66,28 @@ export class AmplifyGeoOutputsAspect implements IAspect { ) { this.addBackendOutput( collectionInstances, + mapInstances, + placeInstances, this.geoOutputStorageStrategy, Stack.of(node).region, ); } } - private validateDefaultCollection = ( - nodes: AmplifyCollection[], - currentNode: AmplifyCollection, + private validateDefault = ( + nodes: GeoResource[], + currentNode: GeoResource, ) => { const collectionCount = nodes.length; - let defaultCollectionName: string | undefined = undefined; + let defaultNode: GeoResource | undefined = undefined; // go through all children and find the default (make duplicity check on defaults) nodes.forEach((instance) => { - if (!defaultCollectionName && instance.isDefault) { + if (!defaultNode && instance.isDefault) { // if no default exists and instance is default, mark it - defaultCollectionName = - instance.resources.collection?.geofenceCollectionName; - } else if (instance.isDefault && defaultCollectionName) { + defaultNode = instance; + } else if (instance.isDefault && defaultNode) { // if default exists and instance is default (throw multiple defaults error) throw new AmplifyUserError('MultipleDefaultCollectionError', { message: @@ -95,11 +98,10 @@ export class AmplifyGeoOutputsAspect implements IAspect { } }); - if (collectionCount === 1 && !defaultCollectionName) { + if (!defaultNode && collectionCount === 1) { // if no defaults and only one construct, instance assumed to be default - defaultCollectionName = - currentNode.resources.collection?.geofenceCollectionName; - } else if (collectionCount > 1 && !defaultCollectionName) { + defaultNode = currentNode; + } else if (collectionCount > 1 && !defaultNode) { // if multiple constructs with default collection, throw error throw new AmplifyUserError('NoDefaultCollectionError', { message: 'No default geofence collection set in the Amplify project', @@ -108,24 +110,69 @@ export class AmplifyGeoOutputsAspect implements IAspect { }); } - return defaultCollectionName; + return defaultNode; + }; + + private validateKeys = (nodes: GeoResource[]) => { + const apiKeys: CfnAPIKey[] = []; + // finding duplicate api keys (same actions) + nodes.forEach((node) => { + if ( + !(node instanceof AmplifyCollection) && + node.resources.cfnResources.cfnAPIKey + ) + apiKeys.push(node.resources.cfnResources.cfnAPIKey); + }); + + const hasDuplicates = apiKeys.some((key, index) => { + apiKeys + .slice(index + 1) + .some( + (secondKey) => + JSON.stringify(key.restrictions) === + JSON.stringify(secondKey.restrictions), + ); + }); + + if (hasDuplicates) + throw new AmplifyUserError('DuplicateAPIKeyError', { + message: + 'Multiple api keys with the same actions have been created for this resource.', + resolution: + 'Remove all api keys for this region with the same actions for this resource.', + }); }; /** * Function responsible for add all collection outputs (with defaults) - * @param collections - all construct instances of AmplifyGeo + * @param collections - all construct instances of Amplify Geo + * @param maps - all map resources of Amplify Geo + * @param places - all place index resources of Amplify Geo * @param outputStorageStrategy - backend output schema of type GeoOutput * @param region - region of geo resources */ private addBackendOutput( collections: AmplifyCollection[], + maps: AmplifyMap[], + places: AmplifyPlace[], outputStorageStrategy: BackendOutputStorageStrategy, region: string, ) { - const defaultCollectionName = this.validateDefaultCollection( + const defaultCollection = this.validateDefault( collections, collections[0], - ); + ) as AmplifyCollection; + + const defaultMap = this.validateDefault(maps, maps[0]) as AmplifyMap; + + this.validateKeys(maps); + + const defaultPlace = this.validateDefault( + places, + places[0], + ) as AmplifyPlace; + + this.validateKeys(places); // Add the main geo output entry with aws_region (snake_case to match schema) outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { @@ -140,18 +187,57 @@ export class AmplifyGeoOutputsAspect implements IAspect { (collection) => collection.resources.collection.geofenceCollectionName, ); + const mapOutputs: ResourceOutputs[] = maps.map((map): ResourceOutputs => { + return { + name: map.name, + apiKey: map.resources.cfnResources.cfnAPIKey?.ref, + }; + }); + + const placeOutputs: ResourceOutputs[] = places.map( + (place): ResourceOutputs => { + return { + name: place.name, + apiKey: place.resources.cfnResources.cfnAPIKey?.ref, + }; + }, + ); + // Add geofence_collections as a single entry with all collections - if (collections.length > 0 && defaultCollectionName) { + if (collections.length > 0 && defaultCollection) { outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { version: '1', payload: { geofenceCollections: JSON.stringify({ // Changed from geofenceCollections to geofence_collections - default: defaultCollectionName, + default: + defaultCollection.resources.collection.geofenceCollectionName, items: collectionNames, // Array of all collection names }), }, }); } + if (maps.length > 0 && defaultMap) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + maps: JSON.stringify({ + default: defaultMap.name, + items: mapOutputs, + }), + }, + }); + } + if (places.length > 0 && defaultPlace) { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + searchIndices: JSON.stringify({ + default: defaultPlace.name, + items: placeOutputs, + }), + }, + }); + } } } diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 32c621dcc6e..a029b4ae4be 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -1,9 +1,14 @@ +import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage'; import { AmplifyMapProps, MapResources } from './types.js'; import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; import { AllowMapsAction, ApiKey } from '@aws-cdk/aws-location-alpha'; import { Aws, Resource } from 'aws-cdk-lib'; import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { Construct } from 'constructs'; +import { fileURLToPath } from 'node:url'; +import { Policy } from 'aws-cdk-lib/aws-iam'; + +const geoStackType = 'geo-Location'; /** * Resource for AWS-managed Maps @@ -15,6 +20,8 @@ export class AmplifyMap readonly resources: MapResources; readonly id: string; readonly name: string; + readonly isDefault: boolean; + readonly policies: Policy[]; private readonly props: AmplifyMapProps; /** @@ -24,14 +31,21 @@ export class AmplifyMap super(scope, id); this.name = props.name; this.id = id; + this.isDefault = props.isDefault || false; this.props = props; this.resources = { region: this.stack.region, - policies: [], + policies: this.policies, cfnResources: {}, }; + + new AttributionMetadataStorage().storeAttributionMetadata( + this.stack, + geoStackType, + fileURLToPath(new URL('../package.json', import.meta.url)), + ); } getResourceArn = (): string => { diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index aaf807d014f..1b1b624afa4 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -1,10 +1,14 @@ +import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage'; import { AmplifyPlaceProps, PlaceResources } from './types.js'; import { ResourceProvider, StackProvider } from '@aws-amplify/plugin-types'; import { AllowPlacesAction, ApiKey } from '@aws-cdk/aws-location-alpha'; -import { Aws, Resource } from 'aws-cdk-lib'; +import { Aws, Resource, Stack } from 'aws-cdk-lib'; +import { Policy } from 'aws-cdk-lib/aws-iam'; import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { Construct } from 'constructs'; +import { fileURLToPath } from 'node:url'; +const geoStackType = 'geo-Location'; /** * Resource for AWS-managed Place Indices */ @@ -15,6 +19,8 @@ export class AmplifyPlace readonly resources: PlaceResources; readonly id: string; readonly name: string; + readonly isDefault: boolean; + readonly policies: Policy[]; private readonly props: AmplifyPlaceProps; /** @@ -25,14 +31,21 @@ export class AmplifyPlace this.name = props.name; this.id = id; + this.isDefault = props.isDefault || false; this.props = props; this.resources = { region: this.stack.region, - policies: [], + policies: this.policies, cfnResources: {}, }; + + new AttributionMetadataStorage().storeAttributionMetadata( + Stack.of(this), + geoStackType, + fileURLToPath(new URL('../package.json', import.meta.url)), + ); } getResourceArn = (): string => { diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index b06527de412..9c72a9f6fce 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -15,6 +15,9 @@ import { import { CfnAPIKey, CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; +import { AmplifyMap } from './map_resource.js'; +import { AmplifyPlace } from './place_resource.js'; +import { AmplifyCollection } from './collection_construct.js'; // ----------------------------------- factory properties ---------------------------------------------- @@ -80,14 +83,14 @@ export type AmplifyCollectionFactoryProps = Omit< export type AmplifyMapProps = Omit< AmplifyCollectionProps, - 'collectionProps' | 'isDefault' + 'collectionProps' > & { apiKeyProps?: GeoApiKeyProps; }; export type AmplifyPlaceProps = Omit< AmplifyCollectionProps, - 'collectionProps' | 'isDefault' + 'collectionProps' > & { apiKeyProps?: GeoApiKeyProps; }; @@ -104,6 +107,8 @@ export type GeoApiKeyProps = Omit< 'allowMapsActions' | 'allowPlacesActions' >; +// ----------------------------------- output properties ---------------------------------------------- + /** * Backend-accessible resources from AmplifyMap * @param policies - access policies of the frontend-accessible map resource @@ -144,6 +149,11 @@ export type CollectionResources = { }; }; +export type ResourceOutputs = { + name: string; + apiKey?: string; +}; + // ----------------------------------- access definitions ---------------------------------------------- export type GeoAccessGenerator = ( @@ -184,6 +194,8 @@ export const resourceActionRecord: Record = { collection: ['create', 'read', 'update', 'delete', 'list'], }; +export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; + export type GeoApiActionType = AllowMapsAction | AllowPlacesAction; export type GeoResourceType = 'map' | 'place' | 'collection'; diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 9be0b349d14..54f527522cb 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -1,16 +1,26 @@ import { z } from 'zod'; -const collectionSchema = z.object({ +const collectionConstructSchema = z.object({ default: z.string(), items: z.array(z.string()), }); +const resourceItemSchema = z.object({ + name: z.string(), + api_key: z.string().optional(), +}); + +const resourceSchema = z.object({ + default: z.string(), + items: z.string(z.array(resourceItemSchema)), +}); + export const geoOutputSchema = z.object({ version: z.literal('1'), payload: z.object({ geoRegion: z.string(), - maps: z.string().optional(), // JSON serialized string - searchIndices: z.string().optional(), // JSON serialized string - geofenceCollections: z.string(collectionSchema).optional(), // JSON serialized string + maps: z.string(resourceSchema).optional(), // JSON serialized string + searchIndices: z.string(resourceSchema).optional(), // JSON serialized string + geofenceCollections: z.string(collectionConstructSchema).optional(), // JSON serialized string }), }); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts index 6aeb34dab86..5bf6e34af2a 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts @@ -4,7 +4,8 @@ import { AuthClientConfigContributor as Auth1_3, CustomClientConfigContributor as Custom1_1, DataClientConfigContributor as Data1_1, - GeoClientConfigContributor as Geo1, + GeoClientConfigContributorV1 as Geo1, + GeoClientConfigContributor as Geo1_1, StorageClientConfigContributorV1 as Storage1, StorageClientConfigContributorV1_1 as Storage1_1, StorageClientConfigContributor as Storage1_2, @@ -37,6 +38,15 @@ export class ClientConfigContributorFactory { private readonly modelIntrospectionSchemaAdapter: ModelIntrospectionSchemaAdapter, ) { this.versionedClientConfigContributors = { + [ClientConfigVersionOption.V1_5]: [ + new Auth1_3(), + new Data1_1(this.modelIntrospectionSchemaAdapter), + new Geo1_1(), + new Storage1_2(), + new VersionContributor1_4(), + new Custom1_1(), + ], + [ClientConfigVersionOption.V1_4]: [ new Auth1_3(), new Data1_1(this.modelIntrospectionSchemaAdapter), diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index a778c66dcaa..61b13009411 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -473,6 +473,51 @@ export class DataClientConfigContributor implements ClientConfigContributor { * Transformer for Geo segment of ClientConfig (V1.1 or later) */ export class GeoClientConfigContributor implements ClientConfigContributor { + contribute = ({ + [geoOutputKey]: geoOutput, + }: UnifiedBackendOutput): Partial | Record => { + if (geoOutput === undefined) { + return {}; + } + + const config: Partial = {}; + + config.geo = { + aws_region: geoOutput.payload.geoRegion, + }; + + let geofenceCollectionsObj; + + if (geoOutput.payload.geofenceCollections) { + const firstParse = JSON.parse( + JSON.parse(geoOutput.payload.geofenceCollections), + ); + + if ( + firstParse && + typeof firstParse === 'object' && + !Array.isArray(firstParse) && + firstParse.default + ) { + geofenceCollectionsObj = firstParse; + } + + if (geofenceCollectionsObj && geofenceCollectionsObj.default) { + config.geo!.geofence_collections = { + default: geofenceCollectionsObj.default, + items: geofenceCollectionsObj.items || [], + }; + } + } + + return config; + }; +} + +/** + * Transformer for Geo segment of ClientConfig (V1.1 or later) + */ +export class GeoClientConfigContributorV1 implements ClientConfigContributor { contribute = ({ [geoOutputKey]: geoOutput, }: UnifiedBackendOutput): Partial | Record => { diff --git a/packages/client-config/src/client-config-schema/client_config_v1.4.ts b/packages/client-config/src/client-config-schema/client_config_v1.4.ts index e84598d88c6..d44b7b52b38 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.4.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.4.ts @@ -74,7 +74,7 @@ export type AmplifyStorageAccessRule = { resource?: AmplifyStorageAccessActions[]; }; /** - * run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated + * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated * * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema * via the `definition` "amplify_storage_access_actions". diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts new file mode 100644 index 00000000000..b1ba572da25 --- /dev/null +++ b/packages/client-config/src/client-config-schema/client_config_v1.5.ts @@ -0,0 +1,337 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_cognito_standard_attributes". + */ +export type AmazonCognitoStandardAttributes = + | 'address' + | 'birthdate' + | 'email' + | 'family_name' + | 'gender' + | 'given_name' + | 'locale' + | 'middle_name' + | 'name' + | 'nickname' + | 'phone_number' + | 'picture' + | 'preferred_username' + | 'profile' + | 'sub' + | 'updated_at' + | 'website' + | 'zoneinfo'; +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "aws_region". + */ +export type AwsRegion = string; +/** + * List of supported auth types for AWS AppSync + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "aws_appsync_authorization_type". + */ +export type AwsAppsyncAuthorizationType = + | 'AMAZON_COGNITO_USER_POOLS' + | 'API_KEY' + | 'AWS_IAM' + | 'AWS_LAMBDA' + | 'OPENID_CONNECT'; +/** + * supported channels for Amazon Pinpoint + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_pinpoint_channels". + */ +export type AmazonPinpointChannels = + | 'IN_APP_MESSAGING' + | 'FCM' + | 'APNS' + | 'EMAIL' + | 'SMS'; +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_access_rule". + */ +export type AmplifyStorageAccessRule = { + guest?: AmplifyStorageAccessActions[]; + authenticated?: AmplifyStorageAccessActions[]; + [groups: `group${string}`]: AmplifyStorageAccessActions[]; + [entity: `entity${string}`]: AmplifyStorageAccessActions[]; + resource?: AmplifyStorageAccessActions[]; +}; +/** + * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_access_actions". + */ +export type AmplifyStorageAccessActions = + | 'read' + | 'get' + | 'list' + | 'write' + | 'delete'; + +/** + * Config format for Amplify Gen 2 client libraries to communicate with backend services. + */ +export interface AWSAmplifyBackendOutputs { + /** + * JSON schema + */ + $schema?: string; + /** + * Version of this schema + */ + version: '1.4'; + /** + * Outputs manually specified by developers for use with frontend library + */ + analytics?: { + amazon_pinpoint?: { + /** + * AWS Region of Amazon Pinpoint resources + */ + aws_region: string; + app_id: string; + }; + }; + /** + * Outputs generated from defineAuth + */ + auth?: { + /** + * AWS Region of Amazon Cognito resources + */ + aws_region: string; + /** + * Cognito User Pool ID + */ + user_pool_id: string; + /** + * Cognito User Pool Client ID + */ + user_pool_client_id: string; + /** + * Cognito Identity Pool ID + */ + identity_pool_id?: string; + /** + * Cognito User Pool password policy + */ + password_policy?: { + min_length: number; + require_numbers: boolean; + require_lowercase: boolean; + require_uppercase: boolean; + require_symbols: boolean; + }; + oauth?: { + /** + * Identity providers set on Cognito User Pool + * + * @minItems 0 + */ + identity_providers: ( + | 'GOOGLE' + | 'FACEBOOK' + | 'LOGIN_WITH_AMAZON' + | 'SIGN_IN_WITH_APPLE' + )[]; + /** + * Domain used for identity providers + */ + domain: string; + /** + * @minItems 0 + */ + scopes: string[]; + /** + * URIs used to redirect after signing in using an identity provider + * + * @minItems 1 + */ + redirect_sign_in_uri: [string, ...string[]]; + /** + * URIs used to redirect after signing out + * + * @minItems 1 + */ + redirect_sign_out_uri: [string, ...string[]]; + response_type: 'code' | 'token'; + }; + /** + * Cognito User Pool standard attributes required for signup + * + * @minItems 0 + */ + standard_required_attributes?: AmazonCognitoStandardAttributes[]; + /** + * Cognito User Pool username attributes + * + * @minItems 1 + */ + username_attributes?: [ + 'email' | 'phone_number' | 'username', + ...('email' | 'phone_number' | 'username')[], + ]; + user_verification_types?: ('email' | 'phone_number')[]; + unauthenticated_identities_enabled?: boolean; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; + groups?: { + [k: string]: AmplifyUserGroupConfig; + }[]; + }; + /** + * Outputs generated from defineData + */ + data?: { + aws_region: AwsRegion; + /** + * AppSync endpoint URL + */ + url: string; + /** + * generated model introspection schema for use with generateClient + */ + model_introspection?: { + [k: string]: unknown; + }; + api_key?: string; + default_authorization_type: AwsAppsyncAuthorizationType; + authorization_types: AwsAppsyncAuthorizationType[]; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + geo?: { + /** + * AWS Region of Amazon Location Service resources + */ + aws_region: string; + /** + * Maps from Amazon Location Service + */ + maps?: { + /** + * @minItems 1 + */ + keys?: [ + { + name?: string; + api_key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + api_key?: string; + [k: string]: unknown; + }[], + ]; + required?: ['keys', 'default']; + }; + /** + * Location search (search by places, addresses, coordinates) + */ + search_indices?: { + /** + * @minItems 1 + */ + keys?: [ + { + name?: string; + api_key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + api_key?: string; + [k: string]: unknown; + }[], + ]; + required?: ['keys', 'default']; + }; + /** + * Geofencing (visualize virtual perimeters) + */ + geofence_collections?: { + /** + * @minItems 1 + */ + items: [string, ...string[]]; + default: string; + }; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + notifications?: { + aws_region: AwsRegion; + amazon_pinpoint_app_id: string; + /** + * @minItems 1 + */ + channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; + }; + /** + * Outputs generated from defineStorage + */ + storage?: { + aws_region: AwsRegion; + bucket_name: string; + buckets?: AmplifyStorageBucket[]; + }; + /** + * Outputs generated from backend.addOutput({ custom: }) + */ + custom?: { + [k: string]: unknown; + }; +} +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_user_group_config". + */ +export interface AmplifyUserGroupConfig { + precedence?: number; +} +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amplify_storage_bucket". + */ +export interface AmplifyStorageBucket { + name: string; + bucket_name: string; + aws_region: string; + paths?: { + [k: string]: AmplifyStorageAccessRule; + }; +} +/** + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_location_service_config". + */ +export interface AmazonLocationServiceConfig { + /** + * Map style + */ + style?: string; +} diff --git a/packages/client-config/src/client-config-schema/schema_v1.5.json b/packages/client-config/src/client-config-schema/schema_v1.5.json new file mode 100644 index 00000000000..01e546605ec --- /dev/null +++ b/packages/client-config/src/client-config-schema/schema_v1.5.json @@ -0,0 +1,480 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amplify.aws/2024-02/outputs-schema.json", + "title": "AWS Amplify Backend Outputs", + "description": "Config format for Amplify Gen 2 client libraries to communicate with backend services.", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "description": "JSON schema", + "type": "string" + }, + "version": { + "description": "Version of this schema", + "const": "1.4" + }, + "analytics": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "amazon_pinpoint": { + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Pinpoint resources", + "$ref": "#/$defs/aws_region" + }, + "app_id": { + "type": "string" + } + }, + "required": ["aws_region", "app_id"] + } + } + }, + "auth": { + "description": "Outputs generated from defineAuth", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Cognito resources", + "$ref": "#/$defs/aws_region" + }, + "user_pool_id": { + "description": "Cognito User Pool ID", + "type": "string" + }, + "user_pool_client_id": { + "description": "Cognito User Pool Client ID", + "type": "string" + }, + "identity_pool_id": { + "description": "Cognito Identity Pool ID", + "type": "string" + }, + "password_policy": { + "description": "Cognito User Pool password policy", + "type": "object", + "additionalProperties": false, + "properties": { + "min_length": { + "type": "integer", + "minimum": 6, + "maximum": 99 + }, + "require_numbers": { + "type": "boolean" + }, + "require_lowercase": { + "type": "boolean" + }, + "require_uppercase": { + "type": "boolean" + }, + "require_symbols": { + "type": "boolean" + } + }, + "required": [ + "min_length", + "require_numbers", + "require_lowercase", + "require_uppercase", + "require_symbols" + ] + }, + "oauth": { + "type": "object", + "additionalProperties": false, + "properties": { + "identity_providers": { + "description": "Identity providers set on Cognito User Pool", + "type": "array", + "items": { + "type": "string", + "enum": [ + "GOOGLE", + "FACEBOOK", + "LOGIN_WITH_AMAZON", + "SIGN_IN_WITH_APPLE" + ] + }, + "minItems": 0, + "uniqueItems": true + }, + "domain": { + "description": "Domain used for identity providers", + "type": "string" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "uniqueItems": true + }, + "redirect_sign_in_uri": { + "description": "URIs used to redirect after signing in using an identity provider", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "redirect_sign_out_uri": { + "description": "URIs used to redirect after signing out", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "response_type": { + "type": "string", + "enum": ["code", "token"] + } + }, + "required": [ + "identity_providers", + "domain", + "scopes", + "redirect_sign_in_uri", + "redirect_sign_out_uri", + "response_type" + ] + }, + "standard_required_attributes": { + "description": "Cognito User Pool standard attributes required for signup", + "type": "array", + "items": { + "$ref": "#/$defs/amazon_cognito_standard_attributes" + }, + "minItems": 0, + "uniqueItems": true + }, + "username_attributes": { + "description": "Cognito User Pool username attributes", + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number", "username"] + }, + "minItems": 1, + "uniqueItems": true + }, + "user_verification_types": { + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number"] + } + }, + "unauthenticated_identities_enabled": { + "type": "boolean", + "default": true + }, + "mfa_configuration": { + "type": "string", + "enum": ["NONE", "OPTIONAL", "REQUIRED"] + }, + "mfa_methods": { + "type": "array", + "items": { + "enum": ["SMS", "TOTP"] + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "propertyNames": { + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_user_group_config" + } + } + } + } + }, + "required": ["aws_region", "user_pool_id", "user_pool_client_id"] + }, + "data": { + "description": "Outputs generated from defineData", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "url": { + "description": "AppSync endpoint URL", + "type": "string" + }, + "model_introspection": { + "description": "generated model introspection schema for use with generateClient", + "type": "object" + }, + "api_key": { + "type": "string" + }, + "default_authorization_type": { + "$ref": "#/$defs/aws_appsync_authorization_type" + }, + "authorization_types": { + "type": "array", + "items": { + "$ref": "#/$defs/aws_appsync_authorization_type" + } + } + }, + "required": [ + "aws_region", + "url", + "default_authorization_type", + "authorization_types" + ] + }, + "geo": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Location Service resources", + "$ref": "#/$defs/aws_region" + }, + "maps": { + "description": "Maps from Amazon Location Service", + "type": "object", + "additionalProperties": false, + "properties": { + "keys": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Actual map name", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "api_key": { + "type": "string" + } + } + }, + "default": { + "type": "string" + } + }, + "required": ["keys", "default"] + } + }, + "search_indices": { + "description": "Location search (search by places, addresses, coordinates)", + "type": "object", + "additionalProperties": false, + "properties": { + "keys": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Actual index name", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "api_key": { + "type": "string" + } + } + }, + "default": { + "type": "string" + } + }, + "required": ["keys", "default"] + } + }, + "geofence_collections": { + "description": "Geofencing (visualize virtual perimeters)", + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Geofence name", + "type": "string" + } + }, + "default": { + "type": "string" + } + }, + "required": ["items", "default"] + } + }, + "required": ["aws_region"] + }, + "notifications": { + "type": "object", + "description": "Outputs manually specified by developers for use with frontend library", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "amazon_pinpoint_app_id": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/$defs/amazon_pinpoint_channels" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["aws_region", "amazon_pinpoint_app_id", "channels"] + }, + "storage": { + "type": "object", + "description": "Outputs generated from defineStorage", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "bucket_name": { + "type": "string" + }, + "buckets": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_bucket" + } + } + }, + "required": ["aws_region", "bucket_name"] + }, + "custom": { + "description": "Outputs generated from backend.addOutput({ custom: })", + "type": "object" + } + }, + "required": ["version"], + "$defs": { + "amplify_storage_access_actions": { + "description": "run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated", + "type": "string", + "enum": ["read", "get", "list", "write", "delete"] + }, + "amplify_storage_access_rule": { + "tsType": "{ guest?: AmplifyStorageAccessActions[]; authenticated?: AmplifyStorageAccessActions[]; [groups: `group${string}`]: AmplifyStorageAccessActions[]; [entity: `entity${string}`]: AmplifyStorageAccessActions[]; resource?: AmplifyStorageAccessActions[]; }" + }, + "amplify_storage_bucket": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "aws_region": { + "type": "string" + }, + "paths": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_storage_access_rule" + } + } + } + }, + "required": ["bucket_name", "aws_region", "name"] + }, + "aws_region": { + "type": "string" + }, + "amazon_cognito_standard_attributes": { + "description": "Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html", + "type": "string", + "enum": [ + "address", + "birthdate", + "email", + "family_name", + "gender", + "given_name", + "locale", + "middle_name", + "name", + "nickname", + "phone_number", + "picture", + "preferred_username", + "profile", + "sub", + "updated_at", + "website", + "zoneinfo" + ] + }, + "aws_appsync_authorization_type": { + "description": "List of supported auth types for AWS AppSync", + "type": "string", + "enum": [ + "AMAZON_COGNITO_USER_POOLS", + "API_KEY", + "AWS_IAM", + "AWS_LAMBDA", + "OPENID_CONNECT" + ] + }, + "amazon_location_service_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "style": { + "description": "Map style", + "type": "string" + } + } + }, + "amazon_pinpoint_channels": { + "description": "supported channels for Amazon Pinpoint", + "type": "string", + "enum": ["IN_APP_MESSAGING", "FCM", "APNS", "EMAIL", "SMS"] + }, + "amplify_user_group_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "precedence": { + "type": "integer" + } + } + } + } +} diff --git a/packages/client-config/src/client-config-types/client_config.ts b/packages/client-config/src/client-config-types/client_config.ts index c642485d0a2..3fc083a00fa 100644 --- a/packages/client-config/src/client-config-types/client_config.ts +++ b/packages/client-config/src/client-config-types/client_config.ts @@ -14,6 +14,7 @@ import * as clientConfigTypesV1_1 from '../client-config-schema/client_config_v1 import * as clientConfigTypesV1_2 from '../client-config-schema/client_config_v1.2.js'; import * as clientConfigTypesV1_3 from '../client-config-schema/client_config_v1.3.js'; import * as clientConfigTypesV1_4 from '../client-config-schema/client_config_v1.4.js'; +// import * as clientConfigTypesV1_5 from '../client-config-schema/client_config_v1.5.js'; /* eslint-enable @typescript-eslint/naming-convention */ /** @@ -36,6 +37,7 @@ export type ClientConfigLegacy = Partial< * ClientConfig = clientConfigTypesV1.AWSAmplifyBackendOutputs | clientConfigTypesV2.AWSAmplifyBackendOutputs; */ export type ClientConfig = + // | clientConfigTypesV1_5.AWSAmplifyBackendOutputs | clientConfigTypesV1_4.AWSAmplifyBackendOutputs | clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs @@ -48,6 +50,7 @@ export { clientConfigTypesV1_2, clientConfigTypesV1_3, clientConfigTypesV1_4, + // clientConfigTypesV1_5, }; export enum ClientConfigVersionOption { @@ -57,6 +60,7 @@ export enum ClientConfigVersionOption { V1_2 = '1.2', V1_3 = '1.3', V1_4 = '1.4', + V1_5 = '1.5', } export type ClientConfigVersion = `${ClientConfigVersionOption}`; @@ -64,6 +68,7 @@ export type ClientConfigVersion = `${ClientConfigVersionOption}`; // Client config version that is generated by default if customers didn't specify one export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = ClientConfigVersionOption.V1_4; +// ClientConfigVersionOption.V1_5 /** * Return type of `getClientConfig`. This types narrow the returned client config version @@ -76,7 +81,7 @@ export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = * ? clientConfigTypesV2.AWSAmplifyBackendOutputs * : never; */ -export type ClientConfigVersionTemplateType = T extends '1.4' +export type ClientConfigVersionTemplateType = T extends '1.4' /* '1.5' */ ? clientConfigTypesV1_4.AWSAmplifyBackendOutputs : T extends '1.3' ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs From a28a75d0082f9a57eaa60a12bc7f23e22a25dc96 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 10:52:24 -0700 Subject: [PATCH 59/93] updating API --- packages/backend-geo/API.md | 20 ++++++++++++++++++-- packages/client-config/API.md | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 28b82fca248..73eb2d08d1b 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -12,14 +12,17 @@ import { ApiKeyProps } from '@aws-cdk/aws-location-alpha'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; +import { Construct } from 'constructs'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { Policy } from 'aws-cdk-lib/aws-iam'; +import { Resource } from 'aws-cdk-lib'; import { ResourceAccessAcceptor } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; import { StackProvider } from '@aws-amplify/plugin-types'; // @public @@ -41,7 +44,7 @@ export type AmplifyMapFactoryProps = Omit & { +export type AmplifyMapProps = Omit & { apiKeyProps?: GeoApiKeyProps; }; @@ -51,7 +54,7 @@ export type AmplifyPlaceFactoryProps = Omit & { +export type AmplifyPlaceProps = Omit & { apiKeyProps?: GeoApiKeyProps; }; @@ -108,6 +111,13 @@ export type GeoApiKeyProps = Omit) => GeoAccessDefinition[]; +// Warning: (ae-forgotten-export) The symbol "AmplifyMap" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "AmplifyPlace" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "AmplifyCollection" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; + // @public (undocumented) export type GeoResourceType = 'map' | 'place' | 'collection'; @@ -134,6 +144,12 @@ export type PlaceResources = { // @public (undocumented) export const resourceActionRecord: Record; +// @public (undocumented) +export type ResourceOutputs = { + name: string; + apiKey?: string; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 2c1f6d22c9e..24aa2f63ddb 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -761,7 +761,9 @@ export enum ClientConfigVersionOption { // (undocumented) V1_3 = "1.3", // (undocumented) - V1_4 = "1.4" + V1_4 = "1.4", + // (undocumented) + V1_5 = "1.5" } // @public From cd472834a3d80c6561adeab0b9d89aba7ac76dc0 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 10:55:33 -0700 Subject: [PATCH 60/93] fixing API --- packages/backend-geo/API.md | 10 ---------- packages/backend-geo/src/geo_outputs_aspect.ts | 4 +++- packages/backend-geo/src/types.ts | 5 ----- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 73eb2d08d1b..68e512c7dff 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -12,17 +12,14 @@ import { ApiKeyProps } from '@aws-cdk/aws-location-alpha'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; -import { Construct } from 'constructs'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { Policy } from 'aws-cdk-lib/aws-iam'; -import { Resource } from 'aws-cdk-lib'; import { ResourceAccessAcceptor } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; -import { Stack } from 'aws-cdk-lib'; import { StackProvider } from '@aws-amplify/plugin-types'; // @public @@ -111,13 +108,6 @@ export type GeoApiKeyProps = Omit) => GeoAccessDefinition[]; -// Warning: (ae-forgotten-export) The symbol "AmplifyMap" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "AmplifyPlace" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "AmplifyCollection" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; - // @public (undocumented) export type GeoResourceType = 'map' | 'place' | 'collection'; diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 1460717ded6..933f1363287 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -6,9 +6,11 @@ import { AmplifyPlace } from './place_resource.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; -import { GeoResource, ResourceOutputs } from './types.js'; +import { ResourceOutputs } from './types.js'; import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; +export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; + /** * Aspect Implementation for Geo Resources */ diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 9c72a9f6fce..d71417f14b1 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -15,9 +15,6 @@ import { import { CfnAPIKey, CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; -import { AmplifyMap } from './map_resource.js'; -import { AmplifyPlace } from './place_resource.js'; -import { AmplifyCollection } from './collection_construct.js'; // ----------------------------------- factory properties ---------------------------------------------- @@ -194,8 +191,6 @@ export const resourceActionRecord: Record = { collection: ['create', 'read', 'update', 'delete', 'list'], }; -export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; - export type GeoApiActionType = AllowMapsAction | AllowPlacesAction; export type GeoResourceType = 'map' | 'place' | 'collection'; From 60eea0db2b7339157ba28dfb61a1b4407553ba8a Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 11:06:25 -0700 Subject: [PATCH 61/93] fixing unit test issues after fixes v3 --- packages/backend-geo/src/geo_outputs_aspect.test.ts | 9 ++++----- packages/backend-geo/src/map_factory.test.ts | 4 ++-- packages/backend-geo/src/place_factory.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index a512260ff80..bf59bc23d73 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -128,10 +128,9 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(newNode); }, new AmplifyUserError('NoDefaultCollectionError', { - message: - 'No instances of geofence collections have been marked as default.', + message: 'No default geofence collection set in the Amplify project', resolution: - 'Add `isDefault: true` to one of the `defineCollection` calls.', + 'Add `isDefault: true` to one of the `defineCollection` calls in your Amplify project', }), ); }); @@ -154,9 +153,9 @@ void describe('AmplifyGeoOutputsAspect', () => { }, new AmplifyUserError('MultipleDefaultCollectionError', { message: - 'Multiple instances of geofence collections have been marked as default.', + 'More than one default geofence collection set in the Amplify project', resolution: - 'Remove `isDefault: true` from all but one `defineCollection` call.', + 'Remove `isDefault: true` from all but one `defineCollection` calls except for one in your Amplify project', }), ); }); diff --git a/packages/backend-geo/src/map_factory.test.ts b/packages/backend-geo/src/map_factory.test.ts index de92746c905..7e5f7b4d6f2 100644 --- a/packages/backend-geo/src/map_factory.test.ts +++ b/packages/backend-geo/src/map_factory.test.ts @@ -117,8 +117,8 @@ void describe('AmplifyMapFactory', () => { }), new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `defineMap` calls not permitted within an Amplify backend', - resolution: 'Maintain one `defineMap` call', + 'Multiple `defineMap` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `defineMap` call', }), ); }); diff --git a/packages/backend-geo/src/place_factory.test.ts b/packages/backend-geo/src/place_factory.test.ts index dc81c67df90..d1cfe66883d 100644 --- a/packages/backend-geo/src/place_factory.test.ts +++ b/packages/backend-geo/src/place_factory.test.ts @@ -118,8 +118,8 @@ void describe('AmplifyPlaceFactory', () => { }), new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `definePlace` calls not permitted within an Amplify backend', - resolution: 'Maintain one `definePlace` call', + 'Multiple `definePlace` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `definePlace` call', }), ); }); From ac1c79780e4bce276ec7f54a0123d8e5ac8a73dc Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 15:09:41 -0700 Subject: [PATCH 62/93] adding unit testing for api key support(all except client config) --- .../backend-geo/src/access_builder.test.ts | 23 ++ .../src/collection_construct.test.ts | 2 +- .../src/collection_factory.test.ts | 1 + .../src/geo_outputs_aspect.test.ts | 214 +++++++++++++++--- .../backend-geo/src/geo_outputs_aspect.ts | 141 +++++------- packages/backend-geo/src/map_factory.test.ts | 10 +- packages/backend-geo/src/map_resource.test.ts | 1 - .../backend-geo/src/place_factory.test.ts | 10 +- .../backend-geo/src/place_resource.test.ts | 1 - packages/backend-geo/src/types.ts | 2 +- 10 files changed, 273 insertions(+), 132 deletions(-) diff --git a/packages/backend-geo/src/access_builder.test.ts b/packages/backend-geo/src/access_builder.test.ts index 0a8489d9898..c2a3f984b8e 100644 --- a/packages/backend-geo/src/access_builder.test.ts +++ b/packages/backend-geo/src/access_builder.test.ts @@ -192,6 +192,29 @@ void describe('GeoAccessBuilder', () => { ); }); + void it('builds geo access definition for api keys', () => { + const accessDefinition = roleAccessBuilder.apiKey.to(['search', 'geocode']); + + assert.deepStrictEqual(accessDefinition.actions, ['search', 'geocode']); + assert.deepStrictEqual( + accessDefinition.getAccessAcceptors.map((getAccessAcceptor) => + getAccessAcceptor(mockGetInstanceProps), + ), + [], + ); + + assert.equal(accessDefinition.uniqueDefinitionValidators.length, 1); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].uniqueRoleToken, + 'api key', + ); + assert.equal( + accessDefinition.uniqueDefinitionValidators[0].validationErrorOptions + .message, + 'Access definition for api key specified multiple times.', + ); + }); + void it('throws error when auth construct factory is not found', () => { const getConstructFactoryMockReturnsNull = mock.fn(() => null); const stubGetInstancePropsWithNullFactory: ConstructFactoryGetInstanceProps = diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts index 3d30b3a1c77..7e0cf5b75b6 100644 --- a/packages/backend-geo/src/collection_construct.test.ts +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -88,7 +88,7 @@ void describe('AmplifyCollection', () => { const template = Template.fromStack(stack); assert.equal( JSON.parse(template.toJSON().Description).stackType, - 'geo-GeofenceCollection', + 'geo-Location', ); }); diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts index d718505caf7..2b778243300 100644 --- a/packages/backend-geo/src/collection_factory.test.ts +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -131,6 +131,7 @@ void describe('AmplifyCollectionFactory', () => { geofenceCollectionName: 'customCollection', description: 'Custom test collection', }, + access: (allow) => [allow.apiKey.to(['create'])], }); const collectionConstruct = diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index a512260ff80..763c7618c4d 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -8,6 +8,7 @@ import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { App, Stack } from 'aws-cdk-lib'; import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { AllowMapsAction } from '@aws-cdk/aws-location-alpha'; void describe('AmplifyGeoOutputsAspect', () => { let app: App; @@ -42,47 +43,59 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); - void it('only backend output entry invoked with AmplifyMap node', () => { - const mapNode = new AmplifyMap(stack, 'testMap', { - name: 'testMapResourceName', + void it('output storage invoked with AmplifyPlace node', () => { + const placeNode = new AmplifyPlace(stack, 'testPlace', { + name: 'testPlaceResourceName', }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(mapNode); + aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); - void it('output storage invoked with AmplifyPlace node', () => { - const placeNode = new AmplifyPlace(stack, 'testPlace', { - name: 'testPlaceResourceName', + void it('output storage invoked with AmplifyCollection node', () => { + const collectionNode = new AmplifyCollection(stack, 'testCollection', { + name: 'testCollectionName', + collectionProps: {}, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(placeNode); + aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); - void it('only backend output entry invoked with AmplifyPlace node', () => { - const placeNode = new AmplifyPlace(stack, 'testPlace', { - name: 'testPlaceResourceName', + void it('output entry called once with multiple maps created', () => { + new AmplifyMap(stack, 'testMap_1', { + name: 'testMap1', + isDefault: true, + }); // set as default map + const mapNode = new AmplifyMap(stack, 'testMap_2', { + name: 'testMap2', }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(placeNode); + aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); - void it('both backend output entry and append list invoked with AmplifyCollection node', () => { - const collectionNode = new AmplifyCollection(stack, 'testCollection', { - name: 'testCollectionName', - collectionProps: {}, + void it('output entry called once with multiple places created', () => { + new AmplifyPlace(stack, 'testPlace_1', { + name: 'testPlace1', + isDefault: true, + }); // set as default place + const placeNode = new AmplifyPlace(stack, 'testPlace_2', { + name: 'testPlace2', }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(collectionNode); + aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); @@ -94,16 +107,13 @@ void describe('AmplifyGeoOutputsAspect', () => { collectionProps: {}, isDefault: true, }); // set as default collection - new AmplifyCollection(stack, 'testCollection_2', { + const collectionNode = new AmplifyCollection(stack, 'testCollection_2', { name: 'testCollection2', collectionProps: {}, }); - const mapNode = new AmplifyMap(stack, 'testMap', { - name: 'testMapResourceName', - }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(mapNode); + aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); @@ -128,10 +138,8 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(newNode); }, new AmplifyUserError('NoDefaultCollectionError', { - message: - 'No instances of geofence collections have been marked as default.', - resolution: - 'Add `isDefault: true` to one of the `defineCollection` calls.', + message: `No default collection set in the Amplify project`, + resolution: `Add 'isDefault: true' to one of the 'defineCollection' calls in your Amplify project`, }), ); }); @@ -153,10 +161,90 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); }, new AmplifyUserError('MultipleDefaultCollectionError', { - message: - 'Multiple instances of geofence collections have been marked as default.', - resolution: - 'Remove `isDefault: true` from all but one `defineCollection` call.', + message: `More than one default collection set in the Amplify project`, + resolution: `Remove 'isDefault: true' from all 'defineCollection' calls except for one in your Amplify project`, + }), + ); + }); + + void it('throws if no map set to default', () => { + const noDuplicateStack = new Stack(app, 'noDuplicateStack'); + const newNode = new AmplifyMap(noDuplicateStack, 'testMap', { + name: 'testMap', + }); + new AmplifyMap(noDuplicateStack, 'testMap2', { + name: 'testDuplicateMap2', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + aspect.visit(newNode); + }, + new AmplifyUserError('NoDefaultMapError', { + message: `No default map set in the Amplify project`, + resolution: `Add 'isDefault: true' to one of the 'defineMap' calls in your Amplify project`, + }), + ); + }); + + void it('throws if multiple default maps', () => { + const node = new AmplifyMap(stack, 'testMapDefault', { + name: 'defaultMap', + isDefault: true, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + new AmplifyMap(stack, 'anotherDefaultMap', { + name: 'anotherDefaultMap', + isDefault: true, + }); + aspect.visit(node); + }, + new AmplifyUserError('MultipleDefaultMapError', { + message: `More than one default map set in the Amplify project`, + resolution: `Remove 'isDefault: true' from all 'defineMap' calls except for one in your Amplify project`, + }), + ); + }); + + void it('throws if no place set to default', () => { + const noDuplicateStack = new Stack(app, 'noDuplicateStack'); + const newNode = new AmplifyPlace(noDuplicateStack, 'testPlace', { + name: 'testPlace', + }); + new AmplifyPlace(noDuplicateStack, 'testPlace2', { + name: 'testDuplicatePlace2', + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + aspect.visit(newNode); + }, + new AmplifyUserError('NoDefaultPlaceError', { + message: `No default place set in the Amplify project`, + resolution: `Add 'isDefault: true' to one of the 'definePlace' calls in your Amplify project`, + }), + ); + }); + + void it('throws if multiple default places', () => { + const node = new AmplifyPlace(stack, 'testPlaceDefault', { + name: 'defaultPlace', + isDefault: true, + }); + aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); + assert.throws( + () => { + new AmplifyPlace(stack, 'anotherDefaultPlace', { + name: 'anotherDefaultPlace', + isDefault: true, + }); + aspect.visit(node); + }, + new AmplifyUserError('MultipleDefaultPlaceError', { + message: `More than one default place set in the Amplify project`, + resolution: `Remove 'isDefault: true' from all 'definePlace' calls except for one in your Amplify project`, }), ); }); @@ -171,7 +259,7 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 0); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( @@ -192,7 +280,12 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('output with multiple collections and all resources', () => { const node = new AmplifyMap(stack, 'mapResource', { name: 'testMapResource', + apiKeyProps: { + apiKeyName: 'myKey', + }, }); + + node.generateApiKey([AllowMapsAction.GET_STATIC_MAP]); new AmplifyPlace(stack, 'placeResource', { name: 'testPlaceIndex' }); new AmplifyCollection(stack, 'defaultCollection', { name: 'default_collection', @@ -208,13 +301,62 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); + assert.equal(appendToBackendOutputListMock.mock.callCount(), 3); - assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( - addBackendOutputEntryMock.mock.calls[0].arguments[0], + appendToBackendOutputListMock.mock.calls[0].arguments.length, + 2, + ); + assert.equal( + appendToBackendOutputListMock.mock.calls[0].arguments[0], 'AWS::Amplify::Geo', ); + + assert.equal( + appendToBackendOutputListMock.mock.calls[1].arguments.length, + 2, + ); + + /** + { + version: '1', + payload: { + map: JSON.stringify({ + default: "testMapResource", + items: [{ + name: "testMapResource", + apiKey: "TOKEN_STRING" + }] + }) + } + } + */ + assert.ok( + JSON.parse( + appendToBackendOutputListMock.mock.calls[1].arguments[1].payload.map, + ).items[0].apiKey.includes('TOKEN'), + ); + + assert.equal( + appendToBackendOutputListMock.mock.calls[2].arguments.length, + 2, + ); + assert.deepStrictEqual( + appendToBackendOutputListMock.mock.calls[2].arguments[1], + { + version: '1', + payload: { + searchIndices: JSON.stringify({ + default: 'testPlaceIndex', + items: [ + { + name: 'testPlaceIndex', + }, + ], + }), + }, + }, + ); }); }); }); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 933f1363287..5f8ac90c593 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -6,8 +6,7 @@ import { AmplifyPlace } from './place_resource.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; -import { ResourceOutputs } from './types.js'; -import { CfnAPIKey } from 'aws-cdk-lib/aws-location'; +import { GeoResourceType, ResourceOutputs } from './types.js'; export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; @@ -66,7 +65,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { placeInstances.length > 0 || collectionInstances.length > 0 ) { - this.addBackendOutput( + this.configureBackendOutputs( collectionInstances, mapInstances, placeInstances, @@ -79,10 +78,13 @@ export class AmplifyGeoOutputsAspect implements IAspect { private validateDefault = ( nodes: GeoResource[], currentNode: GeoResource, + resourceType: GeoResourceType, ) => { - const collectionCount = nodes.length; + const resourceCount = nodes.length; let defaultNode: GeoResource | undefined = undefined; + const defineName = + resourceType.charAt(0).toUpperCase() + resourceType.slice(1); // go through all children and find the default (make duplicity check on defaults) nodes.forEach((instance) => { @@ -91,60 +93,27 @@ export class AmplifyGeoOutputsAspect implements IAspect { defaultNode = instance; } else if (instance.isDefault && defaultNode) { // if default exists and instance is default (throw multiple defaults error) - throw new AmplifyUserError('MultipleDefaultCollectionError', { - message: - 'More than one default geofence collection set in the Amplify project', - resolution: - 'Remove `isDefault: true` from all `defineCollection` calls except for one in your Amplify project', + throw new AmplifyUserError(`MultipleDefault${defineName}Error`, { + message: `More than one default ${resourceType} set in the Amplify project`, + resolution: `Remove 'isDefault: true' from all 'define${defineName}' calls except for one in your Amplify project`, }); } }); - if (!defaultNode && collectionCount === 1) { + if (!defaultNode && resourceCount === 1) { // if no defaults and only one construct, instance assumed to be default defaultNode = currentNode; - } else if (collectionCount > 1 && !defaultNode) { + } else if (resourceCount > 1 && !defaultNode) { // if multiple constructs with default collection, throw error - throw new AmplifyUserError('NoDefaultCollectionError', { - message: 'No default geofence collection set in the Amplify project', - resolution: - 'Add `isDefault: true` to one of the `defineCollection` calls in your Amplify project', + throw new AmplifyUserError(`NoDefault${defineName}Error`, { + message: `No default ${resourceType} set in the Amplify project`, + resolution: `Add 'isDefault: true' to one of the 'define${defineName}' calls in your Amplify project`, }); } return defaultNode; }; - private validateKeys = (nodes: GeoResource[]) => { - const apiKeys: CfnAPIKey[] = []; - // finding duplicate api keys (same actions) - nodes.forEach((node) => { - if ( - !(node instanceof AmplifyCollection) && - node.resources.cfnResources.cfnAPIKey - ) - apiKeys.push(node.resources.cfnResources.cfnAPIKey); - }); - - const hasDuplicates = apiKeys.some((key, index) => { - apiKeys - .slice(index + 1) - .some( - (secondKey) => - JSON.stringify(key.restrictions) === - JSON.stringify(secondKey.restrictions), - ); - }); - - if (hasDuplicates) - throw new AmplifyUserError('DuplicateAPIKeyError', { - message: - 'Multiple api keys with the same actions have been created for this resource.', - resolution: - 'Remove all api keys for this region with the same actions for this resource.', - }); - }; - /** * Function responsible for add all collection outputs (with defaults) * @param collections - all construct instances of Amplify Geo @@ -153,7 +122,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { * @param outputStorageStrategy - backend output schema of type GeoOutput * @param region - region of geo resources */ - private addBackendOutput( + private configureBackendOutputs( collections: AmplifyCollection[], maps: AmplifyMap[], places: AmplifyPlace[], @@ -163,19 +132,17 @@ export class AmplifyGeoOutputsAspect implements IAspect { const defaultCollection = this.validateDefault( collections, collections[0], + 'collection', ) as AmplifyCollection; - const defaultMap = this.validateDefault(maps, maps[0]) as AmplifyMap; - - this.validateKeys(maps); + const defaultMap = this.validateDefault(maps, maps[0], 'map') as AmplifyMap; const defaultPlace = this.validateDefault( places, places[0], + 'place', ) as AmplifyPlace; - this.validateKeys(places); - // Add the main geo output entry with aws_region (snake_case to match schema) outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { version: '1', @@ -206,40 +173,42 @@ export class AmplifyGeoOutputsAspect implements IAspect { ); // Add geofence_collections as a single entry with all collections - if (collections.length > 0 && defaultCollection) { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - geofenceCollections: JSON.stringify({ - // Changed from geofenceCollections to geofence_collections - default: - defaultCollection.resources.collection.geofenceCollectionName, - items: collectionNames, // Array of all collection names - }), - }, - }); - } - if (maps.length > 0 && defaultMap) { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - maps: JSON.stringify({ - default: defaultMap.name, - items: mapOutputs, - }), - }, - }); - } - if (places.length > 0 && defaultPlace) { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - searchIndices: JSON.stringify({ - default: defaultPlace.name, - items: placeOutputs, - }), - }, - }); - } + if (collections.length > 0 && defaultCollection) + this.addOutput( + outputStorageStrategy, + 'geofenceCollections', + defaultCollection.resources.collection.geofenceCollectionName, + collectionNames, + ); + + // Add maps as a single entry with all maps + if (maps.length > 0 && defaultMap) + this.addOutput(outputStorageStrategy, 'map', defaultMap.name, mapOutputs); + + // Add index as a single entry with all place indices + if (places.length > 0 && defaultPlace) + this.addOutput( + outputStorageStrategy, + 'searchIndices', + defaultPlace.name, + placeOutputs, + ); } + + private addOutput = ( + outputStorageStrategy: BackendOutputStorageStrategy, + resourceKey: string, + defaultResource: string, + items: (string | ResourceOutputs)[], + ) => { + outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { + version: '1', + payload: { + [resourceKey]: JSON.stringify({ + default: defaultResource, + items: items, + }), + }, + }); + }; } diff --git a/packages/backend-geo/src/map_factory.test.ts b/packages/backend-geo/src/map_factory.test.ts index de92746c905..8f7888731ec 100644 --- a/packages/backend-geo/src/map_factory.test.ts +++ b/packages/backend-geo/src/map_factory.test.ts @@ -44,6 +44,10 @@ void describe('AmplifyMapFactory', () => { mapFactory = defineMap({ name: 'testMap', + access: (allow) => [allow.apiKey.to(['get'])], + apiKeyProps: { + apiKeyName: 'testKey', + }, }); const stack = createStackAndSetContext(); @@ -117,8 +121,8 @@ void describe('AmplifyMapFactory', () => { }), new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `defineMap` calls not permitted within an Amplify backend', - resolution: 'Maintain one `defineMap` call', + 'Multiple `defineMap` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `defineMap` call', }), ); }); @@ -161,7 +165,7 @@ void describe('AmplifyMapFactory', () => { const mapConstruct = mapFactory.getInstance(getInstanceProps) as AmplifyMap; assert.equal(mapConstruct.name, 'testMap'); - assert.ok(mapConstruct.resources); + assert.equal(typeof mapConstruct.resources.apiKey?.apiKeyName, 'string'); // API Key defined }); void it('verifies stack property exists and is equal to map stack', () => { diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts index b2157a06346..5fe2ca05941 100644 --- a/packages/backend-geo/src/map_resource.test.ts +++ b/packages/backend-geo/src/map_resource.test.ts @@ -38,7 +38,6 @@ void describe('AmplifyMap', () => { }); assert.ok(map.resources); - assert.ok(Array.isArray(map.resources.policies)); assert.equal(typeof map.resources.region, 'string'); }); diff --git a/packages/backend-geo/src/place_factory.test.ts b/packages/backend-geo/src/place_factory.test.ts index dc81c67df90..1a236e518cb 100644 --- a/packages/backend-geo/src/place_factory.test.ts +++ b/packages/backend-geo/src/place_factory.test.ts @@ -44,6 +44,10 @@ void describe('AmplifyPlaceFactory', () => { placeFactory = definePlace({ name: 'testPlace', + access: (allow) => [allow.apiKey.to(['search', 'geocode'])], + apiKeyProps: { + apiKeyName: 'testKey', + }, }); const stack = createStackAndSetContext(); @@ -118,8 +122,8 @@ void describe('AmplifyPlaceFactory', () => { }), new AmplifyUserError('MultipleSingletonResourcesError', { message: - 'Multiple `definePlace` calls not permitted within an Amplify backend', - resolution: 'Maintain one `definePlace` call', + 'Multiple `definePlace` calls are not allowed within an Amplify backend', + resolution: 'Remove all but one `definePlace` call', }), ); }); @@ -166,7 +170,7 @@ void describe('AmplifyPlaceFactory', () => { ) as AmplifyPlace; assert.equal(placeConstruct.name, 'testPlace'); - assert.ok(placeConstruct.resources); + assert.equal(typeof placeConstruct.resources.apiKey?.apiKeyName, 'string'); // checking if API key exists }); void it('verifies stack property exists and is equal to place stack', () => { diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts index 00766cdc27f..f2d9ba7c985 100644 --- a/packages/backend-geo/src/place_resource.test.ts +++ b/packages/backend-geo/src/place_resource.test.ts @@ -38,7 +38,6 @@ void describe('AmplifyPlace', () => { }); assert.ok(place.resources); - assert.ok(Array.isArray(place.resources.policies)); assert.equal(typeof place.resources.region, 'string'); }); diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index d71417f14b1..f72c52f50a2 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -165,7 +165,7 @@ export type GeoAccessBuilder = { authenticated: GeoActionBuilder; guest: GeoActionBuilder; groups: (groupNames: string[]) => GeoActionBuilder; - apiKey?: GeoActionBuilder; + apiKey: GeoActionBuilder; }; export type GeoActionBuilder = { From 04511729cd755e0fa09f5146919e548ddc58941b Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 30 Jul 2025 15:10:31 -0700 Subject: [PATCH 63/93] updating API --- packages/backend-geo/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 68e512c7dff..eac59f56771 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -78,7 +78,7 @@ export type GeoAccessBuilder = { authenticated: GeoActionBuilder; guest: GeoActionBuilder; groups: (groupNames: string[]) => GeoActionBuilder; - apiKey?: GeoActionBuilder; + apiKey: GeoActionBuilder; }; // @public (undocumented) From 5effe4e4050d1c81d920d435fdcb7bb626f1664c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 10:01:20 -0700 Subject: [PATCH 64/93] new output schema to accomodate for api keys --- package-lock.json | 130 +++++++- .../engine/custom_outputs_accumulator.test.ts | 6 +- .../outputs/generate_outputs_command.test.ts | 10 +- .../commands/sandbox/sandbox_command.test.ts | 6 +- .../sandbox_event_handler_factory.test.ts | 6 +- .../generate_seed_policy_template.test.ts | 6 +- .../generate_seed_policy_template.ts | 2 +- packages/client-config/API.md | 285 ++++++++++++++---- packages/client-config/package.json | 1 + .../client_config_contributor_factory.ts | 9 +- .../client_config_contributor_v1.test.ts | 91 +++++- .../client_config_contributor_v1.ts | 64 ++-- .../client_config_v1.5.ts | 22 +- .../src/client-config-schema/schema_v1.5.json | 26 +- .../src/client-config-types/client_config.ts | 33 +- .../client_config_formatter_default.test.ts | 4 +- .../client_config_formatter_legacy.test.ts | 2 +- .../client_config_to_legacy_converter.test.ts | 30 +- .../client_config_to_legacy_converter.ts | 41 ++- .../client_config_writer.test.ts | 2 +- ...nerate_empty_client_config_to_file.test.ts | 6 +- .../generate_empty_client_config_to_file.ts | 2 +- .../unified_client_config_generator.test.ts | 199 +++++++++++- .../seed/src/auth-seed/config_reader.test.ts | 6 +- packages/seed/src/auth-seed/config_reader.ts | 2 +- 25 files changed, 786 insertions(+), 205 deletions(-) diff --git a/package-lock.json b/package-lock.json index eba5c161db1..44715fb35c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,6 @@ "workspaces": [ "packages/*" ], - "dependencies": { - "@aws-amplify/backend-output-schemas": "^1.7.0", - "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" - }, "devDependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", @@ -133,6 +128,41 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@apollo/client": { "version": "3.13.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", @@ -30943,6 +30973,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -41266,6 +41302,47 @@ "node": ">=16" } }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/json-schema-to-typescript/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -46028,6 +46105,48 @@ "node": ">=16" } }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -50007,6 +50126,7 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", + "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { diff --git a/packages/backend/src/engine/custom_outputs_accumulator.test.ts b/packages/backend/src/engine/custom_outputs_accumulator.test.ts index 5fa6f80be8c..60e89536d14 100644 --- a/packages/backend/src/engine/custom_outputs_accumulator.test.ts +++ b/packages/backend/src/engine/custom_outputs_accumulator.test.ts @@ -59,11 +59,11 @@ void describe('Custom outputs accumulator', () => { ); const configPart1: DeepPartialAmplifyGeneratedConfigs = { - version: '1.4', + version: '1.5', custom: { output1: 'val1' }, }; const configPart2: DeepPartialAmplifyGeneratedConfigs = { - version: '1.4', + version: '1.5', custom: { output2: 'val2' }, }; accumulator.addOutput(configPart1); @@ -115,7 +115,7 @@ void describe('Custom outputs accumulator', () => { assert.throws( () => - accumulator.addOutput({ version: '1.4', custom: { output1: 'val1' } }), + accumulator.addOutput({ version: '1.5', custom: { output1: 'val1' } }), (error: AmplifyUserError) => { assert.strictEqual( error.message, diff --git a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts index 387b52c139e..15f8fcf7f37 100644 --- a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts +++ b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts @@ -74,7 +74,7 @@ void describe('generate outputs command', () => { assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual( generateClientConfigMock.mock.calls[0].arguments[1], - '1.4', // default version + '1.5', // default version ); assert.deepEqual( generateClientConfigMock.mock.calls[0].arguments[2], @@ -95,7 +95,7 @@ void describe('generate outputs command', () => { namespace: 'app_id', type: 'branch', }, - '1.4', + '1.5', '/foo/bar', undefined, ], @@ -113,7 +113,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.4', + '1.5', '/foo/bar', undefined, ], @@ -131,7 +131,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.4', + '1.5', 'foo/bar', undefined, ], @@ -149,7 +149,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.4', + '1.5', 'foo/bar', ClientConfigFormat.DART, ], diff --git a/packages/cli/src/commands/sandbox/sandbox_command.test.ts b/packages/cli/src/commands/sandbox/sandbox_command.test.ts index 0619cfaca43..d907f4b2f78 100644 --- a/packages/cli/src/commands/sandbox/sandbox_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_command.test.ts @@ -328,15 +328,15 @@ void describe('sandbox command', () => { ); }); - void it('sandbox creates an empty client config file if one does not already exist for version 1.4', async (contextual) => { + void it('sandbox creates an empty client config file if one does not already exist for version 1.5', async (contextual) => { contextual.mock.method(fs, 'existsSync', () => false); const writeFileMock = contextual.mock.method(fsp, 'writeFile', () => true); - await commandRunner.runCommand('sandbox --outputs-version 1.4'); + await commandRunner.runCommand('sandbox --outputs-version 1.5'); assert.equal(sandboxStartMock.mock.callCount(), 1); assert.equal(writeFileMock.mock.callCount(), 1); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[1], - `{\n "version": "1.4"\n}`, + `{\n "version": "1.5"\n}`, ); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[0], diff --git a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts index 9748f9f248a..83595e67370 100644 --- a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts @@ -24,7 +24,7 @@ void describe('sandbox_event_handler_factory', () => { } as unknown as ClientConfigGeneratorAdapter; const clientConfigLifecycleHandler = new ClientConfigLifecycleHandler( clientConfigGeneratorAdapterMock, - '1.4', + '1.5', 'test-out', ClientConfigFormat.JSON, ); @@ -80,7 +80,7 @@ void describe('sandbox_event_handler_factory', () => { namespace: 'test', name: 'name', }, - '1.4', + '1.5', 'test-out', 'json', ]); @@ -192,7 +192,7 @@ void describe('sandbox_event_handler_factory', () => { namespace: 'test', name: 'name', }, - '1.4', + '1.5', 'test-out', 'json', ]); diff --git a/packages/cli/src/seed-policy-generation/generate_seed_policy_template.test.ts b/packages/cli/src/seed-policy-generation/generate_seed_policy_template.test.ts index b0bd075c00a..33be888b919 100644 --- a/packages/cli/src/seed-policy-generation/generate_seed_policy_template.test.ts +++ b/packages/cli/src/seed-policy-generation/generate_seed_policy_template.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, it, mock } from 'node:test'; import assert from 'assert'; import { BackendIdentifier } from '@aws-amplify/plugin-types'; -import { AWSAmplifyBackendOutputs } from '../../../client-config/src/client-config-schema/client_config_v1.4.js'; +import { AWSAmplifyBackendOutputs } from '../../../client-config/src/client-config-schema/client_config_v1.5.js'; import { generateSeedPolicyTemplate } from './generate_seed_policy_template.js'; import { generateClientConfig } from '@aws-amplify/client-config'; import { AmplifyUserError } from '@aws-amplify/platform-core'; @@ -33,7 +33,7 @@ const testBackendIdentifier: BackendIdentifier = { void describe('generate inline policy for seed', () => { const mockConfigGenerator = mock.fn(async () => Promise.resolve({ - version: '1.4', + version: '1.5', auth: { aws_region: testRegion, user_pool_id: testUserpoolId, @@ -106,7 +106,7 @@ void describe('generate inline policy for seed', () => { void it('throws error if there is no userpool attached to sandbox', async () => { mockConfigGenerator.mock.mockImplementationOnce(async () => Promise.resolve({ - version: '1.4', + version: '1.5', storage: { aws_region: testRegion, bucket_name: 'my-cool-bucket', diff --git a/packages/cli/src/seed-policy-generation/generate_seed_policy_template.ts b/packages/cli/src/seed-policy-generation/generate_seed_policy_template.ts index fc31a34ad34..ab6f7e51650 100644 --- a/packages/cli/src/seed-policy-generation/generate_seed_policy_template.ts +++ b/packages/cli/src/seed-policy-generation/generate_seed_policy_template.ts @@ -18,7 +18,7 @@ export const generateSeedPolicyTemplate = async ( stsClient = new STSClient(), ): Promise => { const seedPolicy = new PolicyDocument(); - const clientConfig = await generateClientConfiguration(backendId, '1.4'); + const clientConfig = await generateClientConfiguration(backendId, '1.5'); if (!clientConfig.auth) { throw new AmplifyUserError('MissingAuthError', { diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 24aa2f63ddb..904546b7e38 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -11,7 +11,7 @@ import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client' import { S3Client } from '@aws-sdk/client-s3'; // @public -type AmazonCognitoStandardAttributes = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; +type AmazonCognitoStandardAttributes = "address" | "birthdate" | "email" | "family_name" | "gender" | "given_name" | "locale" | "middle_name" | "name" | "nickname" | "phone_number" | "picture" | "preferred_username" | "profile" | "sub" | "updated_at" | "website" | "zoneinfo"; // @public type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; @@ -25,9 +25,11 @@ type AmazonCognitoStandardAttributes_4 = 'address' | 'birthdate' | 'email' | 'fa // @public type AmazonCognitoStandardAttributes_5 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; +// @public +type AmazonCognitoStandardAttributes_6 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; + // @public interface AmazonLocationServiceConfig { - name?: string; style?: string; } @@ -56,7 +58,13 @@ interface AmazonLocationServiceConfig_5 { } // @public -type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; +interface AmazonLocationServiceConfig_6 { + name?: string; + style?: string; +} + +// @public +type AmazonPinpointChannels = "IN_APP_MESSAGING" | "FCM" | "APNS" | "EMAIL" | "SMS"; // @public type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -71,14 +79,20 @@ type AmazonPinpointChannels_4 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | type AmazonPinpointChannels_5 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public -type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; +type AmazonPinpointChannels_6 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; -// @public (undocumented) +// @public +type AmplifyStorageAccessActions = "read" | "get" | "list" | "write" | "delete"; + +// @public type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; // @public (undocumented) type AmplifyStorageAccessActions_3 = 'read' | 'get' | 'list' | 'write' | 'delete'; +// @public (undocumented) +type AmplifyStorageAccessActions_4 = 'read' | 'get' | 'list' | 'write' | 'delete'; + // @public type AmplifyStorageAccessRule = { guest?: AmplifyStorageAccessActions[]; @@ -89,18 +103,13 @@ type AmplifyStorageAccessRule = { }; // @public -interface AmplifyStorageAccessRule_2 { - // (undocumented) - authenticated?: AmplifyStorageAccessActions_2[]; - // (undocumented) - entity?: AmplifyStorageAccessActions_2[]; - // (undocumented) - groups?: AmplifyStorageAccessActions_2[]; - // (undocumented) +type AmplifyStorageAccessRule_2 = { guest?: AmplifyStorageAccessActions_2[]; - // (undocumented) + authenticated?: AmplifyStorageAccessActions_2[]; + [groups: `group${string}`]: AmplifyStorageAccessActions_2[]; + [entity: `entity${string}`]: AmplifyStorageAccessActions_2[]; resource?: AmplifyStorageAccessActions_2[]; -} +}; // @public interface AmplifyStorageAccessRule_3 { @@ -116,6 +125,20 @@ interface AmplifyStorageAccessRule_3 { resource?: AmplifyStorageAccessActions_3[]; } +// @public +interface AmplifyStorageAccessRule_4 { + // (undocumented) + authenticated?: AmplifyStorageAccessActions_4[]; + // (undocumented) + entity?: AmplifyStorageAccessActions_4[]; + // (undocumented) + groups?: AmplifyStorageAccessActions_4[]; + // (undocumented) + guest?: AmplifyStorageAccessActions_4[]; + // (undocumented) + resource?: AmplifyStorageAccessActions_4[]; +} + // @public interface AmplifyStorageBucket { // (undocumented) @@ -130,7 +153,7 @@ interface AmplifyStorageBucket { }; } -// @public (undocumented) +// @public interface AmplifyStorageBucket_2 { // (undocumented) aws_region: string; @@ -166,6 +189,20 @@ interface AmplifyStorageBucket_4 { bucket_name: string; // (undocumented) name: string; + // (undocumented) + paths?: { + [k: string]: AmplifyStorageAccessRule_4; + }; +} + +// @public (undocumented) +interface AmplifyStorageBucket_5 { + // (undocumented) + aws_region: string; + // (undocumented) + bucket_name: string; + // (undocumented) + name: string; } // @public @@ -180,6 +217,12 @@ interface AmplifyUserGroupConfig_2 { precedence?: number; } +// @public +interface AmplifyUserGroupConfig_3 { + // (undocumented) + precedence?: number; +} + // @public (undocumented) export type AnalyticsClientConfig = { aws_mobile_analytics_app_id?: string; @@ -222,6 +265,7 @@ export type AuthClientConfig = { // @public interface AWSAmplifyBackendOutputs { + $schema?: string; analytics?: { amazon_pinpoint?: { aws_region: string; @@ -241,19 +285,19 @@ interface AWSAmplifyBackendOutputs { require_symbols: boolean; }; oauth?: { - identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; + identity_providers: ("GOOGLE" | "FACEBOOK" | "LOGIN_WITH_AMAZON" | "SIGN_IN_WITH_APPLE")[]; domain: string; scopes: string[]; - redirect_sign_in_uri: string[]; - redirect_sign_out_uri: string[]; - response_type: 'code' | 'token'; + redirect_sign_in_uri: [string, ...string[]]; + redirect_sign_out_uri: [string, ...string[]]; + response_type: "code" | "token"; }; standard_required_attributes?: AmazonCognitoStandardAttributes[]; - username_attributes?: ('email' | 'phone_number' | 'username')[]; - user_verification_types?: ('email' | 'phone_number')[]; + username_attributes?: ["email" | "phone_number" | "username", ...("email" | "phone_number" | "username")[]]; + user_verification_types?: ("email" | "phone_number")[]; unauthenticated_identities_enabled?: boolean; - mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; - mfa_methods?: ('SMS' | 'TOTP')[]; + mfa_configuration?: "NONE" | "OPTIONAL" | "REQUIRED"; + mfa_methods?: ("SMS" | "TOTP")[]; groups?: { [k: string]: AmplifyUserGroupConfig; }[]; @@ -274,31 +318,53 @@ interface AWSAmplifyBackendOutputs { geo?: { aws_region: string; maps?: { - items: { - [k: string]: AmazonLocationServiceConfig; - }; - default: string; + items?: [ + { + name?: string; + key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + key?: string; + [k: string]: unknown; + }[] + ]; + default?: string; + required?: ["items", "default"]; }; search_indices?: { - items: string[]; - default: string; + items?: [ + { + name?: string; + key?: string; + [k: string]: unknown; + }, + ...{ + name?: string; + key?: string; + [k: string]: unknown; + }[] + ]; + default?: string; + required?: ["items", "default"]; }; geofence_collections?: { - items: string[]; + items: [string, ...string[]]; default: string; }; }; notifications?: { aws_region: AwsRegion; amazon_pinpoint_app_id: string; - channels: AmazonPinpointChannels[]; + channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; }; storage?: { aws_region: AwsRegion; bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: '1.4'; + version: "1.5"; } // @public @@ -379,7 +445,7 @@ interface AWSAmplifyBackendOutputs_2 { bucket_name: string; buckets?: AmplifyStorageBucket_2[]; }; - version: '1.3'; + version: '1.4'; } // @public @@ -416,6 +482,9 @@ interface AWSAmplifyBackendOutputs_3 { unauthenticated_identities_enabled?: boolean; mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; mfa_methods?: ('SMS' | 'TOTP')[]; + groups?: { + [k: string]: AmplifyUserGroupConfig_3; + }[]; }; custom?: { [k: string]: unknown; @@ -457,19 +526,19 @@ interface AWSAmplifyBackendOutputs_3 { bucket_name: string; buckets?: AmplifyStorageBucket_3[]; }; - version: '1.2'; + version: '1.3'; } // @public interface AWSAmplifyBackendOutputs_4 { analytics?: { amazon_pinpoint?: { - aws_region: AwsRegion_4; + aws_region: string; app_id: string; }; }; auth?: { - aws_region: AwsRegion_4; + aws_region: string; user_pool_id: string; user_pool_client_id: string; identity_pool_id?: string; @@ -509,7 +578,7 @@ interface AWSAmplifyBackendOutputs_4 { authorization_types: AwsAppsyncAuthorizationType_4[]; }; geo?: { - aws_region: AwsRegion_4; + aws_region: string; maps?: { items: { [k: string]: AmazonLocationServiceConfig_4; @@ -535,7 +604,7 @@ interface AWSAmplifyBackendOutputs_4 { bucket_name: string; buckets?: AmplifyStorageBucket_4[]; }; - version: '1.1'; + version: '1.2'; } // @public @@ -611,12 +680,90 @@ interface AWSAmplifyBackendOutputs_5 { storage?: { aws_region: AwsRegion_5; bucket_name: string; + buckets?: AmplifyStorageBucket_5[]; + }; + version: '1.1'; +} + +// @public +interface AWSAmplifyBackendOutputs_6 { + analytics?: { + amazon_pinpoint?: { + aws_region: AwsRegion_6; + app_id: string; + }; + }; + auth?: { + aws_region: AwsRegion_6; + user_pool_id: string; + user_pool_client_id: string; + identity_pool_id?: string; + password_policy?: { + min_length: number; + require_numbers: boolean; + require_lowercase: boolean; + require_uppercase: boolean; + require_symbols: boolean; + }; + oauth?: { + identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; + domain: string; + scopes: string[]; + redirect_sign_in_uri: string[]; + redirect_sign_out_uri: string[]; + response_type: 'code' | 'token'; + }; + standard_required_attributes?: AmazonCognitoStandardAttributes_6[]; + username_attributes?: ('email' | 'phone_number' | 'username')[]; + user_verification_types?: ('email' | 'phone_number')[]; + unauthenticated_identities_enabled?: boolean; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; + }; + custom?: { + [k: string]: unknown; + }; + data?: { + aws_region: AwsRegion_6; + url: string; + model_introspection?: { + [k: string]: unknown; + }; + api_key?: string; + default_authorization_type: AwsAppsyncAuthorizationType_6; + authorization_types: AwsAppsyncAuthorizationType_6[]; + }; + geo?: { + aws_region: AwsRegion_6; + maps?: { + items: { + [k: string]: AmazonLocationServiceConfig_6; + }; + default: string; + }; + search_indices?: { + items: string[]; + default: string; + }; + geofence_collections?: { + items: string[]; + default: string; + }; + }; + notifications?: { + aws_region: AwsRegion_6; + amazon_pinpoint_app_id: string; + channels: AmazonPinpointChannels_6[]; + }; + storage?: { + aws_region: AwsRegion_6; + bucket_name: string; }; version: '1'; } // @public -type AwsAppsyncAuthorizationType = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; +type AwsAppsyncAuthorizationType = "AMAZON_COGNITO_USER_POOLS" | "API_KEY" | "AWS_IAM" | "AWS_LAMBDA" | "OPENID_CONNECT"; // @public type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; @@ -630,10 +777,13 @@ type AwsAppsyncAuthorizationType_4 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | ' // @public type AwsAppsyncAuthorizationType_5 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; +// @public +type AwsAppsyncAuthorizationType_6 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; + // @public type AwsRegion = string; -// @public (undocumented) +// @public type AwsRegion_2 = string; // @public (undocumented) @@ -645,8 +795,11 @@ type AwsRegion_4 = string; // @public (undocumented) type AwsRegion_5 = string; +// @public (undocumented) +type AwsRegion_6 = string; + // @public -export type ClientConfig = clientConfigTypesV1_4.AWSAmplifyBackendOutputs | clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs | clientConfigTypesV1_1.AWSAmplifyBackendOutputs | clientConfigTypesV1.AWSAmplifyBackendOutputs; +export type ClientConfig = clientConfigTypesV1_5.AWSAmplifyBackendOutputs | clientConfigTypesV1_4.AWSAmplifyBackendOutputs | clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs | clientConfigTypesV1_1.AWSAmplifyBackendOutputs | clientConfigTypesV1.AWSAmplifyBackendOutputs; // @public (undocumented) export enum ClientConfigFileBaseName { @@ -674,31 +827,46 @@ export enum ClientConfigFormat { export type ClientConfigLegacy = Partial; declare namespace clientConfigTypesV1 { + export { + AmazonCognitoStandardAttributes_6 as AmazonCognitoStandardAttributes, + AwsRegion_6 as AwsRegion, + AwsAppsyncAuthorizationType_6 as AwsAppsyncAuthorizationType, + AmazonPinpointChannels_6 as AmazonPinpointChannels, + AWSAmplifyBackendOutputs_6 as AWSAmplifyBackendOutputs, + AmazonLocationServiceConfig_6 as AmazonLocationServiceConfig + } +} +export { clientConfigTypesV1 } + +declare namespace clientConfigTypesV1_1 { export { AmazonCognitoStandardAttributes_5 as AmazonCognitoStandardAttributes, AwsRegion_5 as AwsRegion, AwsAppsyncAuthorizationType_5 as AwsAppsyncAuthorizationType, AmazonPinpointChannels_5 as AmazonPinpointChannels, AWSAmplifyBackendOutputs_5 as AWSAmplifyBackendOutputs, - AmazonLocationServiceConfig_5 as AmazonLocationServiceConfig + AmazonLocationServiceConfig_5 as AmazonLocationServiceConfig, + AmplifyStorageBucket_5 as AmplifyStorageBucket } } -export { clientConfigTypesV1 } +export { clientConfigTypesV1_1 } -declare namespace clientConfigTypesV1_1 { +declare namespace clientConfigTypesV1_2 { export { AmazonCognitoStandardAttributes_4 as AmazonCognitoStandardAttributes, AwsRegion_4 as AwsRegion, AwsAppsyncAuthorizationType_4 as AwsAppsyncAuthorizationType, AmazonPinpointChannels_4 as AmazonPinpointChannels, + AmplifyStorageAccessActions_4 as AmplifyStorageAccessActions, AWSAmplifyBackendOutputs_4 as AWSAmplifyBackendOutputs, AmazonLocationServiceConfig_4 as AmazonLocationServiceConfig, - AmplifyStorageBucket_4 as AmplifyStorageBucket + AmplifyStorageBucket_4 as AmplifyStorageBucket, + AmplifyStorageAccessRule_4 as AmplifyStorageAccessRule } } -export { clientConfigTypesV1_1 } +export { clientConfigTypesV1_2 } -declare namespace clientConfigTypesV1_2 { +declare namespace clientConfigTypesV1_3 { export { AmazonCognitoStandardAttributes_3 as AmazonCognitoStandardAttributes, AwsRegion_3 as AwsRegion, @@ -706,30 +874,31 @@ declare namespace clientConfigTypesV1_2 { AmazonPinpointChannels_3 as AmazonPinpointChannels, AmplifyStorageAccessActions_3 as AmplifyStorageAccessActions, AWSAmplifyBackendOutputs_3 as AWSAmplifyBackendOutputs, + AmplifyUserGroupConfig_3 as AmplifyUserGroupConfig, AmazonLocationServiceConfig_3 as AmazonLocationServiceConfig, AmplifyStorageBucket_3 as AmplifyStorageBucket, AmplifyStorageAccessRule_3 as AmplifyStorageAccessRule } } -export { clientConfigTypesV1_2 } +export { clientConfigTypesV1_3 } -declare namespace clientConfigTypesV1_3 { +declare namespace clientConfigTypesV1_4 { export { AmazonCognitoStandardAttributes_2 as AmazonCognitoStandardAttributes, AwsRegion_2 as AwsRegion, AwsAppsyncAuthorizationType_2 as AwsAppsyncAuthorizationType, AmazonPinpointChannels_2 as AmazonPinpointChannels, + AmplifyStorageAccessRule_2 as AmplifyStorageAccessRule, AmplifyStorageAccessActions_2 as AmplifyStorageAccessActions, AWSAmplifyBackendOutputs_2 as AWSAmplifyBackendOutputs, AmplifyUserGroupConfig_2 as AmplifyUserGroupConfig, AmazonLocationServiceConfig_2 as AmazonLocationServiceConfig, - AmplifyStorageBucket_2 as AmplifyStorageBucket, - AmplifyStorageAccessRule_2 as AmplifyStorageAccessRule + AmplifyStorageBucket_2 as AmplifyStorageBucket } } -export { clientConfigTypesV1_3 } +export { clientConfigTypesV1_4 } -declare namespace clientConfigTypesV1_4 { +declare namespace clientConfigTypesV1_5 { export { AmazonCognitoStandardAttributes, AwsRegion, @@ -739,11 +908,11 @@ declare namespace clientConfigTypesV1_4 { AmplifyStorageAccessActions, AWSAmplifyBackendOutputs, AmplifyUserGroupConfig, - AmazonLocationServiceConfig, - AmplifyStorageBucket + AmplifyStorageBucket, + AmazonLocationServiceConfig } } -export { clientConfigTypesV1_4 } +export { clientConfigTypesV1_5 } // @public (undocumented) export type ClientConfigVersion = `${ClientConfigVersionOption}`; @@ -767,7 +936,7 @@ export enum ClientConfigVersionOption { } // @public -export type ClientConfigVersionTemplateType = T extends '1.4' ? clientConfigTypesV1_4.AWSAmplifyBackendOutputs : T extends '1.3' ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs : T extends '1.2' ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs : T extends '1.1' ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs : T extends '1' ? clientConfigTypesV1.AWSAmplifyBackendOutputs : never; +export type ClientConfigVersionTemplateType = T extends '1.5' ? clientConfigTypesV1_5.AWSAmplifyBackendOutputs : T extends '1.4' ? clientConfigTypesV1_4.AWSAmplifyBackendOutputs : T extends '1.3' ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs : T extends '1.2' ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs : T extends '1.1' ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs : T extends '1' ? clientConfigTypesV1.AWSAmplifyBackendOutputs : never; // @public (undocumented) export type CustomClientConfig = { diff --git a/packages/client-config/package.json b/packages/client-config/package.json index c407c25ac38..86aee6bbe3e 100644 --- a/packages/client-config/package.json +++ b/packages/client-config/package.json @@ -29,6 +29,7 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", + "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts index 5bf6e34af2a..a89d29012bd 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts @@ -9,11 +9,12 @@ import { StorageClientConfigContributorV1 as Storage1, StorageClientConfigContributorV1_1 as Storage1_1, StorageClientConfigContributor as Storage1_2, - VersionContributor as VersionContributor1_4, + VersionContributor as VersionContributor1_5, VersionContributorV1, VersionContributorV1_1, VersionContributorV1_2, VersionContributorV1_3, + VersionContributorV1_4, } from './client_config_contributor_v1.js'; import { ClientConfigContributor } from '../client-config-types/client_config_contributor.js'; @@ -43,7 +44,7 @@ export class ClientConfigContributorFactory { new Data1_1(this.modelIntrospectionSchemaAdapter), new Geo1_1(), new Storage1_2(), - new VersionContributor1_4(), + new VersionContributor1_5(), new Custom1_1(), ], @@ -52,7 +53,7 @@ export class ClientConfigContributorFactory { new Data1_1(this.modelIntrospectionSchemaAdapter), new Geo1(), new Storage1_2(), - new VersionContributor1_4(), + new VersionContributorV1_4(), new Custom1_1(), ], @@ -97,7 +98,7 @@ export class ClientConfigContributorFactory { new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), new Storage1_2(), - new VersionContributor1_4(), + new VersionContributorV1_4(), new Custom1_1(), ], }; diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 706268e636b..920326e0a4a 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -9,7 +9,7 @@ import { } from './client_config_contributor_v1.js'; import { ClientConfig, - clientConfigTypesV1_4, + clientConfigTypesV1_5, } from '../client-config-types/client_config.js'; import assert from 'node:assert'; import { @@ -76,7 +76,7 @@ void describe('auth client config contributor v1', () => { identity_pool_id: 'testIdentityPoolId', unauthenticated_identities_enabled: true, }, - } as Partial, + } as Partial, ); }); @@ -101,7 +101,7 @@ void describe('auth client config contributor v1', () => { aws_region: 'testRegion', identity_pool_id: 'testIdentityPoolId', }, - } as Partial, + } as Partial, ); }); @@ -135,7 +135,7 @@ void describe('auth client config contributor v1', () => { require_uppercase: true, }, }, - } as Partial, + } as Partial, ); }); @@ -168,7 +168,7 @@ void describe('auth client config contributor v1', () => { require_uppercase: false, }, }, - } as Partial, + } as Partial, ); }); @@ -263,7 +263,7 @@ void describe('auth client config contributor v1', () => { }, ], }, - } as Partial, + } as Partial, ); }); @@ -352,7 +352,7 @@ void describe('auth client config contributor v1', () => { }, ], }, - } as Partial, + } as Partial, ); }); @@ -435,7 +435,7 @@ void describe('auth client config contributor v1', () => { }, ], }, - } as Pick; + } as Pick; void it('returns translated config when mfa is disabled', () => { const contributor = new AuthClientConfigContributor(); @@ -536,7 +536,7 @@ void describe('data client config contributor v1', () => { url: 'testApiEndpoint', aws_region: 'us-east-1', }, - } as Partial); + } as Partial); }); void it('returns translated config with model introspection when resolvable', async () => { @@ -584,7 +584,7 @@ void describe('data client config contributor v1', () => { enums: {}, }, }, - } as Partial); + } as Partial); }); }); @@ -639,6 +639,75 @@ void describe('geo client config contributor v1', () => { }, ); }); + + void it('returns correct config when all geo objects exist', () => { + const contributor = new GeoClientConfigContributor(); + assert.deepStrictEqual( + contributor.contribute({ + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-west-2', + maps: JSON.stringify( + JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + ), + searchIndices: JSON.stringify( + JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + ), + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, + }), + { + geo: { + aws_region: 'us-west-2', + maps: { + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }, + search_indices: { + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }, + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, + }, + ); + }); }); void describe('storage client config contributor v1', () => { @@ -761,6 +830,6 @@ void describe('Custom client config contributor v1', () => { void describe('Custom client config contributor v1', () => { void it('contributes the version correctly', () => { - assert.deepEqual(new VersionContributor().contribute(), { version: '1.4' }); + assert.deepEqual(new VersionContributor().contribute(), { version: '1.5' }); }); }); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index 61b13009411..c29b0c8b315 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -14,6 +14,8 @@ import { clientConfigTypesV1_1, clientConfigTypesV1_2, clientConfigTypesV1_3, + clientConfigTypesV1_4, + clientConfigTypesV1_5, } from '../client-config-types/client_config.js'; import { ModelIntrospectionSchemaAdapter } from '../model_introspection_schema_adapter.js'; import { AwsAppsyncAuthorizationType } from '../client-config-schema/client_config_v1.1.js'; @@ -23,9 +25,19 @@ import { AmplifyStorageAccessRule } from '../client-config-schema/client_config_ // the same schema (version and other types) /** - * Translator for the version number of ClientConfig of V1.4 + * Translator for the version number of ClientConfig of V1.5 */ export class VersionContributor implements ClientConfigContributor { + contribute = (): ClientConfig => { + return { version: ClientConfigVersionOption.V1_5 }; + }; +} + +/** + * Translator for the version number of ClientConfig of V1.4 + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class VersionContributorV1_4 implements ClientConfigContributor { contribute = (): ClientConfig => { return { version: ClientConfigVersionOption.V1_4 }; }; @@ -470,7 +482,7 @@ export class DataClientConfigContributor implements ClientConfigContributor { } /** - * Transformer for Geo segment of ClientConfig (V1.1 or later) + * Transformer for Geo segment of ClientConfig (V1.5 or later) */ export class GeoClientConfigContributor implements ClientConfigContributor { contribute = ({ @@ -480,43 +492,49 @@ export class GeoClientConfigContributor implements ClientConfigContributor { return {}; } - const config: Partial = {}; + const config: Partial = {}; config.geo = { aws_region: geoOutput.payload.geoRegion, }; - let geofenceCollectionsObj; + const mapPayload = this.assignOutput(geoOutput.payload.maps); - if (geoOutput.payload.geofenceCollections) { - const firstParse = JSON.parse( - JSON.parse(geoOutput.payload.geofenceCollections), - ); + const placesPayload = this.assignOutput(geoOutput.payload.searchIndices); - if ( - firstParse && - typeof firstParse === 'object' && - !Array.isArray(firstParse) && - firstParse.default - ) { - geofenceCollectionsObj = firstParse; + const collectionPayload = this.assignOutput( + geoOutput.payload.geofenceCollections, + ); + + if (mapPayload) config.geo.maps = mapPayload; + if (placesPayload) config.geo.search_indices = placesPayload; + if (collectionPayload) config.geo.geofence_collections = collectionPayload; + + return config; + }; + + assignOutput = (resourcePayload: string | undefined) => { + if (resourcePayload) { + let resourceObj; + const firstParse = JSON.parse(JSON.parse(resourcePayload)); + + if (firstParse && typeof firstParse === 'object' && firstParse.default) { + resourceObj = firstParse; } - if (geofenceCollectionsObj && geofenceCollectionsObj.default) { - config.geo!.geofence_collections = { - default: geofenceCollectionsObj.default, - items: geofenceCollectionsObj.items || [], - }; + if (resourceObj) { + return resourceObj; } + } else { + return resourcePayload; } - - return config; }; } /** * Transformer for Geo segment of ClientConfig (V1.1 or later) */ +// eslint-disable-next-line @typescript-eslint/naming-convention export class GeoClientConfigContributorV1 implements ClientConfigContributor { contribute = ({ [geoOutputKey]: geoOutput, @@ -525,7 +543,7 @@ export class GeoClientConfigContributorV1 implements ClientConfigContributor { return {}; } - const config: Partial = {}; + const config: Partial = {}; config.geo = { aws_region: geoOutput.payload.geoRegion, diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts index b1ba572da25..6358a6b8cc2 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.5.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.5.ts @@ -74,7 +74,7 @@ export type AmplifyStorageAccessRule = { resource?: AmplifyStorageAccessActions[]; }; /** - * run json-schema-to-typescript with --unreachableDefinitions to ensure this type is generated + * run json-schema-to-typescript with --unreachableDefitions to ensure this type is generated * * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema * via the `definition` "amplify_storage_access_actions". @@ -97,7 +97,7 @@ export interface AWSAmplifyBackendOutputs { /** * Version of this schema */ - version: '1.4'; + version: '1.5'; /** * Outputs manually specified by developers for use with frontend library */ @@ -231,19 +231,20 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - keys?: [ + items?: [ { name?: string; - api_key?: string; + key?: string; [k: string]: unknown; }, ...{ name?: string; - api_key?: string; + key?: string; [k: string]: unknown; }[], ]; - required?: ['keys', 'default']; + default?: string; + required?: ['items', 'default']; }; /** * Location search (search by places, addresses, coordinates) @@ -252,19 +253,20 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - keys?: [ + items?: [ { name?: string; - api_key?: string; + key?: string; [k: string]: unknown; }, ...{ name?: string; - api_key?: string; + key?: string; [k: string]: unknown; }[], ]; - required?: ['keys', 'default']; + default?: string; + required?: ['items', 'default']; }; /** * Geofencing (visualize virtual perimeters) diff --git a/packages/client-config/src/client-config-schema/schema_v1.5.json b/packages/client-config/src/client-config-schema/schema_v1.5.json index 01e546605ec..585081774de 100644 --- a/packages/client-config/src/client-config-schema/schema_v1.5.json +++ b/packages/client-config/src/client-config-schema/schema_v1.5.json @@ -12,7 +12,7 @@ }, "version": { "description": "Version of this schema", - "const": "1.4" + "const": "1.5" }, "analytics": { "description": "Outputs manually specified by developers for use with frontend library", @@ -258,7 +258,7 @@ "type": "object", "additionalProperties": false, "properties": { - "keys": { + "items": { "type": "array", "uniqueItems": true, "minItems": 1, @@ -269,16 +269,16 @@ "name": { "type": "string" }, - "api_key": { + "key": { "type": "string" } } - }, - "default": { - "type": "string" } }, - "required": ["keys", "default"] + "default": { + "type": "string" + }, + "required": ["items", "default"] } }, "search_indices": { @@ -286,7 +286,7 @@ "type": "object", "additionalProperties": false, "properties": { - "keys": { + "items": { "type": "array", "uniqueItems": true, "minItems": 1, @@ -297,16 +297,16 @@ "name": { "type": "string" }, - "api_key": { + "key": { "type": "string" } } - }, - "default": { - "type": "string" } }, - "required": ["keys", "default"] + "default": { + "type": "string" + }, + "required": ["items", "default"] } }, "geofence_collections": { diff --git a/packages/client-config/src/client-config-types/client_config.ts b/packages/client-config/src/client-config-types/client_config.ts index 3fc083a00fa..73c8f1513e8 100644 --- a/packages/client-config/src/client-config-types/client_config.ts +++ b/packages/client-config/src/client-config-types/client_config.ts @@ -14,7 +14,7 @@ import * as clientConfigTypesV1_1 from '../client-config-schema/client_config_v1 import * as clientConfigTypesV1_2 from '../client-config-schema/client_config_v1.2.js'; import * as clientConfigTypesV1_3 from '../client-config-schema/client_config_v1.3.js'; import * as clientConfigTypesV1_4 from '../client-config-schema/client_config_v1.4.js'; -// import * as clientConfigTypesV1_5 from '../client-config-schema/client_config_v1.5.js'; +import * as clientConfigTypesV1_5 from '../client-config-schema/client_config_v1.5.js'; /* eslint-enable @typescript-eslint/naming-convention */ /** @@ -37,7 +37,7 @@ export type ClientConfigLegacy = Partial< * ClientConfig = clientConfigTypesV1.AWSAmplifyBackendOutputs | clientConfigTypesV2.AWSAmplifyBackendOutputs; */ export type ClientConfig = - // | clientConfigTypesV1_5.AWSAmplifyBackendOutputs + | clientConfigTypesV1_5.AWSAmplifyBackendOutputs | clientConfigTypesV1_4.AWSAmplifyBackendOutputs | clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs @@ -50,7 +50,7 @@ export { clientConfigTypesV1_2, clientConfigTypesV1_3, clientConfigTypesV1_4, - // clientConfigTypesV1_5, + clientConfigTypesV1_5, }; export enum ClientConfigVersionOption { @@ -67,8 +67,7 @@ export type ClientConfigVersion = `${ClientConfigVersionOption}`; // Client config version that is generated by default if customers didn't specify one export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = - ClientConfigVersionOption.V1_4; -// ClientConfigVersionOption.V1_5 + ClientConfigVersionOption.V1_5; /** * Return type of `getClientConfig`. This types narrow the returned client config version @@ -81,17 +80,19 @@ export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = * ? clientConfigTypesV2.AWSAmplifyBackendOutputs * : never; */ -export type ClientConfigVersionTemplateType = T extends '1.4' /* '1.5' */ - ? clientConfigTypesV1_4.AWSAmplifyBackendOutputs - : T extends '1.3' - ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs - : T extends '1.2' - ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs - : T extends '1.1' - ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs - : T extends '1' - ? clientConfigTypesV1.AWSAmplifyBackendOutputs - : never; +export type ClientConfigVersionTemplateType = T extends '1.5' + ? clientConfigTypesV1_5.AWSAmplifyBackendOutputs + : T extends '1.4' + ? clientConfigTypesV1_4.AWSAmplifyBackendOutputs + : T extends '1.3' + ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs + : T extends '1.2' + ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs + : T extends '1.1' + ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs + : T extends '1' + ? clientConfigTypesV1.AWSAmplifyBackendOutputs + : never; export enum ClientConfigFormat { MJS = 'mjs', diff --git a/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts b/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts index 19d9922858d..886683f7068 100644 --- a/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts @@ -13,7 +13,7 @@ void describe('client config formatter', () => { const sampleIdentityPoolId = 'test_identity_pool_id'; const sampleUserPoolClientId = 'test_user_pool_client_id'; const clientConfig: ClientConfig = { - version: '1.4', + version: '1.5', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, @@ -23,7 +23,7 @@ void describe('client config formatter', () => { }; const expectedConfigReturned: ClientConfig = { - version: '1.4', + version: '1.5', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts b/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts index 4e869c18ef6..18c8f96066f 100644 --- a/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts @@ -20,7 +20,7 @@ void describe('client config formatter', () => { const sampleUserPoolId = randomUUID(); const clientConfig: ClientConfig = { - version: '1.4', + version: '1.5', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts index 34597e695fb..98404075bba 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts @@ -26,7 +26,7 @@ void describe('ClientConfigLegacyConverter', () => { version: '3' as any, }), new AmplifyFault('UnsupportedClientConfigVersionFault', { - message: 'Only version 1.4 of ClientConfig is supported.', + message: 'Only version 1.5 of ClientConfig is supported.', }), ); }); @@ -35,7 +35,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, auth: { identity_pool_id: 'testIdentityPoolId', user_pool_id: 'testUserPoolId', @@ -133,7 +133,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, data: { aws_region: 'testRegion', url: 'testUrl', @@ -274,7 +274,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, storage: { aws_region: 'testRegion', bucket_name: 'testBucket', @@ -296,7 +296,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, custom: { customKey: { customNestedKey: { @@ -327,7 +327,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, analytics: { amazon_pinpoint: { aws_region: 'testRegion', @@ -356,19 +356,16 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, geo: { aws_region: 'testRegion', maps: { default: 'map1', - items: { - map1: { style: 'style1' }, - map2: { style: 'style2' }, - }, + items: [{ name: 'map1', api_key: 'key' }], }, search_indices: { default: 'index1', - items: ['index1', 'index2'], + items: [{ name: 'index1', api_key: 'key' }], }, geofence_collections: { default: 'geofence1', @@ -384,13 +381,12 @@ void describe('ClientConfigLegacyConverter', () => { maps: { default: 'map1', items: { - map1: { style: 'style1' }, - map2: { style: 'style2' }, + map1: { style: '' }, }, }, search_indices: { default: 'index1', - items: ['index1', 'index2'], + items: ['index1'], }, geofenceCollections: { default: 'geofence1', @@ -409,7 +405,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); let v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, notifications: { amazon_pinpoint_app_id: 'testAppId', aws_region: 'testRegion', @@ -452,7 +448,7 @@ void describe('ClientConfigLegacyConverter', () => { // both APNS and FCM cannot be specified together as they both map to Push. v1Config = { - version: ClientConfigVersionOption.V1_4, + version: ClientConfigVersionOption.V1_5, notifications: { amazon_pinpoint_app_id: 'testAppId', aws_region: 'testRegion', diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts index 9e62f0fa42a..8caa6999c2e 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts @@ -2,7 +2,7 @@ import { AmplifyFault } from '@aws-amplify/platform-core'; import { ClientConfig, ClientConfigLegacy, - clientConfigTypesV1_4, + clientConfigTypesV1_5, } from '../client-config-types/client_config.js'; import { @@ -23,9 +23,9 @@ export class ClientConfigLegacyConverter { */ convertToLegacyConfig = (clientConfig: ClientConfig): ClientConfigLegacy => { // We can only convert from V1.4 of ClientConfig. For everything else, throw - if (!this.isClientConfigV1_4(clientConfig)) { + if (!this.isClientConfigV1_5(clientConfig)) { throw new AmplifyFault('UnsupportedClientConfigVersionFault', { - message: 'Only version 1.4 of ClientConfig is supported.', + message: 'Only version 1.5 of ClientConfig is supported.', }); } @@ -37,7 +37,8 @@ export class ClientConfigLegacyConverter { legacyConfig.aws_project_region = clientConfig.auth?.aws_region ?? clientConfig.data?.aws_region ?? - clientConfig.storage?.aws_region; + clientConfig.storage?.aws_region ?? + clientConfig.geo?.aws_region; } // Auth @@ -205,22 +206,30 @@ export class ClientConfigLegacyConverter { if (clientConfig.geo.maps) { const mapsLegacyConfig: Record = {}; - for (const mapName in clientConfig.geo.maps.items) { - if (clientConfig.geo.maps.items[mapName].style) { - mapsLegacyConfig[mapName] = { - style: clientConfig.geo.maps.items[mapName].style!, - }; - } + + for (const map of clientConfig.geo.maps.items!) { + mapsLegacyConfig[map.name!] = { + style: '', // leaving empty as it doesn't exist + }; } + geoConfig.geo!.amazon_location_service.maps = { - default: clientConfig.geo.maps.default, + default: clientConfig.geo.maps.default!, items: mapsLegacyConfig, }; } if (clientConfig.geo.search_indices) { - geoConfig.geo!.amazon_location_service.search_indices = - clientConfig.geo.search_indices; + const placesLegacyConfig: string[] = []; + + for (const place of clientConfig.geo.search_indices.items!) { + placesLegacyConfig.push(place.name!); + } + + geoConfig.geo!.amazon_location_service.search_indices = { + default: clientConfig.geo!.search_indices.default!, + items: placesLegacyConfig, + }; } if (clientConfig.geo.geofence_collections) { @@ -274,9 +283,9 @@ export class ClientConfigLegacyConverter { }; // eslint-disable-next-line @typescript-eslint/naming-convention - isClientConfigV1_4 = ( + isClientConfigV1_5 = ( clientConfig: ClientConfig, - ): clientConfig is clientConfigTypesV1_4.AWSAmplifyBackendOutputs => { - return clientConfig.version === '1.4'; + ): clientConfig is clientConfigTypesV1_5.AWSAmplifyBackendOutputs => { + return clientConfig.version === '1.5'; }; } diff --git a/packages/client-config/src/client-config-writer/client_config_writer.test.ts b/packages/client-config/src/client-config-writer/client_config_writer.test.ts index 1eb8d79a32c..205fe410e2a 100644 --- a/packages/client-config/src/client-config-writer/client_config_writer.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_writer.test.ts @@ -43,7 +43,7 @@ void describe('client config writer', () => { }); const clientConfig: ClientConfig = { - version: '1.4', + version: '1.5', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/generate_empty_client_config_to_file.test.ts b/packages/client-config/src/generate_empty_client_config_to_file.test.ts index ac1bb1baed1..7b1e5dab044 100644 --- a/packages/client-config/src/generate_empty_client_config_to_file.test.ts +++ b/packages/client-config/src/generate_empty_client_config_to_file.test.ts @@ -30,15 +30,15 @@ void describe('generate empty client config to file', () => { path.join(process.cwd(), 'userOutDir', 'amplifyconfiguration.ts'), ); }); - void it('correctly generates an empty file for client config version 1.4', async () => { + void it('correctly generates an empty file for client config version 1.5', async () => { await generateEmptyClientConfigToFile( - ClientConfigVersionOption.V1_4, + ClientConfigVersionOption.V1_5, 'userOutDir', ); assert.equal(writeFileMock.mock.callCount(), 1); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[1], - `{\n "version": "1.4"\n}`, + `{\n "version": "1.5"\n}`, ); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[0], diff --git a/packages/client-config/src/generate_empty_client_config_to_file.ts b/packages/client-config/src/generate_empty_client_config_to_file.ts index f43615cc22b..3bdff8d0dec 100644 --- a/packages/client-config/src/generate_empty_client_config_to_file.ts +++ b/packages/client-config/src/generate_empty_client_config_to_file.ts @@ -15,7 +15,7 @@ export const generateEmptyClientConfigToFile = async ( format?: ClientConfigFormat, ): Promise => { const clientConfig: ClientConfig = { - version: '1.4', + version: '1.5', }; return writeClientConfigToFile(clientConfig, version, outDir, format); }; diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index b2d984b49a2..a63708860ab 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -27,6 +27,201 @@ const stubClientProvider = { }; void describe('UnifiedClientConfigGenerator', () => { void describe('generateClientConfig', () => { + void it('transforms backend output into client config for V1.5', async () => { + const groups = [ + { + ADMINS: { + precedence: 0, + }, + }, + { + EDITORS: { + precedence: 1, + }, + }, + ]; + const stubOutput: UnifiedBackendOutput = { + [platformOutputKey]: { + version: '1', + payload: { + deploymentType: 'branch', + region: 'us-east-1', + }, + }, + [authOutputKey]: { + version: '1', + payload: { + identityPoolId: 'testIdentityPoolId', + userPoolId: 'testUserPoolId', + webClientId: 'testWebClientId', + authRegion: 'us-east-1', + passwordPolicyMinLength: '8', + passwordPolicyRequirements: + '["REQUIRES_NUMBERS","REQUIRES_LOWERCASE","REQUIRES_UPPERCASE"]', + mfaTypes: '["SMS","TOTP"]', + mfaConfiguration: 'OPTIONAL', + verificationMechanisms: '["email","phone_number"]', + usernameAttributes: '["email"]', + signupAttributes: '["email"]', + allowUnauthenticatedIdentities: 'true', + groups: JSON.stringify(groups), + }, + }, + [graphqlOutputKey]: { + version: '1', + payload: { + awsAppsyncApiEndpoint: 'testApiEndpoint', + awsAppsyncRegion: 'us-east-1', + awsAppsyncAuthenticationType: 'API_KEY', + awsAppsyncAdditionalAuthenticationTypes: 'API_KEY', + awsAppsyncConflictResolutionMode: 'AUTO_MERGE', + awsAppsyncApiKey: 'testApiKey', + awsAppsyncApiId: 'testApiId', + amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', + }, + }, + [geoOutputKey]: { + version: '1', + payload: { + geoRegion: 'us-east-1', + maps: JSON.stringify( + JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + ), + searchIndices: JSON.stringify( + JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + ), + geofenceCollections: JSON.stringify( + JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), + ), + }, + }, + [customOutputKey]: { + version: '1', + payload: { + customOutputs: JSON.stringify({ + custom: { + output1: 'val1', + output2: 'val2', + }, + }), + }, + }, + }; + + const outputRetrieval = mock.fn(async () => stubOutput); + const modelSchemaAdapter = new ModelIntrospectionSchemaAdapter( + stubClientProvider, + ); + + mock.method( + modelSchemaAdapter, + 'getModelIntrospectionSchemaFromS3Uri', + () => undefined, + ); + const configContributors = new ClientConfigContributorFactory( + modelSchemaAdapter, + ).getContributors('1.5'); + const clientConfigGenerator = new UnifiedClientConfigGenerator( + outputRetrieval, + configContributors, + ); + const result = await clientConfigGenerator.generateClientConfig(); + + const expectedClientConfig: ClientConfig = { + auth: { + user_pool_id: 'testUserPoolId', + aws_region: 'us-east-1', + user_pool_client_id: 'testWebClientId', + identity_pool_id: 'testIdentityPoolId', + mfa_methods: ['SMS', 'TOTP'], + standard_required_attributes: ['email'], + username_attributes: ['email'], + user_verification_types: ['email', 'phone_number'], + mfa_configuration: 'OPTIONAL', + + password_policy: { + min_length: 8, + require_lowercase: true, + require_numbers: true, + require_symbols: false, + require_uppercase: true, + }, + + unauthenticated_identities_enabled: true, + groups: [ + { + ADMINS: { + precedence: 0, + }, + }, + { + EDITORS: { + precedence: 1, + }, + }, + ], + }, + geo: { + aws_region: 'us-east-1', + maps: { + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }, + search_indices: { + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }, + geofence_collections: { + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }, + }, + data: { + url: 'testApiEndpoint', + aws_region: 'us-east-1', + api_key: 'testApiKey', + default_authorization_type: 'API_KEY', + authorization_types: ['API_KEY'], + }, + custom: { + output1: 'val1', + output2: 'val2', + }, + version: '1.5', + }; + + assert.deepStrictEqual(result, expectedClientConfig); + }); + void it('transforms backend output into client config for V1.4', async () => { const groups = [ { @@ -752,7 +947,7 @@ void describe('UnifiedClientConfigGenerator', () => { ); const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter, - ).getContributors('1.4'); //Generate with new configuration format + ).getContributors('1.5'); //Generate with new configuration format const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, configContributors, @@ -784,7 +979,7 @@ void describe('UnifiedClientConfigGenerator', () => { output1: 'val1', output2: 'val2', }, - version: '1.4', // The max version prevails + version: '1.5', // The max version prevails }; assert.deepStrictEqual(result, expectedClientConfig); diff --git a/packages/seed/src/auth-seed/config_reader.test.ts b/packages/seed/src/auth-seed/config_reader.test.ts index 4c34a293922..fec4c34938b 100644 --- a/packages/seed/src/auth-seed/config_reader.test.ts +++ b/packages/seed/src/auth-seed/config_reader.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test'; import assert from 'node:assert'; import { ConfigReader } from './config_reader.js'; import { generateClientConfig } from '@aws-amplify/client-config'; -import { AWSAmplifyBackendOutputs } from '../../../client-config/src/client-config-schema/client_config_v1.4.js'; +import { AWSAmplifyBackendOutputs } from '../../../client-config/src/client-config-schema/client_config_v1.5.js'; const testBackendId = 'testBackendId'; const testSandboxName = 'testSandboxName'; @@ -26,7 +26,7 @@ void describe('reading client configuration', () => { void describe('backendId exists', () => { const mockConfigGenerator = mock.fn(async () => Promise.resolve({ - version: '1.4', + version: '1.5', storage: { aws_region: testRegion, bucket_name: 'my-cool-bucket', @@ -52,7 +52,7 @@ void describe('reading client configuration', () => { void it('successfully reads client config if auth exists', async () => { mockConfigGenerator.mock.mockImplementationOnce(async () => Promise.resolve({ - version: '1.4', + version: '1.5', auth: { aws_region: testRegion, user_pool_id: testUserpoolId, diff --git a/packages/seed/src/auth-seed/config_reader.ts b/packages/seed/src/auth-seed/config_reader.ts index 37544bbee56..0f10a6acc18 100644 --- a/packages/seed/src/auth-seed/config_reader.ts +++ b/packages/seed/src/auth-seed/config_reader.ts @@ -31,7 +31,7 @@ export class ConfigReader { const backendId = JSON.parse(process.env.AMPLIFY_BACKEND_IDENTIFIER); const authConfig = ( - await this.generateClientConfiguration(backendId, '1.4') + await this.generateClientConfiguration(backendId, '1.5') ).auth; if (!authConfig) { throw new AmplifyUserError('MissingAuthError', { From 3653b4a48cf72e964691de72c76b7080c1a75359 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 10:38:26 -0700 Subject: [PATCH 65/93] small fixes v4 --- ...7b8b7701bf946b2f9ebba18b9226758f96746ce.md | 2 -- ...44148aa5bba1757f43705ba336474d190273260.md | 2 -- ...9b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md | 2 -- ...604db161b93db781303542fe438e6de6b6f9921.md | 2 -- ...604db161b93db781303542fe438e6de6b6f9921.md | 5 ---- .changeset/old-dodos-create.md | 7 ----- .changeset/puny-hornets-brush.md | 6 +++- packages/backend-geo/API.md | 15 +++++----- .../src/collection_construct.test.ts | 2 -- .../backend-geo/src/collection_construct.ts | 1 - .../src/collection_factory.test.ts | 8 +++--- .../backend-geo/src/collection_factory.ts | 3 +- .../src/geo_access_orchestrator.ts | 7 ++++- .../backend-geo/src/geo_outputs_aspect.ts | 7 +++-- packages/backend-geo/src/types.ts | 28 ++++++------------- 15 files changed, 38 insertions(+), 59 deletions(-) delete mode 100644 .changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md delete mode 100644 .changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md delete mode 100644 .changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md delete mode 100644 .changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md delete mode 100644 .changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md delete mode 100644 .changeset/old-dodos-create.md diff --git a/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md b/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md b/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md b/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md b/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md b/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md deleted file mode 100644 index de7647ff158..00000000000 --- a/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'create-amplify': patch ---- - -bump create amplify dependencies diff --git a/.changeset/old-dodos-create.md b/.changeset/old-dodos-create.md deleted file mode 100644 index 7cc98260461..00000000000 --- a/.changeset/old-dodos-create.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@aws-amplify/client-config': patch -'@aws-amplify/backend-geo': patch -'create-amplify': patch ---- - -Adding client configuration for auto-generated backend geo resource outputs. diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md index 23aae824a41..2bd78ffdb27 100644 --- a/.changeset/puny-hornets-brush.md +++ b/.changeset/puny-hornets-brush.md @@ -1,6 +1,10 @@ --- '@aws-amplify/backend-geo': minor '@aws-amplify/backend-output-schemas': patch +'@aws-amplify/client-config': patch +'create-amplify': patch --- -Introduces a new backend-geo package that includes new constructs for geo resources. Unit test cases for the functionality of these constructs and resources are provided as well. +- Introduces a new backend-geo package that includes new constructs for geo resources +- Unit test cases for the functionality of these constructs and resources are provided +- Client configurations and backend output storage strategies updated diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 947948b9c3f..a5bc68ed0d0 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -9,7 +9,6 @@ import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; -import { GeofenceCollection } from '@aws-cdk/aws-location-alpha'; import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; import { Policy } from 'aws-cdk-lib/aws-iam'; @@ -36,7 +35,10 @@ export type AmplifyMapFactoryProps = Omit; +export type AmplifyMapProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; // @public export type AmplifyPlaceFactoryProps = Omit & { @@ -44,12 +46,14 @@ export type AmplifyPlaceFactoryProps = Omit; +export type AmplifyPlaceProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; // @public export type CollectionResources = { policies: Policy[]; - collection: GeofenceCollection; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -104,9 +108,6 @@ export type PlaceResources = { region: string; }; -// @public (undocumented) -export const resourceActionRecord: Record; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts index 3d30b3a1c77..5c18191e80f 100644 --- a/packages/backend-geo/src/collection_construct.test.ts +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -135,7 +135,6 @@ void describe('AmplifyCollection', () => { isDefault: false, }); - assert.ok(amplifyCollection.resources.collection); assert.ok(amplifyCollection.resources.cfnResources.cfnCollection); }); @@ -246,7 +245,6 @@ void describe('AmplifyCollection', () => { assert.equal(collection.name, 'minimal'); assert.equal(collection.id, 'minimalCollection'); assert.equal(collection.isDefault, false); - assert.ok(collection.resources.collection); assert.ok(collection.resources.cfnResources.cfnCollection); const template = Template.fromStack(stack); diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index 564497e63bb..ff086d5828a 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -46,7 +46,6 @@ export class AmplifyCollection props.collectionProps, ); this.resources = { - collection: geofenceCollection, policies: this.policies, cfnResources: { cfnCollection: geofenceCollection.node.findChild( diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts index d718505caf7..c6f8bc24b44 100644 --- a/packages/backend-geo/src/collection_factory.test.ts +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -74,7 +74,7 @@ void describe('AmplifyCollectionFactory', () => { const collectionConstruct = collectionFactory.getInstance(getInstanceProps); const template = Template.fromStack( - Stack.of(collectionConstruct.resources.collection), + Stack.of(collectionConstruct.resources.cfnResources.cfnCollection), ); template.resourceCountIs('AWS::Location::GeofenceCollection', 1); @@ -110,7 +110,7 @@ void describe('AmplifyCollectionFactory', () => { const collectionConstruct = collectionFactory.getInstance(getInstanceProps); const template = Template.fromStack( - Stack.of(collectionConstruct.resources.collection), + Stack.of(collectionConstruct.resources.cfnResources.cfnCollection), ); // Check that the friendly name tag is applied @@ -137,7 +137,7 @@ void describe('AmplifyCollectionFactory', () => { customCollectionFactory.getInstance(getInstanceProps); const template = Template.fromStack( - Stack.of(collectionConstruct.resources.collection), + Stack.of(collectionConstruct.resources.cfnResources.cfnCollection), ); template.hasResourceProperties('AWS::Location::GeofenceCollection', { CollectionName: 'customCollection', @@ -155,7 +155,7 @@ void describe('AmplifyCollectionFactory', () => { assert.equal( collectionConstructFactory.stack, - Stack.of(collectionConstructFactory.resources.collection), + Stack.of(collectionConstructFactory.resources.cfnResources.cfnCollection), ); }); }); diff --git a/packages/backend-geo/src/collection_factory.ts b/packages/backend-geo/src/collection_factory.ts index 0a5e8a154c0..8cc7e46f01d 100644 --- a/packages/backend-geo/src/collection_factory.ts +++ b/packages/backend-geo/src/collection_factory.ts @@ -90,7 +90,8 @@ export class AmplifyCollectionGenerator amplifyCollection.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( - amplifyCollection.resources.collection.geofenceCollectionArn, + amplifyCollection.resources.cfnResources.cfnCollection + .attrCollectionArn, 'collection', amplifyCollection.name, ); diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 4e643c76322..1b85b8d1835 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -6,7 +6,6 @@ import { GeoAccessBuilder, GeoAccessGenerator, GeoResourceType, - resourceActionRecord, } from './types.js'; import { roleAccessBuilder as _roleAccessBuilder } from './access_builder.js'; import { GeoAccessPolicyFactory } from './geo_access_policy_factory.js'; @@ -130,3 +129,9 @@ export class GeoAccessOrchestratorFactory { ssmEnvironmentEntries, ); } + +const resourceActionRecord: Record = { + map: ['get'], + place: ['autocomplete', 'geocode', 'search'], + collection: ['create', 'read', 'update', 'delete', 'list'], +}; diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 812496e1da9..34c11bf53ec 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -83,7 +83,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { if (!defaultCollectionName && instance.isDefault) { // if no default exists and instance is default, mark it defaultCollectionName = - instance.resources.collection?.geofenceCollectionName; + instance.resources.cfnResources.cfnCollection.collectionName; } else if (instance.isDefault && defaultCollectionName) { // if default exists and instance is default (throw multiple defaults error) throw new AmplifyUserError('MultipleDefaultCollectionError', { @@ -98,7 +98,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { if (collectionCount === 1 && !defaultCollectionName) { // if no defaults and only one construct, instance assumed to be default defaultCollectionName = - currentNode.resources.collection?.geofenceCollectionName; + currentNode.resources.cfnResources.cfnCollection.collectionName; } else if (collectionCount > 1 && !defaultCollectionName) { // if multiple constructs with default collection, throw error throw new AmplifyUserError('NoDefaultCollectionError', { @@ -137,7 +137,8 @@ export class AmplifyGeoOutputsAspect implements IAspect { // Collect all collection names for the items array const collectionNames = collections.map( - (collection) => collection.resources.collection.geofenceCollectionName, + (collection) => + collection.resources.cfnResources.cfnCollection.collectionName, ); // Add geofence_collections as a single entry with all collections diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index ca2a7def351..6d3bf6e846d 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -4,10 +4,7 @@ import { ResourceAccessAcceptor, } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; -import { - GeofenceCollection, - GeofenceCollectionProps, -} from '@aws-cdk/aws-location-alpha'; +import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; @@ -74,15 +71,15 @@ export type AmplifyCollectionFactoryProps = Omit< access?: GeoAccessGenerator; }; -export type AmplifyMapProps = Omit< - AmplifyCollectionProps, - 'collectionProps' | 'isDefault' ->; +export type AmplifyMapProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; -export type AmplifyPlaceProps = Omit< - AmplifyCollectionProps, - 'collectionProps' | 'isDefault' ->; +export type AmplifyPlaceProps = { + name: string; + outputStorageStrategy?: BackendOutputStorageStrategy; +}; export type AmplifyCollectionProps = { name: string; @@ -117,7 +114,6 @@ export type PlaceResources = { */ export type CollectionResources = { policies: Policy[]; - collection: GeofenceCollection; cfnResources: { cfnCollection: CfnGeofenceCollection; }; @@ -152,10 +148,4 @@ export type GeoAccessDefinition = { // ----------------------------------- misc. types ---------------------------------------------- -export const resourceActionRecord: Record = { - map: ['get'], - place: ['autocomplete', 'geocode', 'search'], - collection: ['create', 'read', 'update', 'delete', 'list'], -}; - export type GeoResourceType = 'map' | 'place' | 'collection'; From a907f0a83dbb879b37f83a6cc8e16fda6f82d5d6 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 11:02:29 -0700 Subject: [PATCH 66/93] small fixes v5 --- packages/backend-geo/API.md | 5 +- .../src/collection_construct.test.ts | 51 ++----------------- .../backend-geo/src/collection_construct.ts | 10 ++-- .../src/collection_factory.test.ts | 12 +---- .../src/geo_outputs_aspect.test.ts | 13 ++--- packages/backend-geo/src/types.ts | 5 +- 6 files changed, 20 insertions(+), 76 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index a5bc68ed0d0..243054c6062 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -9,8 +9,8 @@ import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types'; -import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; +import * as kms from 'aws-cdk-lib/aws-kms'; import { Policy } from 'aws-cdk-lib/aws-iam'; import { ResourceAccessAcceptor } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; @@ -24,7 +24,8 @@ export type AmplifyCollectionFactoryProps = Omit; }; diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts index 5c18191e80f..8afb3e316c6 100644 --- a/packages/backend-geo/src/collection_construct.test.ts +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -16,9 +16,6 @@ void describe('AmplifyCollection', () => { void it('creates a geofence collection', () => { new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); const template = Template.fromStack(stack); @@ -28,9 +25,6 @@ void describe('AmplifyCollection', () => { void it('sets collection name correctly', () => { new AmplifyCollection(stack, 'testCollection', { name: 'myTestCollection', - collectionProps: { - geofenceCollectionName: 'myTestCollection', - }, isDefault: false, }); const template = Template.fromStack(stack); @@ -42,9 +36,6 @@ void describe('AmplifyCollection', () => { void it('sets isDefault property correctly when true', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: true, }); @@ -56,9 +47,6 @@ void describe('AmplifyCollection', () => { void it('sets isDefault property correctly when false', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -68,9 +56,6 @@ void describe('AmplifyCollection', () => { void it('defaults isDefault to false when not specified', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, }); assert.equal(collection.isDefault, false); @@ -79,9 +64,6 @@ void describe('AmplifyCollection', () => { void it('stores attribution data in stack', () => { new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -95,10 +77,7 @@ void describe('AmplifyCollection', () => { void it('sets collection description when provided', () => { new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - description: 'Test geofence collection for unit testing', - }, + collectionDescription: 'Test geofence collection for unit testing', isDefault: false, }); const template = Template.fromStack(stack); @@ -111,10 +90,7 @@ void describe('AmplifyCollection', () => { void it('sets KMS key when provided', () => { new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - kmsKey: new kms.Key(stack, 'testKey', {}), - }, + kmsKey: new kms.Key(stack, 'testKey', {}), isDefault: false, }); const template = Template.fromStack(stack); @@ -129,9 +105,6 @@ void describe('AmplifyCollection', () => { void it('exposes collection resource correctly', () => { const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -141,9 +114,6 @@ void describe('AmplifyCollection', () => { void it('exposes CFN resources for overrides', () => { const amplifyCollection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -158,9 +128,6 @@ void describe('AmplifyCollection', () => { void it('sets tags when provided via CFN resource', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -196,9 +163,6 @@ void describe('AmplifyCollection', () => { void it('can override collection properties', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -216,9 +180,6 @@ void describe('AmplifyCollection', () => { void it('can override KMS key via CFN resource', () => { const collection = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: { - geofenceCollectionName: 'testCollectionName', - }, isDefault: false, }); @@ -238,7 +199,6 @@ void describe('AmplifyCollection', () => { void it('creates collection with minimal required properties', () => { const collection = new AmplifyCollection(stack, 'minimalCollection', { name: 'minimal', - collectionProps: {}, isDefault: false, }); @@ -257,11 +217,8 @@ void describe('AmplifyCollection', () => { void it('creates collection with all optional properties', () => { const collection = new AmplifyCollection(stack, 'fullCollection', { name: 'fullFeatureCollection', - collectionProps: { - geofenceCollectionName: 'fullFeatureCollection', - description: 'A fully configured geofence collection', - kmsKey: new kms.Key(stack, 'testKey', {}), - }, + collectionDescription: 'A fully configured geofence collection', + kmsKey: new kms.Key(stack, 'testKey', {}), isDefault: true, }); diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index ff086d5828a..c8910d8be0c 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -40,11 +40,11 @@ export class AmplifyCollection this.isDefault = props.isDefault || false; this.stack = Stack.of(scope); - const geofenceCollection = new GeofenceCollection( - this, - id, - props.collectionProps, - ); + const geofenceCollection = new GeofenceCollection(this, id, { + geofenceCollectionName: props.name, + description: props.collectionDescription, + kmsKey: props.kmsKey, + }); this.resources = { policies: this.policies, cfnResources: { diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts index c6f8bc24b44..d59d01a70ac 100644 --- a/packages/backend-geo/src/collection_factory.test.ts +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -40,9 +40,6 @@ void describe('AmplifyCollectionFactory', () => { beforeEach(() => { collectionFactory = defineCollection({ name: 'testCollection', - collectionProps: { - geofenceCollectionName: 'testCollection', - }, }); const stack = createStackAndSetContext(); @@ -91,7 +88,6 @@ void describe('AmplifyCollectionFactory', () => { const collectionFactory = defineCollection({ name: '|$%#86430resource', - collectionProps: {}, }); assert.throws( () => @@ -127,10 +123,7 @@ void describe('AmplifyCollectionFactory', () => { void it('creates collection with custom collection properties', () => { const customCollectionFactory = defineCollection({ name: 'customCollection', - collectionProps: { - geofenceCollectionName: 'customCollection', - description: 'Custom test collection', - }, + collectionDescription: 'Custom test collection', }); const collectionConstruct = @@ -148,9 +141,6 @@ void describe('AmplifyCollectionFactory', () => { void it('verifies stack property exists and is equal to collection stack', () => { const collectionConstructFactory = defineCollection({ name: 'testCollection', - collectionProps: { - geofenceCollectionName: 'testCollection', - }, }).getInstance(getInstanceProps); assert.equal( diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index bf59bc23d73..c81d66c32c5 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -79,7 +79,6 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('both backend output entry and append list invoked with AmplifyCollection node', () => { const collectionNode = new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionProps: {}, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); aspect.visit(collectionNode); @@ -91,12 +90,11 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('output entry called once with multiple collections created', () => { new AmplifyCollection(stack, 'testCollection_1', { name: 'testCollection1', - collectionProps: {}, + isDefault: true, }); // set as default collection new AmplifyCollection(stack, 'testCollection_2', { name: 'testCollection2', - collectionProps: {}, }); const mapNode = new AmplifyMap(stack, 'testMap', { name: 'testMapResourceName', @@ -116,11 +114,10 @@ void describe('AmplifyGeoOutputsAspect', () => { const newNode = new AmplifyCollection( noDuplicateStack, 'testCollection2', - { name: 'testCollection_2', collectionProps: {} }, + { name: 'testCollection_2' }, ); new AmplifyCollection(noDuplicateStack, 'testCollection3', { name: 'testCollection_3', - collectionProps: {}, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); assert.throws( @@ -138,7 +135,7 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('throws if multiple default collections', () => { const node = new AmplifyCollection(stack, 'testCollection', { name: 'defaultCollection', - collectionProps: {}, + isDefault: true, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); @@ -146,7 +143,7 @@ void describe('AmplifyGeoOutputsAspect', () => { () => { new AmplifyCollection(stack, 'defaultCollection', { name: 'default_collection', - collectionProps: {}, + isDefault: true, }); aspect.visit(node); @@ -195,12 +192,10 @@ void describe('AmplifyGeoOutputsAspect', () => { new AmplifyPlace(stack, 'placeResource', { name: 'testPlaceIndex' }); new AmplifyCollection(stack, 'defaultCollection', { name: 'default_collection', - collectionProps: { geofenceCollectionName: 'newCollection' }, isDefault: true, }); new AmplifyCollection(stack, 'testCollection', { name: 'default_collection', - collectionProps: {}, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 6d3bf6e846d..dcbdce091a1 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -4,10 +4,10 @@ import { ResourceAccessAcceptor, } from '@aws-amplify/plugin-types'; import { GeoOutput } from '@aws-amplify/backend-output-schemas'; -import { GeofenceCollectionProps } from '@aws-cdk/aws-location-alpha'; import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; import { AmplifyUserErrorOptions } from '@aws-amplify/platform-core'; import { Policy } from 'aws-cdk-lib/aws-iam'; +import * as kms from 'aws-cdk-lib/aws-kms'; // ----------------------------------- factory properties ---------------------------------------------- @@ -83,7 +83,8 @@ export type AmplifyPlaceProps = { export type AmplifyCollectionProps = { name: string; - collectionProps: GeofenceCollectionProps; + collectionDescription?: string; + kmsKey?: kms.IKey; isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; }; From d923b8bc91413bcd23ae225074fceb5a2d43a63c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 14:14:07 -0700 Subject: [PATCH 67/93] API key names outputted and configuring amplify outputs --- .changeset/bitter-emus-sell.md | 8 ++++++-- .changeset/puny-hornets-brush.md | 10 ---------- packages/backend-geo/API.md | 2 +- packages/backend-geo/src/geo_outputs_aspect.ts | 11 ++++++++--- packages/backend-geo/src/map_factory.ts | 2 +- packages/backend-geo/src/map_resource.ts | 1 + packages/backend-geo/src/place_factory.ts | 2 +- packages/backend-geo/src/types.ts | 2 +- packages/backend-output-schemas/src/geo/v1.ts | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 .changeset/puny-hornets-brush.md diff --git a/.changeset/bitter-emus-sell.md b/.changeset/bitter-emus-sell.md index 0080d78b656..70fada9c3eb 100644 --- a/.changeset/bitter-emus-sell.md +++ b/.changeset/bitter-emus-sell.md @@ -1,5 +1,9 @@ --- -'@aws-amplify/backend-geo': patch +'@aws-amplify/backend-geo': minor +'@aws-amplify/backend-output-schemas': patch +'@aws-amplify/client-config': patch +'create-amplify': patch --- -Adding API key support for map and place resources as part of L3 Geo Construct. +- Introduces API Key support for AWS-Managed Geo Resource (i.e. Maps, Place Indices) +- Outputs API Key name to make it user-accessible for resource access diff --git a/.changeset/puny-hornets-brush.md b/.changeset/puny-hornets-brush.md deleted file mode 100644 index 2bd78ffdb27..00000000000 --- a/.changeset/puny-hornets-brush.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@aws-amplify/backend-geo': minor -'@aws-amplify/backend-output-schemas': patch -'@aws-amplify/client-config': patch -'create-amplify': patch ---- - -- Introduces a new backend-geo package that includes new constructs for geo resources -- Unit test cases for the functionality of these constructs and resources are provided -- Client configurations and backend output storage strategies updated diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 47d5a403d09..a7499a5b7a0 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -139,7 +139,7 @@ export type PlaceResources = { // @public (undocumented) export type ResourceOutputs = { name: string; - apiKey?: string; + key?: string; }; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 0118689d884..bb96348321b 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -160,7 +160,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { const mapOutputs: ResourceOutputs[] = maps.map((map): ResourceOutputs => { return { name: map.name, - apiKey: map.resources.cfnResources.cfnAPIKey?.ref, + key: map.resources.cfnResources.cfnAPIKey?.ref, }; }); @@ -168,7 +168,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { (place): ResourceOutputs => { return { name: place.name, - apiKey: place.resources.cfnResources.cfnAPIKey?.ref, + key: place.resources.cfnResources.cfnAPIKey?.ref, }; }, ); @@ -184,7 +184,12 @@ export class AmplifyGeoOutputsAspect implements IAspect { // Add maps as a single entry with all maps if (maps.length > 0 && defaultMap) - this.addOutput(outputStorageStrategy, 'map', defaultMap.name, mapOutputs); + this.addOutput( + outputStorageStrategy, + 'maps', + defaultMap.name, + mapOutputs, + ); // Add index as a single entry with all place indices if (places.length > 0 && defaultPlace) diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index 042717f7295..bf2f9ac5cdb 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -109,7 +109,7 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { 'No API key can be created for maps without access definitions defined for it.', resolution: 'Add at least one map action in the access definition.', }); - } else { + } else if (this.props.apiKeyProps) { amplifyMap.generateApiKey(mapActions); } diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index a029b4ae4be..b30860ecb73 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -58,6 +58,7 @@ export class AmplifyMap noExpiry: this.props.apiKeyProps?.noExpiry ?? true, allowMapsActions: actions, }); + this.resources.cfnResources.cfnAPIKey = this.resources.apiKey.node.findChild('Resource') as CfnAPIKey; }; diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index e7bef4c8d9e..8660f27325d 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -113,7 +113,7 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { 'No API key can be created for places without access definitions defined for it.', resolution: 'Add at least one place action in the access definition.', }); - } else { + } else if (this.props.apiKeyProps) { amplifyPlace.generateApiKey(placeActions); } diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index c21c31040d9..716f0069e7d 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -147,7 +147,7 @@ export type CollectionResources = { export type ResourceOutputs = { name: string; - apiKey?: string; + key?: string; }; // ----------------------------------- access definitions ---------------------------------------------- diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 54f527522cb..86cc066a0ee 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -7,7 +7,7 @@ const collectionConstructSchema = z.object({ const resourceItemSchema = z.object({ name: z.string(), - api_key: z.string().optional(), + key: z.string().optional(), }); const resourceSchema = z.object({ From 8fb285b79dd1c3146400e0b4ae05a39b93346250 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 15:43:47 -0700 Subject: [PATCH 68/93] small changes and test coverage --- packages/backend-geo/API.md | 3 - .../src/geo_access_orchestrator.test.ts | 90 +++++++++++++++++++ .../src/geo_outputs_aspect.test.ts | 4 +- packages/backend-geo/src/types.ts | 4 - 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index a7499a5b7a0..1932dce1caa 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -110,9 +110,6 @@ export type GeoApiActionType = AllowMapsAction | AllowPlacesAction; // @public (undocumented) export type GeoApiKeyProps = Omit; -// @public (undocumented) -export type GeoCollectionAccessGenerator = (allow: Omit) => GeoAccessDefinition[]; - // @public (undocumented) export type GeoResourceType = 'map' | 'place' | 'collection'; diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index 482138c17d4..925d6981569 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -524,6 +524,96 @@ void describe('GeoAccessOrchestrator', () => { ); }); + void it('throws for duplicate action on map resource', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['get', 'get'], // Invalid for map (valid for collection) + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo:us-east-1:123456789012:map/test-map', + 'map', + testResourceName, + ), + new AmplifyUserError('DuplicateActionFoundError', { + message: + 'Desired access action is duplicated for the specific map resource.', + resolution: + 'Remove all but one mentions of the get action for the specific map resource.', + }), + ); + }); + + void it('throws for duplicate action on place resource', () => { + const acceptResourceAccessMock = mock.fn(); + const geoAccessOrchestrator = + new GeoAccessOrchestratorFactory().getInstance( + () => [ + { + actions: ['search', 'search'], // Invalid for map (valid for collection) + getAccessAcceptors: [ + () => ({ + identifier: 'authenticatedUserIamRole', + acceptResourceAccess: acceptResourceAccessMock, + }), + ], + uniqueDefinitionValidators: [ + { + uniqueRoleToken: 'authenticated', + validationErrorOptions: { + message: 'Test error', + resolution: 'Test resolution', + }, + }, + ], + }, + ], + {} as unknown as ConstructFactoryGetInstanceProps, + stack, + ssmEnvironmentEntriesStub, + ); + + assert.throws( + () => + geoAccessOrchestrator.orchestrateGeoAccess( + 'arn:aws:geo:us-east-1:123456789012:place/test-place', + 'place', + testResourceName, + ), + new AmplifyUserError('DuplicateActionFoundError', { + message: + 'Desired access action is duplicated for the specific place resource.', + resolution: + 'Remove all but one mentions of the search action for the specific place resource.', + }), + ); + }); + void it('handles empty actions array', () => { const acceptResourceAccessMock = mock.fn(); const geoAccessOrchestrator = diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index 8e9a697dacb..9e84a1ef54e 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -328,8 +328,8 @@ void describe('AmplifyGeoOutputsAspect', () => { */ assert.ok( JSON.parse( - appendToBackendOutputListMock.mock.calls[1].arguments[1].payload.map, - ).items[0].apiKey.includes('TOKEN'), + appendToBackendOutputListMock.mock.calls[1].arguments[1].payload.maps, + ).items[0].key.includes('TOKEN'), ); assert.equal( diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 716f0069e7d..c7e22eb205e 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -156,10 +156,6 @@ export type GeoAccessGenerator = ( allow: GeoAccessBuilder, ) => GeoAccessDefinition[]; -export type GeoCollectionAccessGenerator = ( - allow: Omit, -) => GeoAccessDefinition[]; - export type GeoAccessBuilder = { authenticated: GeoActionBuilder; guest: GeoActionBuilder; From 4d06e21094f80b2b146c305bcb8d18db8d11c212 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 31 Jul 2025 16:05:39 -0700 Subject: [PATCH 69/93] small changes --- packages/backend-geo/API.md | 6 +----- packages/backend-geo/src/collection_construct.test.ts | 4 ++-- packages/backend-geo/src/collection_construct.ts | 2 +- packages/backend-geo/src/collection_factory.test.ts | 2 +- packages/backend-geo/src/map_factory.ts | 2 +- packages/backend-geo/src/map_resource.test.ts | 11 ----------- packages/backend-geo/src/map_resource.ts | 2 -- packages/backend-geo/src/place_factory.ts | 11 +++++------ packages/backend-geo/src/place_resource.test.ts | 11 ----------- packages/backend-geo/src/place_resource.ts | 2 -- packages/backend-geo/src/types.ts | 6 +----- 11 files changed, 12 insertions(+), 47 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 1932dce1caa..e7a02e262bf 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -29,7 +29,7 @@ export type AmplifyCollectionFactoryProps = Omit; @@ -115,8 +115,6 @@ export type GeoResourceType = 'map' | 'place' | 'collection'; // @public export type MapResources = { - region: string; - policies: Policy[]; apiKey?: ApiKey; cfnResources: { cfnAPIKey?: CfnAPIKey; @@ -125,8 +123,6 @@ export type MapResources = { // @public export type PlaceResources = { - region: string; - policies: Policy[]; apiKey?: ApiKey; cfnResources: { cfnAPIKey?: CfnAPIKey; diff --git a/packages/backend-geo/src/collection_construct.test.ts b/packages/backend-geo/src/collection_construct.test.ts index d5fd01bad62..bee4fb20970 100644 --- a/packages/backend-geo/src/collection_construct.test.ts +++ b/packages/backend-geo/src/collection_construct.test.ts @@ -77,7 +77,7 @@ void describe('AmplifyCollection', () => { void it('sets collection description when provided', () => { new AmplifyCollection(stack, 'testCollection', { name: 'testCollectionName', - collectionDescription: 'Test geofence collection for unit testing', + description: 'Test geofence collection for unit testing', isDefault: false, }); const template = Template.fromStack(stack); @@ -217,7 +217,7 @@ void describe('AmplifyCollection', () => { void it('creates collection with all optional properties', () => { const collection = new AmplifyCollection(stack, 'fullCollection', { name: 'fullFeatureCollection', - collectionDescription: 'A fully configured geofence collection', + description: 'A fully configured geofence collection', kmsKey: new kms.Key(stack, 'testKey', {}), isDefault: true, }); diff --git a/packages/backend-geo/src/collection_construct.ts b/packages/backend-geo/src/collection_construct.ts index e13272d3342..79a6ac7c1df 100644 --- a/packages/backend-geo/src/collection_construct.ts +++ b/packages/backend-geo/src/collection_construct.ts @@ -42,7 +42,7 @@ export class AmplifyCollection const geofenceCollection = new GeofenceCollection(this, id, { geofenceCollectionName: props.name, - description: props.collectionDescription, + description: props.description, kmsKey: props.kmsKey, }); this.resources = { diff --git a/packages/backend-geo/src/collection_factory.test.ts b/packages/backend-geo/src/collection_factory.test.ts index 48151b6cf8f..6c105e999bd 100644 --- a/packages/backend-geo/src/collection_factory.test.ts +++ b/packages/backend-geo/src/collection_factory.test.ts @@ -123,7 +123,7 @@ void describe('AmplifyCollectionFactory', () => { void it('creates collection with custom collection properties', () => { const customCollectionFactory = defineCollection({ name: 'customCollection', - collectionDescription: 'Custom test collection', + description: 'Custom test collection', access: (allow) => [allow.apiKey.to(['create'])], }); diff --git a/packages/backend-geo/src/map_factory.ts b/packages/backend-geo/src/map_factory.ts index bf2f9ac5cdb..f63ca17695f 100644 --- a/packages/backend-geo/src/map_factory.ts +++ b/packages/backend-geo/src/map_factory.ts @@ -93,7 +93,7 @@ export class AmplifyMapGenerator implements ConstructContainerEntryGenerator { Tags.of(amplifyMap).add(TagName.FRIENDLY_NAME, this.props.name); - amplifyMap.resources.policies = geoAccessOrchestrator.orchestrateGeoAccess( + geoAccessOrchestrator.orchestrateGeoAccess( amplifyMap.getResourceArn(), 'map', amplifyMap.name, diff --git a/packages/backend-geo/src/map_resource.test.ts b/packages/backend-geo/src/map_resource.test.ts index 5fe2ca05941..d0175571b9b 100644 --- a/packages/backend-geo/src/map_resource.test.ts +++ b/packages/backend-geo/src/map_resource.test.ts @@ -32,15 +32,6 @@ void describe('AmplifyMap', () => { assert.equal(map.name, 'myTestMap'); }); - void it('exposes map resources correctly', () => { - const map = new AmplifyMap(stack, 'testMap', { - name: 'testMapName', - }); - - assert.ok(map.resources); - assert.equal(typeof map.resources.region, 'string'); - }); - void it('returns correct resource ARN', () => { const map = new AmplifyMap(stack, 'testMap', { name: 'testMapName', @@ -100,8 +91,6 @@ void describe('AmplifyMap', () => { assert.equal(map.name, 'minimal'); assert.equal(map.id, 'minimalMap'); - assert.ok(map.resources); - assert.equal(map.resources.region, 'us-west-2'); assert.ok(map.stack); }); }); diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index b30860ecb73..1f335f8e3db 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -36,8 +36,6 @@ export class AmplifyMap this.props = props; this.resources = { - region: this.stack.region, - policies: this.policies, cfnResources: {}, }; diff --git a/packages/backend-geo/src/place_factory.ts b/packages/backend-geo/src/place_factory.ts index 8660f27325d..dae024ca2ca 100644 --- a/packages/backend-geo/src/place_factory.ts +++ b/packages/backend-geo/src/place_factory.ts @@ -96,12 +96,11 @@ export class AmplifyPlaceGenerator implements ConstructContainerEntryGenerator { [], ); - amplifyPlace.resources.policies = - geoAccessOrchestrator.orchestrateGeoAccess( - amplifyPlace.getResourceArn(), - 'place', - amplifyPlace.name, - ); + geoAccessOrchestrator.orchestrateGeoAccess( + amplifyPlace.getResourceArn(), + 'place', + amplifyPlace.name, + ); // orchestrateGeoAccess already called and ApiKey actions processed const placeActions = diff --git a/packages/backend-geo/src/place_resource.test.ts b/packages/backend-geo/src/place_resource.test.ts index f2d9ba7c985..80483adab29 100644 --- a/packages/backend-geo/src/place_resource.test.ts +++ b/packages/backend-geo/src/place_resource.test.ts @@ -32,15 +32,6 @@ void describe('AmplifyPlace', () => { assert.equal(place.name, 'myTestPlace'); }); - void it('exposes place resources correctly', () => { - const place = new AmplifyPlace(stack, 'testPlace', { - name: 'testPlaceName', - }); - - assert.ok(place.resources); - assert.equal(typeof place.resources.region, 'string'); - }); - void it('returns correct resource ARN', () => { const place = new AmplifyPlace(stack, 'testPlace', { name: 'testPlaceName', @@ -104,8 +95,6 @@ void describe('AmplifyPlace', () => { assert.equal(place.name, 'minimal'); assert.equal(place.id, 'minimalPlace'); - assert.ok(place.resources); - assert.equal(place.resources.region, 'us-west-2'); assert.ok(place.stack); }); }); diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index 1b1b624afa4..7e0b1e10255 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -36,8 +36,6 @@ export class AmplifyPlace this.props = props; this.resources = { - region: this.stack.region, - policies: this.policies, cfnResources: {}, }; diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index c7e22eb205e..699ceac5281 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -93,7 +93,7 @@ export type AmplifyPlaceProps = { export type AmplifyCollectionProps = { name: string; - collectionDescription?: string; + description?: string; kmsKey?: kms.IKey; isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; @@ -111,8 +111,6 @@ export type GeoApiKeyProps = Omit< * @param policies - access policies of the frontend-accessible map resource */ export type MapResources = { - region: string; - policies: Policy[]; apiKey?: ApiKey; cfnResources: { cfnAPIKey?: CfnAPIKey; @@ -124,8 +122,6 @@ export type MapResources = { * @param policies - access policies of the frontend-accessible place resource */ export type PlaceResources = { - region: string; - policies: Policy[]; apiKey?: ApiKey; cfnResources: { cfnAPIKey?: CfnAPIKey; From 633c6fb1f34caff1840e49505f546ee4be17e9c2 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 1 Aug 2025 09:37:27 -0700 Subject: [PATCH 70/93] updating package-lock --- package-lock.json | 125 ---------------------------------------------- 1 file changed, 125 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44715fb35c4..e1ba37c407a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,41 +128,6 @@ "node": ">=6.0.0" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@apollo/client": { "version": "3.13.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", @@ -30973,12 +30938,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -41302,47 +41261,6 @@ "node": ">=16" } }, - "node_modules/json-schema-to-typescript": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", - "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.5", - "@types/json-schema": "^7.0.15", - "@types/lodash": "^4.17.7", - "is-glob": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "prettier": "^3.2.5", - "tinyglobby": "^0.2.9" - }, - "bin": { - "json2ts": "dist/src/cli.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/json-schema-to-typescript/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/json-schema-to-typescript/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -46105,48 +46023,6 @@ "node": ">=16" } }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -50126,7 +50002,6 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", - "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { From 7f618b82cfb6d3f3486277ae59d190c55cdf8ae1 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 1 Aug 2025 09:38:33 -0700 Subject: [PATCH 71/93] updating package-lock --- package-lock.json | 125 ---------------------------------------------- 1 file changed, 125 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44715fb35c4..e1ba37c407a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,41 +128,6 @@ "node": ">=6.0.0" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@apollo/client": { "version": "3.13.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", @@ -30973,12 +30938,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -41302,47 +41261,6 @@ "node": ">=16" } }, - "node_modules/json-schema-to-typescript": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", - "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.5", - "@types/json-schema": "^7.0.15", - "@types/lodash": "^4.17.7", - "is-glob": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "prettier": "^3.2.5", - "tinyglobby": "^0.2.9" - }, - "bin": { - "json2ts": "dist/src/cli.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/json-schema-to-typescript/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/json-schema-to-typescript/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -46105,48 +46023,6 @@ "node": ">=16" } }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -50126,7 +50002,6 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", - "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { From 499583d1e9e49c3cf0e09e740e315e3259d5752e Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 4 Aug 2025 13:07:17 -0700 Subject: [PATCH 72/93] adding integration test for Geo construct and API key support testing --- package-lock.json | 1174 ++++++++++++----- package.json | 1 + packages/client-config/package.json | 4 +- packages/integration-tests/package.json | 1 + .../geo_api_key_support.deployment.test.ts | 4 + .../geo_api_key_support.sandbox.test.ts | 4 + .../geo_api_support_testing.ts | 177 +++ .../test-project-setup/test_project_base.ts | 2 +- .../amplify/auth/resource.ts | 7 + .../geo-api-key-support/amplify/backend.ts | 10 + .../amplify/geo/resource.ts | 32 + 11 files changed, 1074 insertions(+), 342 deletions(-) create mode 100644 packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts create mode 100644 packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts create mode 100644 packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts create mode 100644 packages/integration-tests/src/test-projects/geo-api-key-support/amplify/auth/resource.ts create mode 100644 packages/integration-tests/src/test-projects/geo-api-key-support/amplify/backend.ts create mode 100644 packages/integration-tests/src/test-projects/geo-api-key-support/amplify/geo/resource.ts diff --git a/package-lock.json b/package-lock.json index 765916f16c4..830171e5421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.750.0", "@aws-sdk/client-dynamodb": "^3.750.0", "@aws-sdk/client-iam": "^3.750.0", + "@aws-sdk/client-location": "^3.859.0", "@aws-sdk/client-s3": "^3.750.0", "@aws-sdk/client-ssm": "^3.750.0", "@changesets/cli": "^2.26.1", @@ -128,6 +129,44 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@apollo/client": { "version": "3.13.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", @@ -20512,6 +20551,547 @@ } } }, + "node_modules/@aws-sdk/client-location": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-location/-/client-location-3.859.0.tgz", + "integrity": "sha512-aej+Gu57SFXRBRQXptTjXppCHmXIuK+CRsZC73MprxlbOUsomhvgiETKPniIRw1rZhilcnxuCq68bDlImgM0ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/credential-provider-node": "3.859.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/client-sso": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.858.0.tgz", + "integrity": "sha512-iXuZQs4KH6a3Pwnt0uORalzAZ5EXRPr3lBYAsdNwkP8OYyoUz5/TE3BLyw7ceEh0rj4QKGNnNALYo1cDm0EV8w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/core": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.858.0.tgz", + "integrity": "sha512-iWm4QLAS+/XMlnecIU1Y33qbBr1Ju+pmWam3xVCPlY4CSptKpVY+2hXOnmg9SbHAX9C005fWhrIn51oDd00c9A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.7.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.858.0.tgz", + "integrity": "sha512-kZsGyh2BoSRguzlcGtzdLhw/l/n3KYAC+/l/H0SlsOq3RLHF6tO/cRdsLnwoix2bObChHUp03cex63o1gzdx/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.858.0.tgz", + "integrity": "sha512-GDnfYl3+NPJQ7WQQYOXEA489B212NinpcIDD7rpsB6IWUPo8yDjT5NceK4uUkIR3MFpNCGt9zd/z6NNLdB2fuQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.859.0.tgz", + "integrity": "sha512-KsccE1T88ZDNhsABnqbQj014n5JMDilAroUErFbGqu5/B3sXqUsYmG54C/BjvGTRUFfzyttK9lB9P9h6ddQ8Cw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/credential-provider-env": "3.858.0", + "@aws-sdk/credential-provider-http": "3.858.0", + "@aws-sdk/credential-provider-process": "3.858.0", + "@aws-sdk/credential-provider-sso": "3.859.0", + "@aws-sdk/credential-provider-web-identity": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.859.0.tgz", + "integrity": "sha512-ZRDB2xU5aSyTR/jDcli30tlycu6RFvQngkZhBs9Zoh2BiYXrfh2MMuoYuZk+7uD6D53Q2RIEldDHR9A/TPlRuA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.858.0", + "@aws-sdk/credential-provider-http": "3.858.0", + "@aws-sdk/credential-provider-ini": "3.859.0", + "@aws-sdk/credential-provider-process": "3.858.0", + "@aws-sdk/credential-provider-sso": "3.859.0", + "@aws-sdk/credential-provider-web-identity": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.858.0.tgz", + "integrity": "sha512-l5LJWZJMRaZ+LhDjtupFUKEC5hAjgvCRrOvV5T60NCUBOy0Ozxa7Sgx3x+EOwiruuoh3Cn9O+RlbQlJX6IfZIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.859.0.tgz", + "integrity": "sha512-BwAqmWIivhox5YlFRjManFF8GoTvEySPk6vsJNxDsmGsabY+OQovYxFIYxRCYiHzH7SFjd4Lcd+riJOiXNsvRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.858.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/token-providers": "3.859.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.858.0.tgz", + "integrity": "sha512-8iULWsH83iZDdUuiDsRb83M0NqIlXjlDbJUIddVsIrfWp4NmanKw77SV6yOZ66nuJjPsn9j7RDb9bfEPCy5SWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.858.0.tgz", + "integrity": "sha512-pC3FT/sRZ6n5NyXiTVu9dpf1D9j3YbJz3XmeOOwJqO/Mib2PZyIQktvNMPgwaC5KMVB1zWqS5bmCwxpMOnq0UQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@smithy/core": "^3.7.2", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/nested-clients": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.858.0.tgz", + "integrity": "sha512-ChdIj80T2whoWbovmO7o8ICmhEB2S9q4Jes9MBnKAPm69PexcJAK2dQC8yI4/iUP8b3+BHZoUPrYLWjBxIProQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/token-providers": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.859.0.tgz", + "integrity": "sha512-6P2wlvm9KBWOvRNn0Pt8RntnXg8fzOb5kEShvWsOsAocZeqKNaYbihum5/Onq1ZPoVtkdb++8eWDocDnM4k85Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/util-endpoints": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", + "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.858.0.tgz", + "integrity": "sha512-T1m05QlN8hFpx5/5duMjS8uFSK5e6EXP45HQRkZULVkL3DK+jMaxsnh3KLl5LjUoHn/19M4HM0wNUBhYp4Y2Yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-location/node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-location/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/@aws-sdk/client-personalize-events": { "version": "3.621.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-personalize-events/-/client-personalize-events-3.621.0.tgz", @@ -30953,6 +31533,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -32520,12 +33107,12 @@ "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32558,15 +33145,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { @@ -32574,17 +33161,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.3.0.tgz", - "integrity": "sha512-r6gvs5OfRq/w+9unPm7B3po4rmWaGh0CIL/OwHntGGux7+RhOOZLGuurbeMgWV6W55ZuyMTypJLeH0vn/ZRaWQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", + "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -32593,15 +33181,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" }, "engines": { @@ -32679,14 +33267,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", + "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, @@ -32710,12 +33298,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -32739,12 +33327,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32778,13 +33366,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32792,18 +33380,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.1.tgz", - "integrity": "sha512-z5RmcHxjvScL+LwEDU2mTNCOhgUs4lu5PGdF1K36IPRmUHhNFxNxgenSB7smyDiYD4vdKQ7CAZtG5cUErqib9w==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", + "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.3.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", + "@smithy/core": "^3.7.2", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { @@ -32811,18 +33399,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.1.tgz", - "integrity": "sha512-mBJOxn9aUYwcBUPQpKv9ifzrCn4EbhPUFguEZv3jB57YOMh0caS4P8HoLvUeNUI1nx4bIVH2SIbogbDfFI9DUA==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", + "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.1", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -32844,12 +33432,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32857,12 +33446,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32885,15 +33474,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", + "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32914,12 +33503,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32927,12 +33516,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, @@ -32941,12 +33530,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -32954,12 +33543,12 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0" + "@smithy/types": "^4.3.1" }, "engines": { "node": ">=18.0.0" @@ -32979,16 +33568,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.4", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -32998,17 +33587,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.1.tgz", - "integrity": "sha512-fbniZef60QdsBc4ZY0iyI8xbFHIiC/QRtPi66iE4ufjiE/aaz7AfUXzcWMkpO8r+QhLeNRIfmPchIG+3/QDZ6g==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", + "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.3.0", - "@smithy/middleware-endpoint": "^4.1.1", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", + "@smithy/core": "^3.7.2", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", "tslib": "^2.6.2" }, "engines": { @@ -33028,13 +33617,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -33105,14 +33694,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.9.tgz", - "integrity": "sha512-B8j0XsElvyhv6+5hlFf6vFV/uCSyLKcInpeXOGnOImX2mGXshE01RvPoGipTlRpIk53e6UfYj7WdDdgbVfXDZw==", + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", + "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.1", - "@smithy/types": "^4.2.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -33121,17 +33710,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.9.tgz", - "integrity": "sha512-wTDU8P/zdIf9DOpV5qm64HVgGRXvqjqB/fJZTEQbrz3s79JHM/E7XkMm/876Oq+ZLHJQgnXM9QHDo29dlM62eA==", + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", + "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.1", - "@smithy/types": "^4.2.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -33139,13 +33728,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -33165,12 +33754,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -33178,13 +33767,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -33192,14 +33781,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", + "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", @@ -41303,6 +41892,50 @@ "node": ">=16" } }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/json-schema-to-typescript/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -46059,6 +46692,51 @@ "node": ">=16" } }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -49362,6 +50040,7 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -49370,6 +50049,7 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", + "extraneous": true, "inBundle": true, "license": "ISC", "bin": { @@ -49436,19 +50116,6 @@ "@aws-cdk/cli-plugin-contract": "^2" } }, - "packages/backend-deployer/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/backend-deployer/node_modules/@smithy/core": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", @@ -49504,20 +50171,6 @@ "node": ">=18.0.0" } }, - "packages/backend-deployer/node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/backend-deployer/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", @@ -49534,46 +50187,6 @@ "node": ">=18.0.0" } }, - "packages/backend-deployer/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/backend-deployer/node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/backend-deployer/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/backend-deployer/node_modules/@smithy/service-error-classification": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", @@ -49586,33 +50199,6 @@ "node": ">=18.0.0" } }, - "packages/backend-deployer/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/backend-deployer/node_modules/@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/backend-deployer/node_modules/@smithy/util-retry": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", @@ -49992,7 +50578,8 @@ "devDependencies": { "@aws-sdk/types": "^3.734.0", "@aws-sdk/util-stream-node": "^3.374.0", - "aws-sdk-client-mock": "^4.1.0" + "aws-sdk-client-mock": "^4.1.0", + "json-schema-to-typescript": "^15.0.4" }, "peerDependencies": { "@aws-sdk/client-amplify": "^3.750.0", @@ -50231,6 +50818,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.750.0", "@aws-sdk/client-iam": "^3.750.0", "@aws-sdk/client-lambda": "^3.750.0", + "@aws-sdk/client-location": "^3.859.0", "@aws-sdk/client-s3": "^3.750.0", "@aws-sdk/client-sqs": "^3.750.0", "@aws-sdk/client-sts": "^3.750.0", @@ -52365,6 +52953,7 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -52373,6 +52962,7 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", + "extraneous": true, "inBundle": true, "license": "ISC", "bin": { @@ -52439,19 +53029,6 @@ "@aws-cdk/cli-plugin-contract": "^2" } }, - "packages/plugin-types/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/plugin-types/node_modules/@smithy/core": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", @@ -52507,20 +53084,6 @@ "node": ">=18.0.0" } }, - "packages/plugin-types/node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/plugin-types/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", @@ -52537,46 +53100,6 @@ "node": ">=18.0.0" } }, - "packages/plugin-types/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/plugin-types/node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/plugin-types/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/plugin-types/node_modules/@smithy/service-error-classification": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", @@ -52589,33 +53112,6 @@ "node": ">=18.0.0" } }, - "packages/plugin-types/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/plugin-types/node_modules/@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/plugin-types/node_modules/@smithy/util-retry": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", diff --git a/package.json b/package.json index 29cc9161332..8ab24a43d40 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.750.0", "@aws-sdk/client-dynamodb": "^3.750.0", "@aws-sdk/client-iam": "^3.750.0", + "@aws-sdk/client-location": "^3.859.0", "@aws-sdk/client-s3": "^3.750.0", "@aws-sdk/client-ssm": "^3.750.0", "@changesets/cli": "^2.26.1", diff --git a/packages/client-config/package.json b/packages/client-config/package.json index 86aee6bbe3e..5fd862e025b 100644 --- a/packages/client-config/package.json +++ b/packages/client-config/package.json @@ -29,13 +29,13 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", - "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { "@aws-sdk/types": "^3.734.0", "@aws-sdk/util-stream-node": "^3.374.0", - "aws-sdk-client-mock": "^4.1.0" + "aws-sdk-client-mock": "^4.1.0", + "json-schema-to-typescript": "^15.0.4" }, "peerDependencies": { "@aws-sdk/client-amplify": "^3.750.0", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 647f01e1c96..005e2c85ef3 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -26,6 +26,7 @@ "@aws-sdk/client-cognito-identity-provider": "^3.750.0", "@aws-sdk/client-iam": "^3.750.0", "@aws-sdk/client-lambda": "^3.750.0", + "@aws-sdk/client-location": "^3.859.0", "@aws-sdk/client-s3": "^3.750.0", "@aws-sdk/client-sqs": "^3.750.0", "@aws-sdk/client-sts": "^3.750.0", diff --git a/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts b/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts new file mode 100644 index 00000000000..772034efc73 --- /dev/null +++ b/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts @@ -0,0 +1,4 @@ +import { defineDeploymentTest } from './deployment.test.template.js'; +import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_support_testing.js'; + +defineDeploymentTest(new GeoAPIKeySupportTestProjectCreator()); diff --git a/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts b/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts new file mode 100644 index 00000000000..c062d62f5dd --- /dev/null +++ b/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts @@ -0,0 +1,4 @@ +import { defineSandboxTest } from './sandbox.test.template.js'; +import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_support_testing.js'; + +defineSandboxTest(new GeoAPIKeySupportTestProjectCreator()); diff --git a/packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts b/packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts new file mode 100644 index 00000000000..2cd88c698cf --- /dev/null +++ b/packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts @@ -0,0 +1,177 @@ +import { TestProjectBase } from './test_project_base.js'; +import fs from 'fs/promises'; +import { createEmptyAmplifyProject } from './create_empty_amplify_project.js'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { TestProjectCreator } from './test_project_creator.js'; +import { AmplifyClient } from '@aws-sdk/client-amplify'; +import { e2eToolingClientConfig } from '../e2e_tooling_client_config.js'; +import { BackendIdentifier } from '@aws-amplify/plugin-types'; +import { + ListGeofenceCollectionsCommand, + ListKeysCommand, + Location, + LocationClient, +} from '@aws-sdk/client-location'; +import { DeployedResourcesFinder } from '../find_deployed_resource.js'; +import assert from 'node:assert'; + +/** + * Creates test project for testing new Geo backend functionality with API key support. + */ +export class GeoAPIKeySupportTestProjectCreator implements TestProjectCreator { + readonly name = 'geo-api-key-support'; + + /** + * Initializes project creator object + */ + constructor( + private readonly cfnClient: CloudFormationClient = new CloudFormationClient( + e2eToolingClientConfig, + ), + private readonly amplifyClient: AmplifyClient = new AmplifyClient( + e2eToolingClientConfig, + ), + private readonly locationClient: LocationClient = new Location( + e2eToolingClientConfig, + ), + private readonly resourceFinder: DeployedResourcesFinder = new DeployedResourcesFinder(), + ) {} + + createProject = async (e2eProjectDir: string): Promise => { + const { projectName, projectRoot, projectAmplifyDir } = + await createEmptyAmplifyProject(this.name, e2eProjectDir); + + const project = new GeoAPIKeySupportTestProject( + projectName, + projectRoot, + projectAmplifyDir, + this.cfnClient, + this.amplifyClient, + this.locationClient, + this.resourceFinder, + ); + + await fs.cp( + project.sourceProjectAmplifyDirURL, + project.projectAmplifyDirPath, + { recursive: true }, + ); + + return project; + }; +} + +/** + * Creates project with geo and auth resources initialized + */ +export class GeoAPIKeySupportTestProject extends TestProjectBase { + readonly sourceProjectDirPath = '../../src/test-projects/geo-api-key-support'; + readonly sourceProjectAmplifyDirURL = new URL( + `${this.sourceProjectDirPath}/amplify`, + import.meta.url, + ); + + /** + * Creates an instance of a test project with Geofence Collections and API keys provisioned + */ + constructor( + name: string, + projectDirPath: string, + projectAmplifyDirPath: string, + cfnClient: CloudFormationClient, + amplifyClient: AmplifyClient, + private readonly locationClient: LocationClient, + private readonly resourceFinder: DeployedResourcesFinder, + ) { + super( + name, + projectDirPath, + projectAmplifyDirPath, + cfnClient, + amplifyClient, + ); + } + + /** + * Override implementation of post deployment assertions specific to Amplify Geo tests + */ + override async assertPostDeployment( + backendId: BackendIdentifier, + ): Promise { + await super.assertPostDeployment(backendId); // perform regular post-deployment tests + + const testCollection = await this.resourceFinder.findByBackendIdentifier( + backendId, + 'AWS::Location::GeofenceCollection', + (name) => name.includes('integrationTestCollection'), + ); + + const testAPIKey = await this.resourceFinder.findByBackendIdentifier( + backendId, + 'AWS::Location::APIKey', + (name) => name.includes('integrationTestIndexKey'), + ); + + assert.equal(testCollection.length, 1); + assert.equal(testAPIKey.length, 1); + + const expectedCollectionResponse = { + Entries: [ + { + CollectionName: 'integrationTestCollection', + Description: + 'This is a geofence collection setup for integration testing purposes.', + CreateTime: new Date(), + UpdateTime: new Date(), + }, + ], + }; + + const collectionResponse = await this.locationClient.send( + new ListGeofenceCollectionsCommand(), + ); + + const hasCollection = collectionResponse.Entries?.some( + (entry) => + entry.CollectionName === + expectedCollectionResponse.Entries[0].CollectionName && + entry.Description === expectedCollectionResponse.Entries[0].Description, + ); + + assert.ok( + hasCollection, + 'Expected collection not found in client response.', + ); + + const expectedKeysResponse = { + Entries: { + KeyName: 'integrationTestIndexKey', + CreateTime: new Date(), + UpdateTime: new Date(), + Restrictions: { + AllowActions: ['geo-places:Autocomplete'], + }, + }, + }; + + const keyResponse = await this.locationClient.send(new ListKeysCommand()); + + const hasKey = keyResponse.Entries?.some( + (entry) => + entry.KeyName === expectedKeysResponse.Entries.KeyName && + JSON.stringify(entry.Restrictions?.AllowActions) === + JSON.stringify( + expectedKeysResponse.Entries.Restrictions.AllowActions, + ), + ); + + assert.ok(hasKey, 'Expected key not found in client response.'); + } + + private verifyCollectionResponse = async (expectedResponse: unknown) => { + const locationResponse = await this.locationClient.send( + new ListGeofenceCollectionsCommand(), + ); + assert.equal(locationResponse, expectedResponse); + }; +} diff --git a/packages/integration-tests/src/test-project-setup/test_project_base.ts b/packages/integration-tests/src/test-project-setup/test_project_base.ts index dac0d2e3ee3..9baf4ed168f 100644 --- a/packages/integration-tests/src/test-project-setup/test_project_base.ts +++ b/packages/integration-tests/src/test-project-setup/test_project_base.ts @@ -235,7 +235,7 @@ export abstract class TestProjectBase { const schema = JSON.parse( await fsp.readFile( - './packages/client-config/src/client-config-schema/schema_v1.4.json', + './packages/client-config/src/client-config-schema/schema_v1.5.json', 'utf-8', ), ); diff --git a/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/auth/resource.ts b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/auth/resource.ts new file mode 100644 index 00000000000..cd2d8595084 --- /dev/null +++ b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/auth/resource.ts @@ -0,0 +1,7 @@ +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true, + }, +}); diff --git a/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/backend.ts b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/backend.ts new file mode 100644 index 00000000000..f705b7c9cc1 --- /dev/null +++ b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/backend.ts @@ -0,0 +1,10 @@ +import { defineBackend } from '@aws-amplify/backend'; +import { map, place, collection } from './geo/resource.js'; +import { auth } from './auth/resource.js'; + +defineBackend({ + auth, + map, + place, + collection, +}); diff --git a/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/geo/resource.ts b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/geo/resource.ts new file mode 100644 index 00000000000..53e49c99266 --- /dev/null +++ b/packages/integration-tests/src/test-projects/geo-api-key-support/amplify/geo/resource.ts @@ -0,0 +1,32 @@ +import { + defineMap, + defineCollection, + definePlace, +} from '@aws-amplify/backend-geo'; + +export const map = defineMap({ + name: 'integrationTestMap', + access: (allow) => [allow.authenticated.to(['get'])], +}); + +export const place = definePlace({ + name: 'integrationTestPlaceIndex', + access: (allow) => [ + allow.authenticated.to(['search']), + allow.guest.to(['geocode']), + allow.apiKey.to(['autocomplete']), + ], + apiKeyProps: { + apiKeyName: 'integrationTestIndexKey', + }, +}); + +export const collection = defineCollection({ + name: 'integrationTestCollection', + description: + 'This is a geofence collection setup for integration testing purposes.', + access: (allow) => [ + allow.authenticated.to(['create', 'read', 'update', 'delete']), + allow.guest.to(['read', 'list']), + ], +}); From 612a09797addbc85d957e33c410a81362895d0f0 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 4 Aug 2025 13:17:10 -0700 Subject: [PATCH 73/93] fixing package lock --- package-lock.json | 171 +++++++++++++++++++++++++--- packages/backend-geo/package.json | 4 +- packages/client-config/package.json | 4 +- 3 files changed, 159 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1ba37c407a..42afd0ffd78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,6 +128,44 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@apollo/client": { "version": "3.13.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", @@ -12814,19 +12852,6 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, - "node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.207.0-alpha.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", - "integrity": "sha512-kuV/iUstJwxuGxALLY4W/pxMsPxMrc67iC+vGN+XlretlFayPGIoK12RB+kV5ZsHPL5QK12V31e7s1LE9WH4/A==", - "license": "Apache-2.0", - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.207.0", - "constructs": "^10.0.0" - } - }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.86", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.86.tgz", @@ -30938,6 +30963,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -41261,6 +41293,50 @@ "node": ">=16" } }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/json-schema-to-typescript/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -46023,6 +46099,51 @@ "node": ">=16" } }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -49365,6 +49486,7 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -49373,6 +49495,7 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", + "extraneous": true, "inBundle": true, "license": "ISC", "bin": { @@ -49770,10 +49893,23 @@ "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.206.0", + "constructs": "^10.0.0" + } + }, + "packages/backend-geo/node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.206.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", + "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.207.0", + "aws-cdk-lib": "^2.206.0", "constructs": "^10.0.0" } }, @@ -50007,7 +50143,8 @@ "devDependencies": { "@aws-sdk/types": "^3.734.0", "@aws-sdk/util-stream-node": "^3.374.0", - "aws-sdk-client-mock": "^4.1.0" + "aws-sdk-client-mock": "^4.1.0", + "json-schema-to-typescript": "^15.0.4" }, "peerDependencies": { "@aws-sdk/client-amplify": "^3.750.0", @@ -52380,6 +52517,7 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -52388,6 +52526,7 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", + "extraneous": true, "inBundle": true, "license": "ISC", "bin": { diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index 85fe77c11e8..f484defd43a 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -19,13 +19,13 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.207.0", + "aws-cdk-lib": "^2.206.0", "constructs": "^10.0.0" }, "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" } } diff --git a/packages/client-config/package.json b/packages/client-config/package.json index 86aee6bbe3e..5fd862e025b 100644 --- a/packages/client-config/package.json +++ b/packages/client-config/package.json @@ -29,13 +29,13 @@ "@aws-amplify/model-generator": "^1.2.0", "@aws-amplify/platform-core": "^1.10.0", "@aws-amplify/plugin-types": "^1.10.0", - "json-schema-to-typescript": "^15.0.4", "zod": "3.25.17" }, "devDependencies": { "@aws-sdk/types": "^3.734.0", "@aws-sdk/util-stream-node": "^3.374.0", - "aws-sdk-client-mock": "^4.1.0" + "aws-sdk-client-mock": "^4.1.0", + "json-schema-to-typescript": "^15.0.4" }, "peerDependencies": { "@aws-sdk/client-amplify": "^3.750.0", From d566812db163874891318b0dfe5e38c4049371ec Mon Sep 17 00:00:00 2001 From: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:22:49 -0700 Subject: [PATCH 74/93] update form data to fix dependabot alert (#2940) --- .changeset/nice-hounds-fly.md | 2 + package-lock.json | 417 +++++++++++++++------------------- 2 files changed, 187 insertions(+), 232 deletions(-) create mode 100644 .changeset/nice-hounds-fly.md diff --git a/.changeset/nice-hounds-fly.md b/.changeset/nice-hounds-fly.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/nice-hounds-fly.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/package-lock.json b/package-lock.json index 42afd0ffd78..d68c1917b79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29303,9 +29303,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz", - "integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -29315,7 +29315,7 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~4.0.0", + "form-data": "~4.0.4", "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -34211,20 +34211,19 @@ "license": "ISC" }, "node_modules/@verdaccio/auth": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/auth/-/auth-8.0.0-next-8.15.tgz", - "integrity": "sha512-vAfzGOHbPcPXMCI90jqm/qSZ1OUBnOGzudZA3+YtherncdwADekvXbdJlZVclcfmZ0sRbfVG5Xpf88aETiwfcw==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/auth/-/auth-8.0.0-next-8.19.tgz", + "integrity": "sha512-VEWhj9Zs6qY2vzVpwY0uViPGxCPhiVo+g2WTLPNGa8avYz6sC8eiHZOJv3E22TKm/PaeSzclvSbMXiXP1bYuMA==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/config": "8.0.0-next-8.15", - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/loaders": "8.0.0-next-8.6", - "@verdaccio/signature": "8.0.0-next-8.7", - "@verdaccio/utils": "8.1.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/config": "8.0.0-next-8.19", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/loaders": "8.0.0-next-8.9", + "@verdaccio/signature": "8.0.0-next-8.11", + "debug": "4.4.1", "lodash": "4.17.21", - "verdaccio-htpasswd": "13.0.0-next-8.15" + "verdaccio-htpasswd": "13.0.0-next-8.19" }, "engines": { "node": ">=18" @@ -34260,15 +34259,14 @@ "license": "MIT" }, "node_modules/@verdaccio/config": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/config/-/config-8.0.0-next-8.15.tgz", - "integrity": "sha512-oEzQB+xeqaFAy54veMshqpt1hlZCYNkqoKuwkt7O8J43Fo/beiLluKUVneXckzi+pg1yvvGT7lNCbvuUQrxxQg==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/config/-/config-8.0.0-next-8.19.tgz", + "integrity": "sha512-08Mizx0f88A11Wxpx8EiK+mju0KroiaIRGZhV5h5+jWEKG3qucwTFNqR+29vRlPaGw1VmCEQ0+Vuaqeh03MlAA==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/utils": "8.1.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/core": "8.0.0-next-8.19", + "debug": "4.4.1", "js-yaml": "4.1.0", "lodash": "4.17.21", "minimatch": "7.4.6" @@ -34302,18 +34300,18 @@ } }, "node_modules/@verdaccio/core": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/core/-/core-8.0.0-next-8.15.tgz", - "integrity": "sha512-d5r/ZSkCri7s1hvV35enptquV5LJ81NqMYJnsjuryIUnvwn1yaqLlcdd6zIL08unzCSr7qDdUAdwGRRm6PKzng==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/core/-/core-8.0.0-next-8.19.tgz", + "integrity": "sha512-d/QzHToby2OTB5f4rw9koC0SidWvCkGPpvcQ/V8qh1ejoMtc/tO9OKe8lwUOxEvw31A2HaIBf0J4cFVIvrU31w==", "dev": true, "license": "MIT", "dependencies": { "ajv": "8.17.1", - "core-js": "3.40.0", "http-errors": "2.0.0", "http-status-codes": "2.3.0", + "minimatch": "10.0.1", "process-warning": "1.0.0", - "semver": "7.7.1" + "semver": "7.7.2" }, "engines": { "node": ">=18" @@ -34365,13 +34363,14 @@ } }, "node_modules/@verdaccio/loaders": { - "version": "8.0.0-next-8.6", - "resolved": "https://registry.npmjs.org/@verdaccio/loaders/-/loaders-8.0.0-next-8.6.tgz", - "integrity": "sha512-yuqD8uAZJcgzuNHjV6C438UNT5r2Ai9+SnUlO34AHZdWSYcluO3Zj5R3p5uf+C7YPCE31pUD27wBU74xVbUoBw==", + "version": "8.0.0-next-8.9", + "resolved": "https://registry.npmjs.org/@verdaccio/loaders/-/loaders-8.0.0-next-8.9.tgz", + "integrity": "sha512-y0EIx+jiJGnln7to0ILUsUdAZvpsZTFPF7toJ/VPJlyRZmYYCbNE2j+VK9GLZu8jPZy+H+GdEBF89QdAv6P99A==", "dev": true, "license": "MIT", "dependencies": { - "debug": "4.4.0", + "@verdaccio/core": "8.0.0-next-8.19", + "debug": "4.4.1", "lodash": "4.17.21" }, "engines": { @@ -34439,14 +34438,14 @@ "license": "MIT" }, "node_modules/@verdaccio/logger": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/logger/-/logger-8.0.0-next-8.15.tgz", - "integrity": "sha512-3gjhqvB87JUNDHFMN3YG4IweS9EgbCpAWZatNYzcoIWOoGiEaFQQBSM592CaFiI0yf8acyqWkNa1V95L1NMbRg==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/logger/-/logger-8.0.0-next-8.19.tgz", + "integrity": "sha512-rCZ4eUYJhCytezVeihYSs5Ct17UJvhCnQ4dAyuO/+JSeKY1Fpxgl+es8F6PU+o6iIVeN5qfFh55llJ2LwZ4gjg==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/logger-commons": "8.0.0-next-8.15", - "pino": "9.6.0" + "@verdaccio/logger-commons": "8.0.0-next-8.19", + "pino": "9.7.0" }, "engines": { "node": ">=18" @@ -34457,16 +34456,16 @@ } }, "node_modules/@verdaccio/logger-commons": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/logger-commons/-/logger-commons-8.0.0-next-8.15.tgz", - "integrity": "sha512-nF7VgBC2cl5ufv+mZEwBHHyZFb1F0+kVkuRMf3Tyk+Qp4lXilC9MRZ0oc+RnzsDbNmJ6IZHgHNbs6aJrNfaRGg==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/logger-commons/-/logger-commons-8.0.0-next-8.19.tgz", + "integrity": "sha512-4Zl5+JyPMC4Kiu9f93uzN9312vg3eh1BeOx0qGGXksJTPSebS+O8HG2530ApjamSkApOHvGs5x3GEsEKza9WOA==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/logger-prettify": "8.0.0-next-8.2", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/logger-prettify": "8.0.0-next-8.3", "colorette": "2.0.20", - "debug": "4.4.0" + "debug": "4.4.1" }, "engines": { "node": ">=18" @@ -34477,15 +34476,16 @@ } }, "node_modules/@verdaccio/logger-prettify": { - "version": "8.0.0-next-8.2", - "resolved": "https://registry.npmjs.org/@verdaccio/logger-prettify/-/logger-prettify-8.0.0-next-8.2.tgz", - "integrity": "sha512-WMXnZPLw5W7GSIQE8UOTp6kRIwiTmnnoJbMmyMlGiNrsRaFKTqk09R5tKUgOyGgd4Lu6yncLbmdm5UjAuwHf1Q==", + "version": "8.0.0-next-8.3", + "resolved": "https://registry.npmjs.org/@verdaccio/logger-prettify/-/logger-prettify-8.0.0-next-8.3.tgz", + "integrity": "sha512-mehQMpCtUbmW5dHpUwvi6hSYBdEXZjmDzI76hGacYKEKBwJk5aVXmrdYbYVyWtYlmegaxjLRMmA/iebXDEggYA==", "dev": true, "license": "MIT", "dependencies": { "colorette": "2.0.20", "dayjs": "1.11.13", "lodash": "4.17.21", + "on-exit-leak-free": "2.1.2", "pino-abstract-transport": "1.2.0", "sonic-boom": "3.8.1" }, @@ -34498,17 +34498,16 @@ } }, "node_modules/@verdaccio/middleware": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/middleware/-/middleware-8.0.0-next-8.15.tgz", - "integrity": "sha512-xsCLGbnhqcYwE8g/u9wxNLfDcESpr9ptEZ8Ce7frVTphU7kYIL48QCDPMzug7U+AguNtCq4v4zcoY1PaOQ8mgw==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/middleware/-/middleware-8.0.0-next-8.19.tgz", + "integrity": "sha512-KpfvMNvztaHK+6YrK3uhu6DbzwkQQnxtmNuesCwTQnMNmunwvMBCuwvNTvi1wip1GrmP8At4r3NSSz9ttPcHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/config": "8.0.0-next-8.15", - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/url": "13.0.0-next-8.15", - "@verdaccio/utils": "8.1.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/config": "8.0.0-next-8.19", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/url": "13.0.0-next-8.19", + "debug": "4.4.1", "express": "4.21.2", "express-rate-limit": "5.5.1", "lodash": "4.17.21", @@ -34534,9 +34533,9 @@ } }, "node_modules/@verdaccio/search-indexer": { - "version": "8.0.0-next-8.4", - "resolved": "https://registry.npmjs.org/@verdaccio/search-indexer/-/search-indexer-8.0.0-next-8.4.tgz", - "integrity": "sha512-Oea9m9VDqdlDPyQ9+fpcxZk0sIYH2twVK+YbykHpSYpjZRzz9hJfIr/uUwAgpWq83zAl2YDbz4zR3TjzjrWQig==", + "version": "8.0.0-next-8.5", + "resolved": "https://registry.npmjs.org/@verdaccio/search-indexer/-/search-indexer-8.0.0-next-8.5.tgz", + "integrity": "sha512-0GC2tJKstbPg/W2PZl2yE+hoAxffD2ZWilEnEYSEo2e9UQpNIy2zg7KE/uMUq2P72Vf5EVfVzb8jdaH4KV4QeA==", "dev": true, "license": "MIT", "engines": { @@ -34548,14 +34547,15 @@ } }, "node_modules/@verdaccio/signature": { - "version": "8.0.0-next-8.7", - "resolved": "https://registry.npmjs.org/@verdaccio/signature/-/signature-8.0.0-next-8.7.tgz", - "integrity": "sha512-sqP+tNzUtVIwUtt1ZHwYoxsO3roDLK7GW8c8Hj0SNaON+9ele9z4NBhaor+g95zRuLy6xtw/RgOvpyLon/vPrA==", + "version": "8.0.0-next-8.11", + "resolved": "https://registry.npmjs.org/@verdaccio/signature/-/signature-8.0.0-next-8.11.tgz", + "integrity": "sha512-mq5ZHB8a7JRMrbLATCZFVSUo0EtnsD9k7OZwEgdYEjzRYx3dQm93lY1smBAfluPfrcTeHRJY4W76Fdy/Or5JmA==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/config": "8.0.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/config": "8.0.0-next-8.19", + "@verdaccio/core": "8.0.0-next-8.19", + "debug": "4.4.1", "jsonwebtoken": "9.0.2" }, "engines": { @@ -34582,18 +34582,16 @@ } }, "node_modules/@verdaccio/tarball": { - "version": "13.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/tarball/-/tarball-13.0.0-next-8.15.tgz", - "integrity": "sha512-oSNmq7zD/iPIC5HpJbOJjW/lb0JV9k3jLwI6sG7kPgm+UIxVAOV4fKQOAD18HpHl/WjkF247NA6zGlAB94Habw==", + "version": "13.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/tarball/-/tarball-13.0.0-next-8.19.tgz", + "integrity": "sha512-BVdPcZj2EtneRY0HKqQTG02gF4q1YdxUqw+ZbOMdWRRFqNkCG/5PaaNhP/xh3UFk/VpNqmv4/OwyTv68c9186g==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/url": "13.0.0-next-8.15", - "@verdaccio/utils": "8.1.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/url": "13.0.0-next-8.19", + "debug": "4.4.1", "gunzip-maybe": "^1.4.2", - "lodash": "4.17.21", "tar-stream": "^3.1.7" }, "engines": { @@ -34605,21 +34603,21 @@ } }, "node_modules/@verdaccio/ui-theme": { - "version": "8.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/ui-theme/-/ui-theme-8.0.0-next-8.15.tgz", - "integrity": "sha512-k9BAM7rvbUqB2JPReNgXKUVTzBkdmIrNw0f6/7uyO+9cp7eVuarrPBnVF0oMc7jzVNBZRCpUksrhMZ0KwDZTpw==", + "version": "8.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/ui-theme/-/ui-theme-8.0.0-next-8.19.tgz", + "integrity": "sha512-grJ+8wqD+iE9cRHMQ9hYPj/IerW3pDELccoK6CLn1xYC+sunYVpnivkUv5HUmK6G0B64ptoYST1xFSjiiZXNKg==", "dev": true, "license": "MIT" }, "node_modules/@verdaccio/url": { - "version": "13.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/url/-/url-13.0.0-next-8.15.tgz", - "integrity": "sha512-1N/dGhw7cZMhupf/Xlm73beiL3oCaAiyo9DTumjF3aTcJnipVcT1hoj6CSj9RIX54824rUK9WVmo83dk0KPnjw==", + "version": "13.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/url/-/url-13.0.0-next-8.19.tgz", + "integrity": "sha512-QCtRu6gnE3FWh1CX11VdQfXDoNUYpiZm+767dUKkvo4LfEiKHrpIqq8ZeE37dOC5w9SBJdY1X6ddlIz8p4GTxA==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", - "debug": "4.4.0", + "@verdaccio/core": "8.0.0-next-8.19", + "debug": "4.4.1", "lodash": "4.17.21", "validator": "13.12.0" }, @@ -34632,16 +34630,15 @@ } }, "node_modules/@verdaccio/utils": { - "version": "8.1.0-next-8.15", - "resolved": "https://registry.npmjs.org/@verdaccio/utils/-/utils-8.1.0-next-8.15.tgz", - "integrity": "sha512-efg/bunOUMVXV+MlljJCrpuT+OQRrQS4wJyGL92B3epUGlgZ8DXs+nxN5v59v1a6AocAdSKwHgZS0g9txmBhOg==", + "version": "8.1.0-next-8.19", + "resolved": "https://registry.npmjs.org/@verdaccio/utils/-/utils-8.1.0-next-8.19.tgz", + "integrity": "sha512-mQoe1yUlYR4ujR65xVNAr4and102UNvAjlx6+IYyVPa7h3CZ0w5e8sRjlbYIXXL/qDI4RPVzfJVpquiwPkUCGg==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/core": "8.0.0-next-8.19", "lodash": "4.17.21", - "minimatch": "7.4.6", - "semver": "7.7.1" + "minimatch": "7.4.6" }, "engines": { "node": ">=18" @@ -34825,6 +34822,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -37031,9 +37041,9 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { @@ -37041,7 +37051,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -37359,9 +37369,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -37683,13 +37693,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -37730,9 +37733,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", "dependencies": { @@ -39517,15 +39520,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -40157,6 +40161,20 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-id": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz", @@ -41238,6 +41256,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", @@ -41480,13 +41505,13 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dev": true, "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -43135,9 +43160,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -43787,9 +43812,9 @@ } }, "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", + "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", "dev": true, "license": "MIT", "dependencies": { @@ -43798,7 +43823,7 @@ "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", @@ -43838,9 +43863,9 @@ } }, "node_modules/pino/node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", "dev": true, "funding": [ { @@ -44859,9 +44884,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -45385,13 +45410,6 @@ "node": ">=0.10.0" } }, - "node_modules/sshpk/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -46154,22 +46172,22 @@ } }, "node_modules/tldts": { - "version": "6.1.85", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.85.tgz", - "integrity": "sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.85" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.85", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.85.tgz", - "integrity": "sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, "license": "MIT" }, @@ -46903,32 +46921,32 @@ } }, "node_modules/verdaccio": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/verdaccio/-/verdaccio-6.1.2.tgz", - "integrity": "sha512-HQCquycSQkA+tKRVqMjIVRzmhzTciLfScvKIhhiwZZ9Qd13e2KJQTOdB7QrSacfJuPpl94TA5EZ7XmVRQKk3ag==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/verdaccio/-/verdaccio-6.1.6.tgz", + "integrity": "sha512-zUMMKW0hjtOaLIm1cY9AqA0bMjvuGtKJVolzXQacIW9PHTnTjcsWF2+sbNLBhVrHwo+FJ1DzdNVaTWXOBWZgiQ==", "dev": true, "license": "MIT", "dependencies": { - "@cypress/request": "3.0.8", - "@verdaccio/auth": "8.0.0-next-8.15", - "@verdaccio/config": "8.0.0-next-8.15", - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/loaders": "8.0.0-next-8.6", + "@cypress/request": "3.0.9", + "@verdaccio/auth": "8.0.0-next-8.19", + "@verdaccio/config": "8.0.0-next-8.19", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/loaders": "8.0.0-next-8.9", "@verdaccio/local-storage-legacy": "11.0.2", - "@verdaccio/logger": "8.0.0-next-8.15", - "@verdaccio/middleware": "8.0.0-next-8.15", - "@verdaccio/search-indexer": "8.0.0-next-8.4", - "@verdaccio/signature": "8.0.0-next-8.7", + "@verdaccio/logger": "8.0.0-next-8.19", + "@verdaccio/middleware": "8.0.0-next-8.19", + "@verdaccio/search-indexer": "8.0.0-next-8.5", + "@verdaccio/signature": "8.0.0-next-8.11", "@verdaccio/streams": "10.2.1", - "@verdaccio/tarball": "13.0.0-next-8.15", - "@verdaccio/ui-theme": "8.0.0-next-8.15", - "@verdaccio/url": "13.0.0-next-8.15", - "@verdaccio/utils": "8.1.0-next-8.15", + "@verdaccio/tarball": "13.0.0-next-8.19", + "@verdaccio/ui-theme": "8.0.0-next-8.19", + "@verdaccio/url": "13.0.0-next-8.19", + "@verdaccio/utils": "8.1.0-next-8.19", "async": "3.2.6", "clipanion": "4.0.0-rc.4", - "compression": "1.8.0", + "compression": "1.8.1", "cors": "2.8.5", - "debug": "4.4.0", + "debug": "4.4.1", "envinfo": "7.14.0", "express": "4.21.2", "handlebars": "4.7.8", @@ -46938,9 +46956,9 @@ "mime": "3.0.0", "mkdirp": "1.0.4", "pkginfo": "0.4.1", - "semver": "7.6.3", - "verdaccio-audit": "13.0.0-next-8.15", - "verdaccio-htpasswd": "13.0.0-next-8.15" + "semver": "7.7.2", + "verdaccio-audit": "13.0.0-next-8.19", + "verdaccio-htpasswd": "13.0.0-next-8.19" }, "bin": { "verdaccio": "bin/verdaccio" @@ -46954,14 +46972,14 @@ } }, "node_modules/verdaccio-audit": { - "version": "13.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/verdaccio-audit/-/verdaccio-audit-13.0.0-next-8.15.tgz", - "integrity": "sha512-Aeau0u0fi5l4PoSDyOV6glz2FDO9+ofvogJIELV4H6fhDXhgPc2MnoKuaUgOT//khESLle/a6YfcLY2/KNLs6g==", + "version": "13.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/verdaccio-audit/-/verdaccio-audit-13.0.0-next-8.19.tgz", + "integrity": "sha512-lF/5g4CwfhGzZIySeFYBCWXaBnIRQ02Q27gQ7OSS9KTQ9qnHXHbFrXjEAml2udQSNk6Z9jieNa5TufwgjR3Nyw==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/config": "8.0.0-next-8.15", - "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/config": "8.0.0-next-8.19", + "@verdaccio/core": "8.0.0-next-8.19", "express": "4.21.2", "https-proxy-agent": "5.0.1", "node-fetch": "cjs" @@ -46974,46 +46992,18 @@ "url": "https://opencollective.com/verdaccio" } }, - "node_modules/verdaccio-audit/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/verdaccio-audit/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/verdaccio-htpasswd": { - "version": "13.0.0-next-8.15", - "resolved": "https://registry.npmjs.org/verdaccio-htpasswd/-/verdaccio-htpasswd-13.0.0-next-8.15.tgz", - "integrity": "sha512-rQg5oZ/rReDAM4g4W68hvtzReTbM6vduvVtobHsQxhbtbotEuUjP6O8uaROYtgZ60giGva5Tub2SOm2T9Ln9Dw==", + "version": "13.0.0-next-8.19", + "resolved": "https://registry.npmjs.org/verdaccio-htpasswd/-/verdaccio-htpasswd-13.0.0-next-8.19.tgz", + "integrity": "sha512-XVkkJJKfXLVXC8E+7CLklnndkagZaFWXhGbYIxFYRJ+0bCff0VgUfmyXpwWJ9ADdOnMSqvUPFwMsx4LAhGxFvg==", "dev": true, "license": "MIT", "dependencies": { - "@verdaccio/core": "8.0.0-next-8.15", - "@verdaccio/file-locking": "13.0.0-next-8.3", + "@verdaccio/core": "8.0.0-next-8.19", + "@verdaccio/file-locking": "13.0.0-next-8.4", "apache-md5": "1.1.8", "bcryptjs": "2.4.3", - "core-js": "3.40.0", - "debug": "4.4.0", + "debug": "4.4.1", "http-errors": "2.0.0", "unix-crypt-td-js": "1.1.4" }, @@ -47026,9 +47016,9 @@ } }, "node_modules/verdaccio-htpasswd/node_modules/@verdaccio/file-locking": { - "version": "13.0.0-next-8.3", - "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-13.0.0-next-8.3.tgz", - "integrity": "sha512-Sugx6XYp8nEJ9SmBoEOExEIQQ0T0q8fcyc/afWdiSNDGWviqqSx2IriCvtMwKZrE4XG0BQo6bXO+A8AOOoo7KQ==", + "version": "13.0.0-next-8.4", + "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-13.0.0-next-8.4.tgz", + "integrity": "sha512-LzW8V7O65ZGvBbeK43JfHBjoRch3vopBx/HDnOwpA++XrfDTFt/e9Omk28Gu7wY/4BSunJGHMCIrs2EzYc9IVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -47065,19 +47055,6 @@ "node": ">=10.0.0" } }, - "node_modules/verdaccio/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -49834,18 +49811,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/backend-deployer/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "packages/backend-deployer/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -52853,18 +52818,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/plugin-types/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "packages/sandbox": { "name": "@aws-amplify/sandbox", "version": "2.1.2", From 6b01022807f8cd5157e3e9bd00bc52cefc3cda6c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 4 Aug 2025 17:00:59 -0700 Subject: [PATCH 75/93] fixing stringify issue from PR 1 and downgrading location alpha version --- .changeset/six-falcons-taste.md | 8 ++ package-lock.json | 4 +- packages/backend-geo/package.json | 4 +- .../src/geo_outputs_aspect.test.ts | 72 +++++++-------- .../backend-geo/src/geo_outputs_aspect.ts | 56 +++++------- .../client_config_contributor_v1.test.ts | 60 ++++++------- .../client_config_contributor_v1.ts | 6 +- .../unified_client_config_generator.test.ts | 90 ++++++++----------- 8 files changed, 132 insertions(+), 168 deletions(-) create mode 100644 .changeset/six-falcons-taste.md diff --git a/.changeset/six-falcons-taste.md b/.changeset/six-falcons-taste.md new file mode 100644 index 00000000000..67f55c3ad24 --- /dev/null +++ b/.changeset/six-falcons-taste.md @@ -0,0 +1,8 @@ +--- +'@aws-amplify/client-config': patch +'@aws-amplify/backend-geo': patch +'@aws-amplify/seed': patch +'@aws-amplify/backend-cli': patch +--- + +Optimizing output processing and removing redundancies within client outputs. diff --git a/package-lock.json b/package-lock.json index d68c1917b79..6b855fd316c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49858,10 +49858,10 @@ "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.195.0-alpha.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.206.0", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index f484defd43a..1f0dba82edf 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -19,13 +19,13 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.206.0", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" }, "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.206.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.195.0-alpha.0" } } diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index 9e84a1ef54e..ccc20ebfd73 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -43,7 +43,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output storage invoked with AmplifyPlace node', () => { @@ -54,7 +53,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output storage invoked with AmplifyCollection node', () => { @@ -65,7 +63,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple maps created', () => { @@ -81,7 +78,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple places created', () => { @@ -97,7 +93,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple collections created', () => { @@ -114,7 +109,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); }); @@ -256,7 +250,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( @@ -289,34 +282,19 @@ void describe('AmplifyGeoOutputsAspect', () => { isDefault: true, }); new AmplifyCollection(stack, 'testCollection', { - name: 'default_collection', + name: 'testCollection', }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 3); - - assert.equal( - appendToBackendOutputListMock.mock.calls[0].arguments.length, - 2, - ); - assert.equal( - appendToBackendOutputListMock.mock.calls[0].arguments[0], - 'AWS::Amplify::Geo', - ); - - assert.equal( - appendToBackendOutputListMock.mock.calls[1].arguments.length, - 2, - ); /** { version: '1', payload: { - map: JSON.stringify({ + maps: JSON.stringify({ default: "testMapResource", items: [{ name: "testMapResource", @@ -328,29 +306,39 @@ void describe('AmplifyGeoOutputsAspect', () => { */ assert.ok( JSON.parse( - appendToBackendOutputListMock.mock.calls[1].arguments[1].payload.maps, + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, ).items[0].key.includes('TOKEN'), ); assert.equal( - appendToBackendOutputListMock.mock.calls[2].arguments.length, - 2, + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, + ).items[0].name, + 'testMapResource', ); - assert.deepStrictEqual( - appendToBackendOutputListMock.mock.calls[2].arguments[1], - { - version: '1', - payload: { - searchIndices: JSON.stringify({ - default: 'testPlaceIndex', - items: [ - { - name: 'testPlaceIndex', - }, - ], - }), - }, - }, + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .searchIndices, + ).items[0].name, + 'testPlaceIndex', + ); + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofenceCollections, + ).items[0], + 'default_collection', + ); + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofenceCollections, + ).items[1], + 'testCollection', ); }); }); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index bb96348321b..0ec0f2aa057 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -21,7 +21,6 @@ export class AmplifyGeoOutputsAspect implements IAspect { * 3. store the outputs for all collections within outputStorageStrategy */ isGeoOutputProcessed: boolean = false; - defaultCollectionName: string | undefined = undefined; private readonly geoOutputStorageStrategy: BackendOutputStorageStrategy; /** * Constructs an instance of the AmplifyGeoOutputsAspect @@ -143,14 +142,6 @@ export class AmplifyGeoOutputsAspect implements IAspect { 'place', ) as AmplifyPlace; - // Add the main geo output entry with aws_region (snake_case to match schema) - outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { - version: '1', - payload: { - geoRegion: region, - }, - }); - // Collect all collection names for the items array const collectionNames = collections.map( (collection) => @@ -173,48 +164,49 @@ export class AmplifyGeoOutputsAspect implements IAspect { }, ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const geoPayload: any = { + geoRegion: region, // same type as payload of V1 schema + }; + // Add geofence_collections as a single entry with all collections if (collections.length > 0 && defaultCollection) - this.addOutput( - outputStorageStrategy, + this.addPayload( 'geofenceCollections', defaultCollection.resources.cfnResources.cfnCollection.collectionName, collectionNames, + geoPayload, ); // Add maps as a single entry with all maps if (maps.length > 0 && defaultMap) - this.addOutput( - outputStorageStrategy, - 'maps', - defaultMap.name, - mapOutputs, - ); + this.addPayload('maps', defaultMap.name, mapOutputs, geoPayload); // Add index as a single entry with all place indices if (places.length > 0 && defaultPlace) - this.addOutput( - outputStorageStrategy, + this.addPayload( 'searchIndices', defaultPlace.name, placeOutputs, + geoPayload, ); - } - private addOutput = ( - outputStorageStrategy: BackendOutputStorageStrategy, + // Add the main geo output entry with aws_region (snake_case to match schema) + outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: geoPayload, + }); + } + private addPayload = ( resourceKey: string, - defaultResource: string, - items: (string | ResourceOutputs)[], + defaultResourceName: string, + resources: (ResourceOutputs | string)[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + currentPayload: any, // type of payload from V1 schema ) => { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - [resourceKey]: JSON.stringify({ - default: defaultResource, - items: items, - }), - }, + currentPayload[resourceKey] = JSON.stringify({ + default: defaultResourceName, + items: resources, }); }; } diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 920326e0a4a..b1f2c8dbddc 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -619,12 +619,10 @@ void describe('geo client config contributor v1', () => { version: '1', payload: { geoRegion: 'us-west-2', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, }), @@ -648,34 +646,28 @@ void describe('geo client config contributor v1', () => { version: '1', payload: { geoRegion: 'us-west-2', - maps: JSON.stringify( - JSON.stringify({ - default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', - }, - ], - }), - ), - searchIndices: JSON.stringify( - JSON.stringify({ - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', - }, - ], - }), - ), - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + maps: JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + searchIndices: JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, }), diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index c29b0c8b315..d3596157944 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -516,7 +516,7 @@ export class GeoClientConfigContributor implements ClientConfigContributor { assignOutput = (resourcePayload: string | undefined) => { if (resourcePayload) { let resourceObj; - const firstParse = JSON.parse(JSON.parse(resourcePayload)); + const firstParse = JSON.parse(resourcePayload); if (firstParse && typeof firstParse === 'object' && firstParse.default) { resourceObj = firstParse; @@ -552,9 +552,7 @@ export class GeoClientConfigContributorV1 implements ClientConfigContributor { let geofenceCollectionsObj; if (geoOutput.payload.geofenceCollections) { - const firstParse = JSON.parse( - JSON.parse(geoOutput.payload.geofenceCollections), - ); + const firstParse = JSON.parse(geoOutput.payload.geofenceCollections); if ( firstParse && diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index a63708860ab..e849611e004 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -84,34 +84,28 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - maps: JSON.stringify( - JSON.stringify({ - default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', - }, - ], - }), - ), - searchIndices: JSON.stringify( - JSON.stringify({ - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', - }, - ], - }), - ), - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + maps: JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + searchIndices: JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -279,12 +273,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -433,12 +425,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -573,12 +563,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -701,12 +689,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { From d2a01fb93cc3d88a5cde764949b71ebac2d79776 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 4 Aug 2025 17:00:59 -0700 Subject: [PATCH 76/93] adding new output stringify algorithm --- .changeset/six-falcons-taste.md | 8 ++ package-lock.json | 4 - .../src/geo_outputs_aspect.test.ts | 72 +++++++-------- .../backend-geo/src/geo_outputs_aspect.ts | 56 +++++------- .../client_config_contributor_v1.test.ts | 60 ++++++------- .../client_config_contributor_v1.ts | 6 +- .../unified_client_config_generator.test.ts | 90 ++++++++----------- 7 files changed, 128 insertions(+), 168 deletions(-) create mode 100644 .changeset/six-falcons-taste.md diff --git a/.changeset/six-falcons-taste.md b/.changeset/six-falcons-taste.md new file mode 100644 index 00000000000..67f55c3ad24 --- /dev/null +++ b/.changeset/six-falcons-taste.md @@ -0,0 +1,8 @@ +--- +'@aws-amplify/client-config': patch +'@aws-amplify/backend-geo': patch +'@aws-amplify/seed': patch +'@aws-amplify/backend-cli': patch +--- + +Optimizing output processing and removing redundancies within client outputs. diff --git a/package-lock.json b/package-lock.json index 830171e5421..ede39879716 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50040,7 +50040,6 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -50049,7 +50048,6 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", - "extraneous": true, "inBundle": true, "license": "ISC", "bin": { @@ -52953,7 +52951,6 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -52962,7 +52959,6 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", - "extraneous": true, "inBundle": true, "license": "ISC", "bin": { diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index 9e84a1ef54e..ccc20ebfd73 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -43,7 +43,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output storage invoked with AmplifyPlace node', () => { @@ -54,7 +53,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output storage invoked with AmplifyCollection node', () => { @@ -65,7 +63,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple maps created', () => { @@ -81,7 +78,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(mapNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple places created', () => { @@ -97,7 +93,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(placeNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); void it('output entry called once with multiple collections created', () => { @@ -114,7 +109,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(collectionNode); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); }); }); @@ -256,7 +250,6 @@ void describe('AmplifyGeoOutputsAspect', () => { aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 1); assert.equal(addBackendOutputEntryMock.mock.calls[0].arguments.length, 2); assert.equal( @@ -289,34 +282,19 @@ void describe('AmplifyGeoOutputsAspect', () => { isDefault: true, }); new AmplifyCollection(stack, 'testCollection', { - name: 'default_collection', + name: 'testCollection', }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); aspect.visit(node); assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - assert.equal(appendToBackendOutputListMock.mock.callCount(), 3); - - assert.equal( - appendToBackendOutputListMock.mock.calls[0].arguments.length, - 2, - ); - assert.equal( - appendToBackendOutputListMock.mock.calls[0].arguments[0], - 'AWS::Amplify::Geo', - ); - - assert.equal( - appendToBackendOutputListMock.mock.calls[1].arguments.length, - 2, - ); /** { version: '1', payload: { - map: JSON.stringify({ + maps: JSON.stringify({ default: "testMapResource", items: [{ name: "testMapResource", @@ -328,29 +306,39 @@ void describe('AmplifyGeoOutputsAspect', () => { */ assert.ok( JSON.parse( - appendToBackendOutputListMock.mock.calls[1].arguments[1].payload.maps, + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, ).items[0].key.includes('TOKEN'), ); assert.equal( - appendToBackendOutputListMock.mock.calls[2].arguments.length, - 2, + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, + ).items[0].name, + 'testMapResource', ); - assert.deepStrictEqual( - appendToBackendOutputListMock.mock.calls[2].arguments[1], - { - version: '1', - payload: { - searchIndices: JSON.stringify({ - default: 'testPlaceIndex', - items: [ - { - name: 'testPlaceIndex', - }, - ], - }), - }, - }, + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .searchIndices, + ).items[0].name, + 'testPlaceIndex', + ); + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofenceCollections, + ).items[0], + 'default_collection', + ); + + assert.equal( + JSON.parse( + addBackendOutputEntryMock.mock.calls[0].arguments[1].payload + .geofenceCollections, + ).items[1], + 'testCollection', ); }); }); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index bb96348321b..0ec0f2aa057 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -21,7 +21,6 @@ export class AmplifyGeoOutputsAspect implements IAspect { * 3. store the outputs for all collections within outputStorageStrategy */ isGeoOutputProcessed: boolean = false; - defaultCollectionName: string | undefined = undefined; private readonly geoOutputStorageStrategy: BackendOutputStorageStrategy; /** * Constructs an instance of the AmplifyGeoOutputsAspect @@ -143,14 +142,6 @@ export class AmplifyGeoOutputsAspect implements IAspect { 'place', ) as AmplifyPlace; - // Add the main geo output entry with aws_region (snake_case to match schema) - outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { - version: '1', - payload: { - geoRegion: region, - }, - }); - // Collect all collection names for the items array const collectionNames = collections.map( (collection) => @@ -173,48 +164,49 @@ export class AmplifyGeoOutputsAspect implements IAspect { }, ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const geoPayload: any = { + geoRegion: region, // same type as payload of V1 schema + }; + // Add geofence_collections as a single entry with all collections if (collections.length > 0 && defaultCollection) - this.addOutput( - outputStorageStrategy, + this.addPayload( 'geofenceCollections', defaultCollection.resources.cfnResources.cfnCollection.collectionName, collectionNames, + geoPayload, ); // Add maps as a single entry with all maps if (maps.length > 0 && defaultMap) - this.addOutput( - outputStorageStrategy, - 'maps', - defaultMap.name, - mapOutputs, - ); + this.addPayload('maps', defaultMap.name, mapOutputs, geoPayload); // Add index as a single entry with all place indices if (places.length > 0 && defaultPlace) - this.addOutput( - outputStorageStrategy, + this.addPayload( 'searchIndices', defaultPlace.name, placeOutputs, + geoPayload, ); - } - private addOutput = ( - outputStorageStrategy: BackendOutputStorageStrategy, + // Add the main geo output entry with aws_region (snake_case to match schema) + outputStorageStrategy.addBackendOutputEntry(geoOutputKey, { + version: '1', + payload: geoPayload, + }); + } + private addPayload = ( resourceKey: string, - defaultResource: string, - items: (string | ResourceOutputs)[], + defaultResourceName: string, + resources: (ResourceOutputs | string)[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + currentPayload: any, // type of payload from V1 schema ) => { - outputStorageStrategy.appendToBackendOutputList(geoOutputKey, { - version: '1', - payload: { - [resourceKey]: JSON.stringify({ - default: defaultResource, - items: items, - }), - }, + currentPayload[resourceKey] = JSON.stringify({ + default: defaultResourceName, + items: resources, }); }; } diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 920326e0a4a..b1f2c8dbddc 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -619,12 +619,10 @@ void describe('geo client config contributor v1', () => { version: '1', payload: { geoRegion: 'us-west-2', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, }), @@ -648,34 +646,28 @@ void describe('geo client config contributor v1', () => { version: '1', payload: { geoRegion: 'us-west-2', - maps: JSON.stringify( - JSON.stringify({ - default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', - }, - ], - }), - ), - searchIndices: JSON.stringify( - JSON.stringify({ - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', - }, - ], - }), - ), - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + maps: JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + searchIndices: JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, }), diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index c29b0c8b315..d3596157944 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -516,7 +516,7 @@ export class GeoClientConfigContributor implements ClientConfigContributor { assignOutput = (resourcePayload: string | undefined) => { if (resourcePayload) { let resourceObj; - const firstParse = JSON.parse(JSON.parse(resourcePayload)); + const firstParse = JSON.parse(resourcePayload); if (firstParse && typeof firstParse === 'object' && firstParse.default) { resourceObj = firstParse; @@ -552,9 +552,7 @@ export class GeoClientConfigContributorV1 implements ClientConfigContributor { let geofenceCollectionsObj; if (geoOutput.payload.geofenceCollections) { - const firstParse = JSON.parse( - JSON.parse(geoOutput.payload.geofenceCollections), - ); + const firstParse = JSON.parse(geoOutput.payload.geofenceCollections); if ( firstParse && diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index a63708860ab..e849611e004 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -84,34 +84,28 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - maps: JSON.stringify( - JSON.stringify({ - default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', - }, - ], - }), - ), - searchIndices: JSON.stringify( - JSON.stringify({ - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', - }, - ], - }), - ), - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + maps: JSON.stringify({ + default: 'defaultMap', + items: [ + { + name: 'defaultMap', + key: 'defaultKey', + }, + ], + }), + searchIndices: JSON.stringify({ + default: 'defaultPlace', + items: [ + { + name: 'defaultIndex', + key: 'defaultKey', + }, + ], + }), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -279,12 +273,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -433,12 +425,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -573,12 +563,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { @@ -701,12 +689,10 @@ void describe('UnifiedClientConfigGenerator', () => { version: '1', payload: { geoRegion: 'us-east-1', - geofenceCollections: JSON.stringify( - JSON.stringify({ - default: 'defaultCollection', - items: ['defaultCollection', 'testCollection'], - }), - ), + geofenceCollections: JSON.stringify({ + default: 'defaultCollection', + items: ['defaultCollection', 'testCollection'], + }), }, }, [customOutputKey]: { From 45b2ffa45979ddbb8a0d3f014d8bd679e9d939ac Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Tue, 5 Aug 2025 13:28:24 -0700 Subject: [PATCH 77/93] fix to bumping cdk-lib deps to 2.195 --- package-lock.json | 38 +++++++++---------- packages/ai-constructs/package.json | 2 +- packages/auth-construct/package.json | 2 +- packages/backend-ai/package.json | 2 +- packages/backend-auth/package.json | 2 +- packages/backend-data/package.json | 2 +- packages/backend-deployer/package.json | 2 +- packages/backend-function/package.json | 2 +- packages/backend-output-storage/package.json | 2 +- .../backend-platform-test-stubs/package.json | 2 +- packages/backend-storage/package.json | 2 +- packages/backend/package.json | 2 +- packages/cli/package.json | 2 +- packages/integration-tests/package.json | 2 +- packages/platform-core/package.json | 2 +- packages/plugin-types/package.json | 2 +- 16 files changed, 32 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b855fd316c..5499395acd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47483,7 +47483,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49331,7 +49331,7 @@ "@aws-sdk/util-arn-parser": "^3.723.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49359,7 +49359,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49376,7 +49376,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49399,7 +49399,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49422,7 +49422,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49440,7 +49440,7 @@ "tsx": "4.19.4" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "typescript": "^5.0.0" } }, @@ -49463,7 +49463,6 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -49472,7 +49471,6 @@ }, "packages/backend-deployer/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", - "extraneous": true, "inBundle": true, "license": "ISC", "bin": { @@ -49846,7 +49844,7 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49866,7 +49864,7 @@ } }, "packages/backend-geo/node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.206.0-alpha.0", + "version": "2.195.0-alpha.0", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", "license": "Apache-2.0", @@ -49874,7 +49872,7 @@ "node": ">= 14.15.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.206.0", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49899,7 +49897,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1" + "aws-cdk-lib": "^2.195.0" } }, "packages/backend-platform-test-stubs": { @@ -49908,7 +49906,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.10.1", - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49939,7 +49937,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -49993,7 +49991,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.189.1" + "aws-cdk-lib": "^2.195.0" } }, "packages/cli-core": { @@ -50359,7 +50357,7 @@ "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", "aws-cdk": "^2.1005.0", - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0", "execa": "^9.5.1", "fs-extra": "^11.1.1", @@ -52446,7 +52444,7 @@ "@types/uuid": "10.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -52459,7 +52457,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } }, @@ -52482,7 +52480,6 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -52491,7 +52488,6 @@ }, "packages/plugin-types/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", - "extraneous": true, "inBundle": true, "license": "ISC", "bin": { diff --git a/packages/ai-constructs/package.json b/packages/ai-constructs/package.json index e954435eef7..5397239278f 100644 --- a/packages/ai-constructs/package.json +++ b/packages/ai-constructs/package.json @@ -41,7 +41,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/auth-construct/package.json b/packages/auth-construct/package.json index 4225631a24f..412d5ff5d90 100644 --- a/packages/auth-construct/package.json +++ b/packages/auth-construct/package.json @@ -25,7 +25,7 @@ "@aws-sdk/util-arn-parser": "^3.723.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-ai/package.json b/packages/backend-ai/package.json index 51d3de9605a..fe0200eff95 100644 --- a/packages/backend-ai/package.json +++ b/packages/backend-ai/package.json @@ -30,7 +30,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-auth/package.json b/packages/backend-auth/package.json index 56a0074c1de..027b3e074c1 100644 --- a/packages/backend-auth/package.json +++ b/packages/backend-auth/package.json @@ -33,7 +33,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-data/package.json b/packages/backend-data/package.json index 802577a0546..97ac3796b0a 100644 --- a/packages/backend-data/package.json +++ b/packages/backend-data/package.json @@ -24,7 +24,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" }, "dependencies": { diff --git a/packages/backend-deployer/package.json b/packages/backend-deployer/package.json index 02c60a799ca..9b956437b16 100644 --- a/packages/backend-deployer/package.json +++ b/packages/backend-deployer/package.json @@ -28,7 +28,7 @@ "minimatch": "10.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "typescript": "^5.0.0" }, "overrides": { diff --git a/packages/backend-function/package.json b/packages/backend-function/package.json index 95b2cc40a0c..66e1c6ca1e5 100644 --- a/packages/backend-function/package.json +++ b/packages/backend-function/package.json @@ -39,7 +39,7 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-output-storage/package.json b/packages/backend-output-storage/package.json index 0eab344305c..3d9d00b4645 100644 --- a/packages/backend-output-storage/package.json +++ b/packages/backend-output-storage/package.json @@ -24,6 +24,6 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1" + "aws-cdk-lib": "^2.195.0" } } diff --git a/packages/backend-platform-test-stubs/package.json b/packages/backend-platform-test-stubs/package.json index 65de170eac2..05393594838 100644 --- a/packages/backend-platform-test-stubs/package.json +++ b/packages/backend-platform-test-stubs/package.json @@ -17,7 +17,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.10.1", - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-storage/package.json b/packages/backend-storage/package.json index a34da7853db..33516135c02 100644 --- a/packages/backend-storage/package.json +++ b/packages/backend-storage/package.json @@ -28,7 +28,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 59d60ae879e..fc6413742e6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -44,7 +44,7 @@ "@aws-sdk/client-amplify": "^3.750.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" }, "devDependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 27aa19ec8a6..09b3815f6a3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -65,7 +65,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.189.1" + "aws-cdk-lib": "^2.195.0" }, "devDependencies": { "@types/envinfo": "^7.8.3", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 647f01e1c96..a9356e2f3ca 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -37,7 +37,7 @@ "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", "aws-cdk": "^2.1005.0", - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0", "execa": "^9.5.1", "fs-extra": "^11.1.1", diff --git a/packages/platform-core/package.json b/packages/platform-core/package.json index 14dab379a20..e5acdd4f902 100644 --- a/packages/platform-core/package.json +++ b/packages/platform-core/package.json @@ -42,7 +42,7 @@ "zod": "3.25.17" }, "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0" } } diff --git a/packages/plugin-types/package.json b/packages/plugin-types/package.json index d23d8bc0fa3..b2ff2264fd4 100644 --- a/packages/plugin-types/package.json +++ b/packages/plugin-types/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.189.1", + "aws-cdk-lib": "^2.195.0", "constructs": "^10.0.0", "@aws-sdk/types": "^3.734.0" }, From 7a03148a936d61c14d296c32a3eda4631fccb393 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 6 Aug 2025 16:48:42 -0700 Subject: [PATCH 78/93] bumping all baselines to 2.207 --- .changeset/fluffy-icons-wave.md | 18 +++++++++ package-lock.json | 40 +++++++++---------- packages/ai-constructs/package.json | 2 +- packages/auth-construct/package.json | 2 +- packages/backend-ai/package.json | 2 +- packages/backend-auth/package.json | 2 +- packages/backend-data/package.json | 2 +- packages/backend-deployer/package.json | 2 +- packages/backend-function/package.json | 2 +- packages/backend-geo/package.json | 4 +- packages/backend-output-storage/package.json | 2 +- .../backend-platform-test-stubs/package.json | 2 +- packages/backend-storage/package.json | 2 +- packages/backend/package.json | 2 +- packages/cli/package.json | 2 +- packages/integration-tests/package.json | 2 +- packages/platform-core/package.json | 2 +- packages/plugin-types/package.json | 2 +- 18 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 .changeset/fluffy-icons-wave.md diff --git a/.changeset/fluffy-icons-wave.md b/.changeset/fluffy-icons-wave.md new file mode 100644 index 00000000000..d053d01132d --- /dev/null +++ b/.changeset/fluffy-icons-wave.md @@ -0,0 +1,18 @@ +--- +'@aws-amplify/backend-platform-test-stubs': patch +'@aws-amplify/backend-output-storage': patch +'@aws-amplify/integration-tests': patch +'@aws-amplify/backend-deployer': patch +'@aws-amplify/backend-function': patch +'@aws-amplify/backend-storage': patch +'@aws-amplify/auth-construct': patch +'@aws-amplify/ai-constructs': patch +'@aws-amplify/platform-core': patch +'@aws-amplify/backend-auth': patch +'@aws-amplify/backend-data': patch +'@aws-amplify/plugin-types': patch +'@aws-amplify/backend-geo': patch +'@aws-amplify/backend-ai': patch +'@aws-amplify/backend': patch +'@aws-amplify/backend-cli': patch +--- diff --git a/package-lock.json b/package-lock.json index 988bf76d8a2..803f9dfcd22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47503,7 +47503,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49351,7 +49351,7 @@ "@aws-sdk/util-arn-parser": "^3.723.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49379,7 +49379,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49396,7 +49396,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49419,7 +49419,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49442,7 +49442,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49460,7 +49460,7 @@ "tsx": "4.19.4" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "typescript": "^5.0.0" } }, @@ -49864,7 +49864,7 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49876,23 +49876,23 @@ "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.195.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, "packages/backend-geo/node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.195.0-alpha.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.206.0-alpha.0.tgz", + "version": "2.207.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", "license": "Apache-2.0", "engines": { "node": ">= 14.15.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49917,7 +49917,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0" + "aws-cdk-lib": "^2.207.0" } }, "packages/backend-platform-test-stubs": { @@ -49926,7 +49926,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.10.1", - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -49957,7 +49957,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -50011,7 +50011,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.195.0" + "aws-cdk-lib": "^2.207.0" } }, "packages/cli-core": { @@ -50377,7 +50377,7 @@ "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", "aws-cdk": "^2.1005.0", - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0", "execa": "^9.5.1", "fs-extra": "^11.1.1", @@ -52464,7 +52464,7 @@ "@types/uuid": "10.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, @@ -52477,7 +52477,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } }, diff --git a/packages/ai-constructs/package.json b/packages/ai-constructs/package.json index 5397239278f..c513054a9ef 100644 --- a/packages/ai-constructs/package.json +++ b/packages/ai-constructs/package.json @@ -41,7 +41,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/auth-construct/package.json b/packages/auth-construct/package.json index 412d5ff5d90..7426c9b52e8 100644 --- a/packages/auth-construct/package.json +++ b/packages/auth-construct/package.json @@ -25,7 +25,7 @@ "@aws-sdk/util-arn-parser": "^3.723.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-ai/package.json b/packages/backend-ai/package.json index fe0200eff95..150723bb3a3 100644 --- a/packages/backend-ai/package.json +++ b/packages/backend-ai/package.json @@ -30,7 +30,7 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-auth/package.json b/packages/backend-auth/package.json index 027b3e074c1..2e2e40f9699 100644 --- a/packages/backend-auth/package.json +++ b/packages/backend-auth/package.json @@ -33,7 +33,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-data/package.json b/packages/backend-data/package.json index 97ac3796b0a..6a6eaf56c40 100644 --- a/packages/backend-data/package.json +++ b/packages/backend-data/package.json @@ -24,7 +24,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" }, "dependencies": { diff --git a/packages/backend-deployer/package.json b/packages/backend-deployer/package.json index 9b956437b16..8e52a692da1 100644 --- a/packages/backend-deployer/package.json +++ b/packages/backend-deployer/package.json @@ -28,7 +28,7 @@ "minimatch": "10.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "typescript": "^5.0.0" }, "overrides": { diff --git a/packages/backend-function/package.json b/packages/backend-function/package.json index 66e1c6ca1e5..4ead6582465 100644 --- a/packages/backend-function/package.json +++ b/packages/backend-function/package.json @@ -39,7 +39,7 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-geo/package.json b/packages/backend-geo/package.json index 1f0dba82edf..85fe77c11e8 100644 --- a/packages/backend-geo/package.json +++ b/packages/backend-geo/package.json @@ -19,13 +19,13 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" }, "dependencies": { "@aws-amplify/backend-output-schemas": "^1.7.0", "@aws-amplify/backend-output-storage": "^1.3.1", "@aws-amplify/platform-core": "^1.10.0", - "@aws-cdk/aws-location-alpha": "^2.195.0-alpha.0" + "@aws-cdk/aws-location-alpha": "^2.207.0-alpha.0" } } diff --git a/packages/backend-output-storage/package.json b/packages/backend-output-storage/package.json index 3d9d00b4645..6e5b45dbeea 100644 --- a/packages/backend-output-storage/package.json +++ b/packages/backend-output-storage/package.json @@ -24,6 +24,6 @@ "@aws-amplify/plugin-types": "^1.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0" + "aws-cdk-lib": "^2.207.0" } } diff --git a/packages/backend-platform-test-stubs/package.json b/packages/backend-platform-test-stubs/package.json index 05393594838..ae92b614f17 100644 --- a/packages/backend-platform-test-stubs/package.json +++ b/packages/backend-platform-test-stubs/package.json @@ -17,7 +17,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.10.1", - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-storage/package.json b/packages/backend-storage/package.json index 33516135c02..276495c5c19 100644 --- a/packages/backend-storage/package.json +++ b/packages/backend-storage/package.json @@ -28,7 +28,7 @@ "@aws-amplify/platform-core": "^1.9.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/backend/package.json b/packages/backend/package.json index fc6413742e6..b0dafbef202 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -44,7 +44,7 @@ "@aws-sdk/client-amplify": "^3.750.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" }, "devDependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 09b3815f6a3..0e5a0805daa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -65,7 +65,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.734.0", - "aws-cdk-lib": "^2.195.0" + "aws-cdk-lib": "^2.207.0" }, "devDependencies": { "@types/envinfo": "^7.8.3", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a9356e2f3ca..9bcec09c01a 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -37,7 +37,7 @@ "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", "aws-cdk": "^2.1005.0", - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0", "execa": "^9.5.1", "fs-extra": "^11.1.1", diff --git a/packages/platform-core/package.json b/packages/platform-core/package.json index e5acdd4f902..6e176338802 100644 --- a/packages/platform-core/package.json +++ b/packages/platform-core/package.json @@ -42,7 +42,7 @@ "zod": "3.25.17" }, "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0" } } diff --git a/packages/plugin-types/package.json b/packages/plugin-types/package.json index b2ff2264fd4..4defa97c02f 100644 --- a/packages/plugin-types/package.json +++ b/packages/plugin-types/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.195.0", + "aws-cdk-lib": "^2.207.0", "constructs": "^10.0.0", "@aws-sdk/types": "^3.734.0" }, From 5e8e718c36b7078b3ab7a065e7cb923e7319ad39 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 7 Aug 2025 09:43:23 -0700 Subject: [PATCH 79/93] updating alpha package --- package-lock.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 803f9dfcd22..4911e3d3c62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12852,6 +12852,19 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", "license": "Apache-2.0" }, + "node_modules/@aws-cdk/aws-location-alpha": { + "version": "2.207.0-alpha.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", + "integrity": "sha512-kuV/iUstJwxuGxALLY4W/pxMsPxMrc67iC+vGN+XlretlFayPGIoK12RB+kV5ZsHPL5QK12V31e7s1LE9WH4/A==", + "license": "Apache-2.0", + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.207.0", + "constructs": "^10.0.0" + } + }, "node_modules/@aws-cdk/aws-service-spec": { "version": "0.1.89", "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.89.tgz", @@ -49883,19 +49896,6 @@ "constructs": "^10.0.0" } }, - "packages/backend-geo/node_modules/@aws-cdk/aws-location-alpha": { - "version": "2.207.0-alpha.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-location-alpha/-/aws-location-alpha-2.207.0-alpha.0.tgz", - "integrity": "sha512-nL1NwRy6CWUrNhwjl/OTkZz54LBpA9TlREYPiXDg43jVZomD4uN/w1vGU1Q3ajVC+nGFX2HNQK0UiBXG7AgSFw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "aws-cdk-lib": "^2.207.0", - "constructs": "^10.0.0" - } - }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", "version": "1.7.0", From 4d809df7df2f5841475502f9b2444aa2f614df2c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 7 Aug 2025 09:58:13 -0700 Subject: [PATCH 80/93] adding missing changesets --- .../dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md | 2 ++ .../dependabot-544148aa5bba1757f43705ba336474d190273260.md | 2 ++ .../dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md | 2 ++ .../dependabot-9604db161b93db781303542fe438e6de6b6f9921.md | 2 ++ ...reate-amplify-9604db161b93db781303542fe438e6de6b6f9921.md | 5 +++++ 5 files changed, 13 insertions(+) create mode 100644 .changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md create mode 100644 .changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md create mode 100644 .changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md create mode 100644 .changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md create mode 100644 .changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md diff --git a/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md b/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/dependabot-37b8b7701bf946b2f9ebba18b9226758f96746ce.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md b/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/dependabot-544148aa5bba1757f43705ba336474d190273260.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md b/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/dependabot-89b34f806657bbdbc1fff5cabcb3e6123e09f3c9.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md b/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/dependabot-9604db161b93db781303542fe438e6de6b6f9921.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md b/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md new file mode 100644 index 00000000000..de7647ff158 --- /dev/null +++ b/.changeset/dependabot-create-amplify-9604db161b93db781303542fe438e6de6b6f9921.md @@ -0,0 +1,5 @@ +--- +'create-amplify': patch +--- + +bump create amplify dependencies From 0d64e146876d2880a23b71002d7f621c8d07cc58 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 7 Aug 2025 12:45:53 -0700 Subject: [PATCH 81/93] fixing e2e issues v1 --- .../client_config_v1.5.ts | 47 ++++++------------- .../src/amplify_auth_credentials_factory.ts | 2 +- .../cdk/auth_cdk_project.ts | 2 +- .../test-project-setup/seed_test_project.ts | 4 +- .../test-project-setup/test_project_base.ts | 2 +- 5 files changed, 20 insertions(+), 37 deletions(-) diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts index 6358a6b8cc2..9b2acb125df 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.5.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.5.ts @@ -165,13 +165,13 @@ export interface AWSAmplifyBackendOutputs { * * @minItems 1 */ - redirect_sign_in_uri: [string, ...string[]]; + redirect_sign_in_uri: string[]; /** * URIs used to redirect after signing out * * @minItems 1 */ - redirect_sign_out_uri: [string, ...string[]]; + redirect_sign_out_uri: string[]; response_type: 'code' | 'token'; }; /** @@ -185,10 +185,7 @@ export interface AWSAmplifyBackendOutputs { * * @minItems 1 */ - username_attributes?: [ - 'email' | 'phone_number' | 'username', - ...('email' | 'phone_number' | 'username')[], - ]; + username_attributes?: ('email' | 'phone_number' | 'username')[]; user_verification_types?: ('email' | 'phone_number')[]; unauthenticated_identities_enabled?: boolean; mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; @@ -231,18 +228,11 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - items?: [ - { - name?: string; - key?: string; - [k: string]: unknown; - }, - ...{ - name?: string; - key?: string; - [k: string]: unknown; - }[], - ]; + items?: { + name?: string; + key?: string; + [k: string]: unknown; + }[]; default?: string; required?: ['items', 'default']; }; @@ -253,18 +243,11 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - items?: [ - { - name?: string; - key?: string; - [k: string]: unknown; - }, - ...{ - name?: string; - key?: string; - [k: string]: unknown; - }[], - ]; + items?: { + name?: string; + key?: string; + [k: string]: unknown; + }[]; default?: string; required?: ['items', 'default']; }; @@ -275,7 +258,7 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - items: [string, ...string[]]; + items: string[]; default: string; }; }; @@ -288,7 +271,7 @@ export interface AWSAmplifyBackendOutputs { /** * @minItems 1 */ - channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; + channels: AmazonPinpointChannels[]; }; /** * Outputs generated from defineStorage diff --git a/packages/integration-tests/src/amplify_auth_credentials_factory.ts b/packages/integration-tests/src/amplify_auth_credentials_factory.ts index c885f6bd057..a5a89af42cb 100644 --- a/packages/integration-tests/src/amplify_auth_credentials_factory.ts +++ b/packages/integration-tests/src/amplify_auth_credentials_factory.ts @@ -33,7 +33,7 @@ export class AmplifyAuthCredentialsFactory { */ constructor( private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient, - authConfig: NonNullable['auth']>, + authConfig: NonNullable['auth']>, ) { if (!authConfig.identity_pool_id) { throw new Error('Client config must have identity pool id.'); diff --git a/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts b/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts index 6bce6af8bf5..a4a8b6e78c2 100644 --- a/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts +++ b/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts @@ -80,7 +80,7 @@ class AuthTestCdkProject extends TestCdkProjectBase { { stackName: this.stackName, }, - '1.4', //version of the config + '1.5', //version of the config awsClientProvider, ); diff --git a/packages/integration-tests/src/test-project-setup/seed_test_project.ts b/packages/integration-tests/src/test-project-setup/seed_test_project.ts index bfa6c547a33..b99781ce037 100644 --- a/packages/integration-tests/src/test-project-setup/seed_test_project.ts +++ b/packages/integration-tests/src/test-project-setup/seed_test_project.ts @@ -133,7 +133,7 @@ class SeedTestProject extends TestProjectBase { const cleanedPolicyString = seedPolicyProcessResult.stdout.slice(startingInd); - const clientConfig = await generateClientConfig(backendIdentifier, '1.4'); + const clientConfig = await generateClientConfig(backendIdentifier, '1.5'); if (!clientConfig.custom) { throw new Error('Client config missing custom section'); } @@ -172,7 +172,7 @@ class SeedTestProject extends TestProjectBase { ): Promise { await super.assertPostDeployment(backendId); const testUsername = 'testUser@amazon.com'; - const clientConfig = await generateClientConfig(backendId, '1.4'); + const clientConfig = await generateClientConfig(backendId, '1.5'); if (!clientConfig.auth) { throw new Error('Client config missing auth section'); diff --git a/packages/integration-tests/src/test-project-setup/test_project_base.ts b/packages/integration-tests/src/test-project-setup/test_project_base.ts index dac0d2e3ee3..9baf4ed168f 100644 --- a/packages/integration-tests/src/test-project-setup/test_project_base.ts +++ b/packages/integration-tests/src/test-project-setup/test_project_base.ts @@ -235,7 +235,7 @@ export abstract class TestProjectBase { const schema = JSON.parse( await fsp.readFile( - './packages/client-config/src/client-config-schema/schema_v1.4.json', + './packages/client-config/src/client-config-schema/schema_v1.5.json', 'utf-8', ), ); From 7566d1be63aa7151a7a628277c9f58dfcbc8dfa1 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 7 Aug 2025 12:46:36 -0700 Subject: [PATCH 82/93] updating API --- packages/client-config/API.md | 59 +++++++++++++---------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/packages/client-config/API.md b/packages/client-config/API.md index ed2a869a55f..ab23f93f1d4 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -11,7 +11,7 @@ import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client' import { S3Client } from '@aws-sdk/client-s3'; // @public -type AmazonCognitoStandardAttributes = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; +type AmazonCognitoStandardAttributes = "address" | "birthdate" | "email" | "family_name" | "gender" | "given_name" | "locale" | "middle_name" | "name" | "nickname" | "phone_number" | "picture" | "preferred_username" | "profile" | "sub" | "updated_at" | "website" | "zoneinfo"; // @public type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; @@ -64,7 +64,7 @@ interface AmazonLocationServiceConfig_6 { } // @public -type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; +type AmazonPinpointChannels = "IN_APP_MESSAGING" | "FCM" | "APNS" | "EMAIL" | "SMS"; // @public type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -82,7 +82,7 @@ type AmazonPinpointChannels_5 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | type AmazonPinpointChannels_6 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public -type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; +type AmplifyStorageAccessActions = "read" | "get" | "list" | "write" | "delete"; // @public type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; @@ -285,22 +285,19 @@ interface AWSAmplifyBackendOutputs { require_symbols: boolean; }; oauth?: { - identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; + identity_providers: ("GOOGLE" | "FACEBOOK" | "LOGIN_WITH_AMAZON" | "SIGN_IN_WITH_APPLE")[]; domain: string; scopes: string[]; - redirect_sign_in_uri: [string, ...string[]]; - redirect_sign_out_uri: [string, ...string[]]; - response_type: 'code' | 'token'; + redirect_sign_in_uri: string[]; + redirect_sign_out_uri: string[]; + response_type: "code" | "token"; }; standard_required_attributes?: AmazonCognitoStandardAttributes[]; - username_attributes?: [ - 'email' | 'phone_number' | 'username', - ...('email' | 'phone_number' | 'username')[] - ]; - user_verification_types?: ('email' | 'phone_number')[]; + username_attributes?: ("email" | "phone_number" | "username")[]; + user_verification_types?: ("email" | "phone_number")[]; unauthenticated_identities_enabled?: boolean; - mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; - mfa_methods?: ('SMS' | 'TOTP')[]; + mfa_configuration?: "NONE" | "OPTIONAL" | "REQUIRED"; + mfa_methods?: ("SMS" | "TOTP")[]; groups?: { [k: string]: AmplifyUserGroupConfig; }[]; @@ -321,53 +318,39 @@ interface AWSAmplifyBackendOutputs { geo?: { aws_region: string; maps?: { - items?: [ - { + items?: { name?: string; key?: string; [k: string]: unknown; - }, - ...{ - name?: string; - key?: string; - [k: string]: unknown; - }[] - ]; + }[]; default?: string; - required?: ['items', 'default']; + required?: ["items", "default"]; }; search_indices?: { - items?: [ - { + items?: { name?: string; key?: string; [k: string]: unknown; - }, - ...{ - name?: string; - key?: string; - [k: string]: unknown; - }[] - ]; + }[]; default?: string; - required?: ['items', 'default']; + required?: ["items", "default"]; }; geofence_collections?: { - items: [string, ...string[]]; + items: string[]; default: string; }; }; notifications?: { aws_region: AwsRegion; amazon_pinpoint_app_id: string; - channels: [AmazonPinpointChannels, ...AmazonPinpointChannels[]]; + channels: AmazonPinpointChannels[]; }; storage?: { aws_region: AwsRegion; bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: '1.5'; + version: "1.5"; } // @public @@ -766,7 +749,7 @@ interface AWSAmplifyBackendOutputs_6 { } // @public -type AwsAppsyncAuthorizationType = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; +type AwsAppsyncAuthorizationType = "AMAZON_COGNITO_USER_POOLS" | "API_KEY" | "AWS_IAM" | "AWS_LAMBDA" | "OPENID_CONNECT"; // @public type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; From dbe662de91d1e92462974aefe19abc9841d296d7 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 7 Aug 2025 13:58:58 -0700 Subject: [PATCH 83/93] following naming convention of integration test --- .../test-e2e/deployment/geo_api_key_support.deployment.test.ts | 2 +- .../src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts | 2 +- ...eo_api_support_testing.ts => geo_api_key_support_testing.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/integration-tests/src/test-project-setup/{geo_api_support_testing.ts => geo_api_key_support_testing.ts} (100%) diff --git a/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts b/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts index 772034efc73..ee1847cc9b0 100644 --- a/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts +++ b/packages/integration-tests/src/test-e2e/deployment/geo_api_key_support.deployment.test.ts @@ -1,4 +1,4 @@ import { defineDeploymentTest } from './deployment.test.template.js'; -import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_support_testing.js'; +import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_key_support_testing.js'; defineDeploymentTest(new GeoAPIKeySupportTestProjectCreator()); diff --git a/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts b/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts index c062d62f5dd..4b7334542eb 100644 --- a/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts +++ b/packages/integration-tests/src/test-e2e/sandbox/geo_api_key_support.sandbox.test.ts @@ -1,4 +1,4 @@ import { defineSandboxTest } from './sandbox.test.template.js'; -import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_support_testing.js'; +import { GeoAPIKeySupportTestProjectCreator } from '../../test-project-setup/geo_api_key_support_testing.js'; defineSandboxTest(new GeoAPIKeySupportTestProjectCreator()); diff --git a/packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts b/packages/integration-tests/src/test-project-setup/geo_api_key_support_testing.ts similarity index 100% rename from packages/integration-tests/src/test-project-setup/geo_api_support_testing.ts rename to packages/integration-tests/src/test-project-setup/geo_api_key_support_testing.ts From 87853321ad44f59ee1a8778eb35e1ed7c16e3ecd Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Fri, 8 Aug 2025 16:57:41 -0700 Subject: [PATCH 84/93] small fix for node:crypto --- packages/integration-tests/tsconfig.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/integration-tests/tsconfig.json b/packages/integration-tests/tsconfig.json index 3ab9f98c0ed..b389f96efc4 100644 --- a/packages/integration-tests/tsconfig.json +++ b/packages/integration-tests/tsconfig.json @@ -1,6 +1,11 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { "rootDir": "src", "outDir": "lib", "allowJs": true }, + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "allowJs": true, + "types": ["node"] + }, "references": [ { "path": "../ai-constructs" }, { "path": "../auth-construct" }, From ed7f937037936f72877ed91f3b5d532b69018224 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 11 Aug 2025 13:15:28 -0700 Subject: [PATCH 85/93] adding changeset description and removing isdefault --- .changeset/fluffy-icons-wave.md | 2 ++ .../src/geo_outputs_aspect.test.ts | 25 ------------------- packages/backend-geo/src/map_resource.ts | 1 - packages/backend-geo/src/place_resource.ts | 1 - packages/backend-geo/src/types.ts | 2 -- 5 files changed, 2 insertions(+), 29 deletions(-) diff --git a/.changeset/fluffy-icons-wave.md b/.changeset/fluffy-icons-wave.md index d053d01132d..ae19fcec3d2 100644 --- a/.changeset/fluffy-icons-wave.md +++ b/.changeset/fluffy-icons-wave.md @@ -16,3 +16,5 @@ '@aws-amplify/backend': patch '@aws-amplify/backend-cli': patch --- + +- Upgrading `aws-cdk-lib` peer dependency versions for the above changesets to `2.207.0` to accomodate the `aws-location-alpha` package diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index ccc20ebfd73..b169736f49e 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -68,7 +68,6 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('output entry called once with multiple maps created', () => { new AmplifyMap(stack, 'testMap_1', { name: 'testMap1', - isDefault: true, }); // set as default map const mapNode = new AmplifyMap(stack, 'testMap_2', { name: 'testMap2', @@ -83,7 +82,6 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('output entry called once with multiple places created', () => { new AmplifyPlace(stack, 'testPlace_1', { name: 'testPlace1', - isDefault: true, }); // set as default place const placeNode = new AmplifyPlace(stack, 'testPlace_2', { name: 'testPlace2', @@ -178,27 +176,6 @@ void describe('AmplifyGeoOutputsAspect', () => { ); }); - void it('throws if multiple default maps', () => { - const node = new AmplifyMap(stack, 'testMapDefault', { - name: 'defaultMap', - isDefault: true, - }); - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - assert.throws( - () => { - new AmplifyMap(stack, 'anotherDefaultMap', { - name: 'anotherDefaultMap', - isDefault: true, - }); - aspect.visit(node); - }, - new AmplifyUserError('MultipleDefaultMapError', { - message: `More than one default map set in the Amplify project`, - resolution: `Remove 'isDefault: true' from all 'defineMap' calls except for one in your Amplify project`, - }), - ); - }); - void it('throws if no place set to default', () => { const noDuplicateStack = new Stack(app, 'noDuplicateStack'); const newNode = new AmplifyPlace(noDuplicateStack, 'testPlace', { @@ -222,14 +199,12 @@ void describe('AmplifyGeoOutputsAspect', () => { void it('throws if multiple default places', () => { const node = new AmplifyPlace(stack, 'testPlaceDefault', { name: 'defaultPlace', - isDefault: true, }); aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); assert.throws( () => { new AmplifyPlace(stack, 'anotherDefaultPlace', { name: 'anotherDefaultPlace', - isDefault: true, }); aspect.visit(node); }, diff --git a/packages/backend-geo/src/map_resource.ts b/packages/backend-geo/src/map_resource.ts index 1f335f8e3db..9656a06d028 100644 --- a/packages/backend-geo/src/map_resource.ts +++ b/packages/backend-geo/src/map_resource.ts @@ -31,7 +31,6 @@ export class AmplifyMap super(scope, id); this.name = props.name; this.id = id; - this.isDefault = props.isDefault || false; this.props = props; diff --git a/packages/backend-geo/src/place_resource.ts b/packages/backend-geo/src/place_resource.ts index 7e0b1e10255..9d6eee80c69 100644 --- a/packages/backend-geo/src/place_resource.ts +++ b/packages/backend-geo/src/place_resource.ts @@ -31,7 +31,6 @@ export class AmplifyPlace this.name = props.name; this.id = id; - this.isDefault = props.isDefault || false; this.props = props; diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 699ceac5281..8fdc3f169d3 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -79,14 +79,12 @@ export type AmplifyCollectionFactoryProps = Omit< export type AmplifyMapProps = { name: string; - isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; apiKeyProps?: GeoApiKeyProps; }; export type AmplifyPlaceProps = { name: string; - isDefault?: boolean; outputStorageStrategy?: BackendOutputStorageStrategy; apiKeyProps?: GeoApiKeyProps; }; From 9d562af95b47dea765a0a04dc47cb9826aca2659 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 11 Aug 2025 13:17:59 -0700 Subject: [PATCH 86/93] updating API --- packages/backend-geo/API.md | 2 -- packages/backend-output-schemas/API.md | 48 +++++++++++++------------- packages/client-config/API.md | 22 ++++-------- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index e7a02e262bf..d0eae7bdf7d 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -43,7 +43,6 @@ export type AmplifyMapFactoryProps = Omit; apiKeyProps?: GeoApiKeyProps; }; @@ -56,7 +55,6 @@ export type AmplifyPlaceFactoryProps = Omit; apiKeyProps?: GeoApiKeyProps; }; diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index b27ed5d7bfe..4b2d8672852 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -381,34 +381,34 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ geoRegion: z.ZodString; - maps: z.ZodOptional; - searchIndices: z.ZodOptional; + map: z.ZodOptional; + searchIndex: z.ZodOptional; geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }, { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; }>]>>; @@ -488,8 +488,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; } | undefined; @@ -569,8 +569,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; } | undefined; @@ -764,34 +764,34 @@ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zod version: z.ZodLiteral<"1">; payload: z.ZodObject<{ geoRegion: z.ZodString; - maps: z.ZodOptional; - searchIndices: z.ZodOptional; + map: z.ZodOptional; + searchIndex: z.ZodOptional; geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }, { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { geoRegion: string; - maps?: string | undefined; - searchIndices?: string | undefined; + map?: string | undefined; + searchIndex?: string | undefined; geofenceCollections?: string | undefined; }; }>]>; diff --git a/packages/client-config/API.md b/packages/client-config/API.md index ab23f93f1d4..75931ec1ea1 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -317,23 +317,13 @@ interface AWSAmplifyBackendOutputs { }; geo?: { aws_region: string; - maps?: { - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ["items", "default"]; + map?: { + name?: string; + api_key?: string; }; - search_indices?: { - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ["items", "default"]; + search_index?: { + name?: string; + api_key?: string; }; geofence_collections?: { items: string[]; From 1ed89174804cf45e237a7cb2bd1d5b668e63274c Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 11 Aug 2025 14:53:25 -0700 Subject: [PATCH 87/93] small fixes v2 --- packages/backend-geo/src/geo_access_orchestrator.test.ts | 5 ++++- packages/backend-geo/src/geo_access_orchestrator.ts | 6 ++++++ packages/backend-geo/src/geo_access_policy_factory.test.ts | 6 ------ packages/backend-geo/src/geo_access_policy_factory.ts | 7 ------- .../client_config_to_legacy_converter.ts | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index 925d6981569..261b3983d1d 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -650,7 +650,10 @@ void describe('GeoAccessOrchestrator', () => { 'collection', testResourceName, ), - { message: 'At least one permission must be specified' }, + new AmplifyUserError('NoGeoAccessActionsFoundError', { + message: `No access actions found for the authenticated role.`, + resolution: `Please add an action for the authenticated role or remove the action statement.`, + }), ); }); }); diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index ae8653fe868..4ab7a439ed7 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -67,6 +67,12 @@ export class GeoAccessOrchestrator { definition.uniqueDefinitionValidators.forEach( ({ uniqueRoleToken, validationErrorOptions }) => { + if (!definition.actions.length) + throw new AmplifyUserError('NoGeoAccessActionsFoundError', { + message: `No access actions found for the ${uniqueRoleToken} role.`, + resolution: `Please add an action for the ${uniqueRoleToken} role or remove the action statement.`, + }); + if (uniqueRoleTokenSet.has(uniqueRoleToken)) { throw new AmplifyUserError( 'InvalidGeoAccessDefinitionError', diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts index 0f63a996a72..e5f5159f78c 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.test.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -568,12 +568,6 @@ void describe('GeoAccessPolicyFactory', () => { ]); }); - void it('generateKeyActions returns empty array for empty input', () => { - const keyActions = geoAccessPolicyFactory.generateKeyActions([]); - - assert.deepStrictEqual(keyActions, []); - }); - void it('generateKeyActions handles duplicate actions', () => { const keyActions = geoAccessPolicyFactory.generateKeyActions([ 'get', diff --git a/packages/backend-geo/src/geo_access_policy_factory.ts b/packages/backend-geo/src/geo_access_policy_factory.ts index 59c5eb9c32a..5049c216d8b 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.ts @@ -1,5 +1,4 @@ import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { AmplifyFault } from '@aws-amplify/platform-core'; import { Stack } from 'aws-cdk-lib'; import { AllowMapsAction, @@ -20,12 +19,6 @@ export class GeoAccessPolicyFactory { resourceName: string, stack: Stack, ) => { - if (permissions.length === 0) { - throw new AmplifyFault('EmptyPolicyFault', { - message: 'At least one permission must be specified', - }); - } - // policy statements created for each resource type const policyStatement: PolicyStatement = new PolicyStatement(); diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts index 8caa6999c2e..06096e48684 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts @@ -22,7 +22,7 @@ export class ClientConfigLegacyConverter { * Converts client config to a shape consumable by legacy libraries. */ convertToLegacyConfig = (clientConfig: ClientConfig): ClientConfigLegacy => { - // We can only convert from V1.4 of ClientConfig. For everything else, throw + // We can only convert from V1.5 of ClientConfig. For everything else, throw if (!this.isClientConfigV1_5(clientConfig)) { throw new AmplifyFault('UnsupportedClientConfigVersionFault', { message: 'Only version 1.5 of ClientConfig is supported.', From ae1e1881c8b60e6353e444a8d3f18cba73be8986 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Mon, 11 Aug 2025 14:54:34 -0700 Subject: [PATCH 88/93] updating API --- packages/backend-output-schemas/API.md | 48 +++++++++++++------------- packages/client-config/API.md | 44 ++++++++++++++--------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 4b2d8672852..b27ed5d7bfe 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -381,34 +381,34 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: z.ZodLiteral<"1">; payload: z.ZodObject<{ geoRegion: z.ZodString; - map: z.ZodOptional; - searchIndex: z.ZodOptional; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }, { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; }>]>>; @@ -488,8 +488,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; } | undefined; @@ -569,8 +569,8 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; } | undefined; @@ -764,34 +764,34 @@ export const versionedGeoOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zod version: z.ZodLiteral<"1">; payload: z.ZodObject<{ geoRegion: z.ZodString; - map: z.ZodOptional; - searchIndex: z.ZodOptional; + maps: z.ZodOptional; + searchIndices: z.ZodOptional; geofenceCollections: z.ZodOptional; }, "strip", z.ZodTypeAny, { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }, { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; }, { version: "1"; payload: { geoRegion: string; - map?: string | undefined; - searchIndex?: string | undefined; + maps?: string | undefined; + searchIndices?: string | undefined; geofenceCollections?: string | undefined; }; }>]>; diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 75931ec1ea1..48ced515cd5 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -11,7 +11,7 @@ import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client' import { S3Client } from '@aws-sdk/client-s3'; // @public -type AmazonCognitoStandardAttributes = "address" | "birthdate" | "email" | "family_name" | "gender" | "given_name" | "locale" | "middle_name" | "name" | "nickname" | "phone_number" | "picture" | "preferred_username" | "profile" | "sub" | "updated_at" | "website" | "zoneinfo"; +type AmazonCognitoStandardAttributes = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; // @public type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; @@ -64,7 +64,7 @@ interface AmazonLocationServiceConfig_6 { } // @public -type AmazonPinpointChannels = "IN_APP_MESSAGING" | "FCM" | "APNS" | "EMAIL" | "SMS"; +type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -82,7 +82,7 @@ type AmazonPinpointChannels_5 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | type AmazonPinpointChannels_6 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public -type AmplifyStorageAccessActions = "read" | "get" | "list" | "write" | "delete"; +type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; // @public type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; @@ -285,19 +285,19 @@ interface AWSAmplifyBackendOutputs { require_symbols: boolean; }; oauth?: { - identity_providers: ("GOOGLE" | "FACEBOOK" | "LOGIN_WITH_AMAZON" | "SIGN_IN_WITH_APPLE")[]; + identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; domain: string; scopes: string[]; redirect_sign_in_uri: string[]; redirect_sign_out_uri: string[]; - response_type: "code" | "token"; + response_type: 'code' | 'token'; }; standard_required_attributes?: AmazonCognitoStandardAttributes[]; - username_attributes?: ("email" | "phone_number" | "username")[]; - user_verification_types?: ("email" | "phone_number")[]; + username_attributes?: ('email' | 'phone_number' | 'username')[]; + user_verification_types?: ('email' | 'phone_number')[]; unauthenticated_identities_enabled?: boolean; - mfa_configuration?: "NONE" | "OPTIONAL" | "REQUIRED"; - mfa_methods?: ("SMS" | "TOTP")[]; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; groups?: { [k: string]: AmplifyUserGroupConfig; }[]; @@ -317,13 +317,23 @@ interface AWSAmplifyBackendOutputs { }; geo?: { aws_region: string; - map?: { - name?: string; - api_key?: string; + maps?: { + items?: { + name?: string; + key?: string; + [k: string]: unknown; + }[]; + default?: string; + required?: ['items', 'default']; }; - search_index?: { - name?: string; - api_key?: string; + search_indices?: { + items?: { + name?: string; + key?: string; + [k: string]: unknown; + }[]; + default?: string; + required?: ['items', 'default']; }; geofence_collections?: { items: string[]; @@ -340,7 +350,7 @@ interface AWSAmplifyBackendOutputs { bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: "1.5"; + version: '1.5'; } // @public @@ -739,7 +749,7 @@ interface AWSAmplifyBackendOutputs_6 { } // @public -type AwsAppsyncAuthorizationType = "AMAZON_COGNITO_USER_POOLS" | "API_KEY" | "AWS_IAM" | "AWS_LAMBDA" | "OPENID_CONNECT"; +type AwsAppsyncAuthorizationType = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; // @public type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; From 8e594a3b430e2ea1ef1881a7555eb0221c365ec9 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Tue, 12 Aug 2025 16:00:35 -0700 Subject: [PATCH 89/93] fixing schema --- packages/backend-geo/API.md | 6 -- .../src/geo_access_orchestrator.test.ts | 26 +++-- .../src/geo_access_orchestrator.ts | 4 +- .../src/geo_access_policy_factory.test.ts | 12 --- .../src/geo_outputs_aspect.test.ts | 94 +------------------ .../backend-geo/src/geo_outputs_aspect.ts | 20 ++-- packages/backend-geo/src/types.ts | 5 - packages/backend-output-schemas/src/geo/v1.ts | 2 +- packages/client-config/API.md | 49 +++++----- .../client_config_contributor_v1.test.ts | 28 +++--- .../client_config_contributor_v1.ts | 20 +++- .../client_config_v1.5.ts | 58 ++++++------ .../src/client-config-schema/schema_v1.5.json | 58 ++++++------ .../client_config_to_legacy_converter.test.ts | 12 ++- .../client_config_to_legacy_converter.ts | 14 ++- .../unified_client_config_generator.test.ts | 26 +++-- 16 files changed, 173 insertions(+), 261 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index d0eae7bdf7d..6884de920a0 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -127,12 +127,6 @@ export type PlaceResources = { }; }; -// @public (undocumented) -export type ResourceOutputs = { - name: string; - key?: string; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-geo/src/geo_access_orchestrator.test.ts b/packages/backend-geo/src/geo_access_orchestrator.test.ts index 261b3983d1d..8a9ef755ad4 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.test.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.test.ts @@ -651,8 +651,8 @@ void describe('GeoAccessOrchestrator', () => { testResourceName, ), new AmplifyUserError('NoGeoAccessActionsFoundError', { - message: `No access actions found for the authenticated role.`, - resolution: `Please add an action for the authenticated role or remove the action statement.`, + message: `No access actions found for the authenticated users.`, + resolution: `Please add an action for the authenticated users or remove the action statement.`, }), ); }); @@ -805,15 +805,21 @@ void describe('GeoAccessOrchestrator', () => { ssmEnvironmentEntriesStub, ); - geoAccessOrchestrator.orchestrateGeoAccess( - testResourceArn, - 'map', - testResourceName, - ); - - const keyActions = geoAccessOrchestrator.orchestrateKeyAccess(); + assert.throws( + () => { + geoAccessOrchestrator.orchestrateGeoAccess( + testResourceArn, + 'map', + testResourceName, + ); - assert.deepStrictEqual(keyActions, []); + geoAccessOrchestrator.orchestrateKeyAccess(); + }, + new AmplifyUserError('NoGeoAccessActionsFoundError', { + message: `No access actions found for the api key users.`, + resolution: `Please add an action for the api key users or remove the action statement.`, + }), + ); }); void it('handles mixed API key and role-based access', () => { diff --git a/packages/backend-geo/src/geo_access_orchestrator.ts b/packages/backend-geo/src/geo_access_orchestrator.ts index 4ab7a439ed7..846a1b2cbc9 100644 --- a/packages/backend-geo/src/geo_access_orchestrator.ts +++ b/packages/backend-geo/src/geo_access_orchestrator.ts @@ -69,8 +69,8 @@ export class GeoAccessOrchestrator { ({ uniqueRoleToken, validationErrorOptions }) => { if (!definition.actions.length) throw new AmplifyUserError('NoGeoAccessActionsFoundError', { - message: `No access actions found for the ${uniqueRoleToken} role.`, - resolution: `Please add an action for the ${uniqueRoleToken} role or remove the action statement.`, + message: `No access actions found for the ${uniqueRoleToken} users.`, + resolution: `Please add an action for the ${uniqueRoleToken} users or remove the action statement.`, }); if (uniqueRoleTokenSet.has(uniqueRoleToken)) { diff --git a/packages/backend-geo/src/geo_access_policy_factory.test.ts b/packages/backend-geo/src/geo_access_policy_factory.test.ts index e5f5159f78c..c6af4d1ce11 100644 --- a/packages/backend-geo/src/geo_access_policy_factory.test.ts +++ b/packages/backend-geo/src/geo_access_policy_factory.test.ts @@ -18,18 +18,6 @@ void describe('GeoAccessPolicyFactory', () => { geoAccessPolicyFactory = new GeoAccessPolicyFactory(); }); - void it('throws if no permissions are specified', () => { - assert.throws(() => - geoAccessPolicyFactory.createPolicy( - [], - testResourceArn, - 'test-role', - testResourceName, - stack, - ), - ); - }); - void it('returns policy with get actions', () => { const policy = geoAccessPolicyFactory.createPolicy( ['get'], diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index b169736f49e..ac12a3cd436 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -65,34 +65,6 @@ void describe('AmplifyGeoOutputsAspect', () => { assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); }); - void it('output entry called once with multiple maps created', () => { - new AmplifyMap(stack, 'testMap_1', { - name: 'testMap1', - }); // set as default map - const mapNode = new AmplifyMap(stack, 'testMap_2', { - name: 'testMap2', - }); - - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(mapNode); - - assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - }); - - void it('output entry called once with multiple places created', () => { - new AmplifyPlace(stack, 'testPlace_1', { - name: 'testPlace1', - }); // set as default place - const placeNode = new AmplifyPlace(stack, 'testPlace_2', { - name: 'testPlace2', - }); - - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - aspect.visit(placeNode); - - assert.equal(addBackendOutputEntryMock.mock.callCount(), 1); - }); - void it('output entry called once with multiple collections created', () => { new AmplifyCollection(stack, 'testCollection_1', { name: 'testCollection1', @@ -155,65 +127,6 @@ void describe('AmplifyGeoOutputsAspect', () => { }), ); }); - - void it('throws if no map set to default', () => { - const noDuplicateStack = new Stack(app, 'noDuplicateStack'); - const newNode = new AmplifyMap(noDuplicateStack, 'testMap', { - name: 'testMap', - }); - new AmplifyMap(noDuplicateStack, 'testMap2', { - name: 'testDuplicateMap2', - }); - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - assert.throws( - () => { - aspect.visit(newNode); - }, - new AmplifyUserError('NoDefaultMapError', { - message: `No default map set in the Amplify project`, - resolution: `Add 'isDefault: true' to one of the 'defineMap' calls in your Amplify project`, - }), - ); - }); - - void it('throws if no place set to default', () => { - const noDuplicateStack = new Stack(app, 'noDuplicateStack'); - const newNode = new AmplifyPlace(noDuplicateStack, 'testPlace', { - name: 'testPlace', - }); - new AmplifyPlace(noDuplicateStack, 'testPlace2', { - name: 'testDuplicatePlace2', - }); - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - assert.throws( - () => { - aspect.visit(newNode); - }, - new AmplifyUserError('NoDefaultPlaceError', { - message: `No default place set in the Amplify project`, - resolution: `Add 'isDefault: true' to one of the 'definePlace' calls in your Amplify project`, - }), - ); - }); - - void it('throws if multiple default places', () => { - const node = new AmplifyPlace(stack, 'testPlaceDefault', { - name: 'defaultPlace', - }); - aspect = new AmplifyGeoOutputsAspect(outputStorageStrategy); - assert.throws( - () => { - new AmplifyPlace(stack, 'anotherDefaultPlace', { - name: 'anotherDefaultPlace', - }); - aspect.visit(node); - }, - new AmplifyUserError('MultipleDefaultPlaceError', { - message: `More than one default place set in the Amplify project`, - resolution: `Remove 'isDefault: true' from all 'definePlace' calls except for one in your Amplify project`, - }), - ); - }); }); void describe('output validation', () => { @@ -273,16 +186,17 @@ void describe('AmplifyGeoOutputsAspect', () => { default: "testMapResource", items: [{ name: "testMapResource", - apiKey: "TOKEN_STRING" + api_key_name: "TOKEN_STRING" }] }) } } */ - assert.ok( + assert.equal( JSON.parse( addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, - ).items[0].key.includes('TOKEN'), + ).items[0].api_key_name, + 'myKey', ); assert.equal( diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 0ec0f2aa057..29e9a38f751 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -6,10 +6,16 @@ import { AmplifyPlace } from './place_resource.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import { GeoOutput, geoOutputKey } from '@aws-amplify/backend-output-schemas'; -import { GeoResourceType, ResourceOutputs } from './types.js'; +import { GeoResourceType } from './types.js'; export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; +type ResourceOutputs = { + name?: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + api_key_name?: string; +}; + /** * Aspect Implementation for Geo Resources */ @@ -134,13 +140,9 @@ export class AmplifyGeoOutputsAspect implements IAspect { 'collection', ) as AmplifyCollection; - const defaultMap = this.validateDefault(maps, maps[0], 'map') as AmplifyMap; + const defaultMap = maps[0]; - const defaultPlace = this.validateDefault( - places, - places[0], - 'place', - ) as AmplifyPlace; + const defaultPlace = places[0]; // Collect all collection names for the items array const collectionNames = collections.map( @@ -151,7 +153,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { const mapOutputs: ResourceOutputs[] = maps.map((map): ResourceOutputs => { return { name: map.name, - key: map.resources.cfnResources.cfnAPIKey?.ref, + api_key_name: map.resources.cfnResources.cfnAPIKey?.keyName, }; }); @@ -159,7 +161,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { (place): ResourceOutputs => { return { name: place.name, - key: place.resources.cfnResources.cfnAPIKey?.ref, + api_key_name: place.resources.cfnResources.cfnAPIKey?.keyName, }; }, ); diff --git a/packages/backend-geo/src/types.ts b/packages/backend-geo/src/types.ts index 8fdc3f169d3..ec6036150d9 100644 --- a/packages/backend-geo/src/types.ts +++ b/packages/backend-geo/src/types.ts @@ -139,11 +139,6 @@ export type CollectionResources = { }; }; -export type ResourceOutputs = { - name: string; - key?: string; -}; - // ----------------------------------- access definitions ---------------------------------------------- export type GeoAccessGenerator = ( diff --git a/packages/backend-output-schemas/src/geo/v1.ts b/packages/backend-output-schemas/src/geo/v1.ts index 86cc066a0ee..c8e3e9e06fd 100644 --- a/packages/backend-output-schemas/src/geo/v1.ts +++ b/packages/backend-output-schemas/src/geo/v1.ts @@ -7,7 +7,7 @@ const collectionConstructSchema = z.object({ const resourceItemSchema = z.object({ name: z.string(), - key: z.string().optional(), + api_key_name: z.string().optional(), }); const resourceSchema = z.object({ diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 48ced515cd5..c48128fa6a4 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -11,7 +11,7 @@ import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client' import { S3Client } from '@aws-sdk/client-s3'; // @public -type AmazonCognitoStandardAttributes = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; +type AmazonCognitoStandardAttributes = "address" | "birthdate" | "email" | "family_name" | "gender" | "given_name" | "locale" | "middle_name" | "name" | "nickname" | "phone_number" | "picture" | "preferred_username" | "profile" | "sub" | "updated_at" | "website" | "zoneinfo"; // @public type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; @@ -30,6 +30,7 @@ type AmazonCognitoStandardAttributes_6 = 'address' | 'birthdate' | 'email' | 'fa // @public interface AmazonLocationServiceConfig { + api_key_name?: string; style?: string; } @@ -64,7 +65,7 @@ interface AmazonLocationServiceConfig_6 { } // @public -type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; +type AmazonPinpointChannels = "IN_APP_MESSAGING" | "FCM" | "APNS" | "EMAIL" | "SMS"; // @public type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -82,7 +83,7 @@ type AmazonPinpointChannels_5 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | type AmazonPinpointChannels_6 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public -type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; +type AmplifyStorageAccessActions = "read" | "get" | "list" | "write" | "delete"; // @public type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; @@ -285,19 +286,19 @@ interface AWSAmplifyBackendOutputs { require_symbols: boolean; }; oauth?: { - identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; + identity_providers: ("GOOGLE" | "FACEBOOK" | "LOGIN_WITH_AMAZON" | "SIGN_IN_WITH_APPLE")[]; domain: string; scopes: string[]; redirect_sign_in_uri: string[]; redirect_sign_out_uri: string[]; - response_type: 'code' | 'token'; + response_type: "code" | "token"; }; standard_required_attributes?: AmazonCognitoStandardAttributes[]; - username_attributes?: ('email' | 'phone_number' | 'username')[]; - user_verification_types?: ('email' | 'phone_number')[]; + username_attributes?: ("email" | "phone_number" | "username")[]; + user_verification_types?: ("email" | "phone_number")[]; unauthenticated_identities_enabled?: boolean; - mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; - mfa_methods?: ('SMS' | 'TOTP')[]; + mfa_configuration?: "NONE" | "OPTIONAL" | "REQUIRED"; + mfa_methods?: ("SMS" | "TOTP")[]; groups?: { [k: string]: AmplifyUserGroupConfig; }[]; @@ -318,22 +319,16 @@ interface AWSAmplifyBackendOutputs { geo?: { aws_region: string; maps?: { - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ['items', 'default']; + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; }; search_indices?: { - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ['items', 'default']; + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; }; geofence_collections?: { items: string[]; @@ -350,7 +345,7 @@ interface AWSAmplifyBackendOutputs { bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: '1.5'; + version: "1.5"; } // @public @@ -749,7 +744,7 @@ interface AWSAmplifyBackendOutputs_6 { } // @public -type AwsAppsyncAuthorizationType = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; +type AwsAppsyncAuthorizationType = "AMAZON_COGNITO_USER_POOLS" | "API_KEY" | "AWS_IAM" | "AWS_LAMBDA" | "OPENID_CONNECT"; // @public type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; @@ -894,8 +889,8 @@ declare namespace clientConfigTypesV1_5 { AmplifyStorageAccessActions, AWSAmplifyBackendOutputs, AmplifyUserGroupConfig, - AmplifyStorageBucket, - AmazonLocationServiceConfig + AmazonLocationServiceConfig, + AmplifyStorageBucket } } export { clientConfigTypesV1_5 } diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index e2469434c86..7f89a515eda 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -588,7 +588,7 @@ void describe('data client config contributor v1', () => { }); }); -void describe('geo client config contributor v1', () => { +void describe('geo client config contributor v1.1', () => { void it('empty outputs if no geo output provided', () => { const contributor = new GeoClientConfigContributor(); assert.deepStrictEqual( @@ -651,16 +651,16 @@ void describe('geo client config contributor v1', () => { items: [ { name: 'defaultMap', - key: 'defaultKey', + api_key_name: 'defaultKey', }, ], }), searchIndices: JSON.stringify({ - default: 'defaultPlace', + default: 'defaultIndex', items: [ { name: 'defaultIndex', - key: 'defaultKey', + api_key_name: 'defaultKey', }, ], }), @@ -676,21 +676,19 @@ void describe('geo client config contributor v1', () => { aws_region: 'us-west-2', maps: { default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', + items: { + defaultMap: { + api_key_name: 'defaultKey', }, - ], + }, }, search_indices: { - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', + default: 'defaultIndex', + items: { + defaultIndex: { + api_key_name: 'defaultKey', }, - ], + }, }, geofence_collections: { default: 'defaultCollection', diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index d3596157944..1fcd74eaf64 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -506,8 +506,24 @@ export class GeoClientConfigContributor implements ClientConfigContributor { geoOutput.payload.geofenceCollections, ); - if (mapPayload) config.geo.maps = mapPayload; - if (placesPayload) config.geo.search_indices = placesPayload; + if (mapPayload) + config.geo.maps = { + default: mapPayload.items[0].name, + items: { + [mapPayload.items[0].name]: { + api_key_name: mapPayload.items[0].api_key_name, + }, + }, + }; + if (placesPayload) + config.geo.search_indices = { + default: placesPayload.items[0].name, + items: { + [placesPayload.items[0].name]: { + api_key_name: placesPayload.items[0].api_key_name, + }, + }, + }; if (collectionPayload) config.geo.geofence_collections = collectionPayload; return config; diff --git a/packages/client-config/src/client-config-schema/client_config_v1.5.ts b/packages/client-config/src/client-config-schema/client_config_v1.5.ts index 9b2acb125df..31c70d76d13 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.5.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.5.ts @@ -225,31 +225,19 @@ export interface AWSAmplifyBackendOutputs { * Maps from Amazon Location Service */ maps?: { - /** - * @minItems 1 - */ - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ['items', 'default']; + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; }; /** * Location search (search by places, addresses, coordinates) */ search_indices?: { - /** - * @minItems 1 - */ - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ['items', 'default']; + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; }; /** * Geofencing (visualize virtual perimeters) @@ -298,6 +286,26 @@ export interface AWSAmplifyBackendOutputs { export interface AmplifyUserGroupConfig { precedence?: number; } +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + * + * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema + * via the `definition` "amazon_location_service_config". + */ +export interface AmazonLocationServiceConfig { + /** + * Map style + */ + style?: string; + /** + * Resource API Key Name + */ + api_key_name?: string; +} /** * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema * via the `definition` "amplify_storage_bucket". @@ -310,13 +318,3 @@ export interface AmplifyStorageBucket { [k: string]: AmplifyStorageAccessRule; }; } -/** - * This interface was referenced by `AWSAmplifyBackendOutputs`'s JSON-Schema - * via the `definition` "amazon_location_service_config". - */ -export interface AmazonLocationServiceConfig { - /** - * Map style - */ - style?: string; -} diff --git a/packages/client-config/src/client-config-schema/schema_v1.5.json b/packages/client-config/src/client-config-schema/schema_v1.5.json index 585081774de..6236dca9354 100644 --- a/packages/client-config/src/client-config-schema/schema_v1.5.json +++ b/packages/client-config/src/client-config-schema/schema_v1.5.json @@ -259,27 +259,23 @@ "additionalProperties": false, "properties": { "items": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "description": "Actual map name", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "key": { - "type": "string" - } + "type": "object", + "additionalProperties": false, + "propertyNames": { + "description": "Amazon Location Service Map name", + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amazon_location_service_config" } } }, "default": { "type": "string" - }, - "required": ["items", "default"] - } + } + }, + "required": ["items", "default"] }, "search_indices": { "description": "Location search (search by places, addresses, coordinates)", @@ -287,27 +283,23 @@ "additionalProperties": false, "properties": { "items": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { + "type": "object", + "additionalProperties": false, + "propertyNames": { "description": "Actual index name", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "key": { - "type": "string" - } + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amazon_location_service_config" } } }, "default": { "type": "string" - }, - "required": ["items", "default"] - } + } + }, + "required": ["items", "default"] }, "geofence_collections": { "description": "Geofencing (visualize virtual perimeters)", @@ -459,6 +451,10 @@ "style": { "description": "Map style", "type": "string" + }, + "api_key_name": { + "description": "Resource API Key Name", + "type": "string" } } }, diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts index 98404075bba..18c4f676b6e 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts @@ -361,11 +361,19 @@ void describe('ClientConfigLegacyConverter', () => { aws_region: 'testRegion', maps: { default: 'map1', - items: [{ name: 'map1', api_key: 'key' }], + items: { + map1: { + api_key_name: 'key', + }, + }, }, search_indices: { default: 'index1', - items: [{ name: 'index1', api_key: 'key' }], + items: { + index1: { + api_key_name: 'key', + }, + }, }, geofence_collections: { default: 'geofence1', diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts index 06096e48684..9d00aaec92c 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts @@ -207,9 +207,11 @@ export class ClientConfigLegacyConverter { if (clientConfig.geo.maps) { const mapsLegacyConfig: Record = {}; - for (const map of clientConfig.geo.maps.items!) { - mapsLegacyConfig[map.name!] = { - style: '', // leaving empty as it doesn't exist + for (const [name, config] of Object.entries( + clientConfig.geo.maps.items!, + )) { + mapsLegacyConfig[name!] = { + style: config.style || '', // leaving empty as it doesn't exist }; } @@ -222,8 +224,10 @@ export class ClientConfigLegacyConverter { if (clientConfig.geo.search_indices) { const placesLegacyConfig: string[] = []; - for (const place of clientConfig.geo.search_indices.items!) { - placesLegacyConfig.push(place.name!); + for (const config of Object.entries( + clientConfig.geo.search_indices.items!, + )) { + placesLegacyConfig.push(config[0]); } geoConfig.geo!.amazon_location_service.search_indices = { diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index e849611e004..f152e724085 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -89,16 +89,16 @@ void describe('UnifiedClientConfigGenerator', () => { items: [ { name: 'defaultMap', - key: 'defaultKey', + api_key_name: 'defaultKey', }, ], }), searchIndices: JSON.stringify({ - default: 'defaultPlace', + default: 'defaultIndex', items: [ { name: 'defaultIndex', - key: 'defaultKey', + api_key_name: 'defaultKey', }, ], }), @@ -178,21 +178,19 @@ void describe('UnifiedClientConfigGenerator', () => { aws_region: 'us-east-1', maps: { default: 'defaultMap', - items: [ - { - name: 'defaultMap', - key: 'defaultKey', + items: { + defaultMap: { + api_key_name: 'defaultKey', }, - ], + }, }, search_indices: { - default: 'defaultPlace', - items: [ - { - name: 'defaultIndex', - key: 'defaultKey', + default: 'defaultIndex', + items: { + defaultIndex: { + api_key_name: 'defaultKey', }, - ], + }, }, geofence_collections: { default: 'defaultCollection', From 6a843f15c52d96b0571cf1b0d1b580092b2c09da Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Wed, 13 Aug 2025 10:02:47 -0700 Subject: [PATCH 90/93] naming conventions --- .../src/geo_outputs_aspect.test.ts | 2 +- .../backend-geo/src/geo_outputs_aspect.ts | 7 +++--- packages/client-config/API.md | 22 +++++++++---------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/backend-geo/src/geo_outputs_aspect.test.ts b/packages/backend-geo/src/geo_outputs_aspect.test.ts index ac12a3cd436..ed76ec21b1a 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.test.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.test.ts @@ -195,7 +195,7 @@ void describe('AmplifyGeoOutputsAspect', () => { assert.equal( JSON.parse( addBackendOutputEntryMock.mock.calls[0].arguments[1].payload.maps, - ).items[0].api_key_name, + ).items[0].apiKeyName, 'myKey', ); diff --git a/packages/backend-geo/src/geo_outputs_aspect.ts b/packages/backend-geo/src/geo_outputs_aspect.ts index 29e9a38f751..d08104944f7 100644 --- a/packages/backend-geo/src/geo_outputs_aspect.ts +++ b/packages/backend-geo/src/geo_outputs_aspect.ts @@ -12,8 +12,7 @@ export type GeoResource = AmplifyMap | AmplifyPlace | AmplifyCollection; type ResourceOutputs = { name?: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - api_key_name?: string; + apiKeyName?: string; }; /** @@ -153,7 +152,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { const mapOutputs: ResourceOutputs[] = maps.map((map): ResourceOutputs => { return { name: map.name, - api_key_name: map.resources.cfnResources.cfnAPIKey?.keyName, + apiKeyName: map.resources.cfnResources.cfnAPIKey?.keyName, }; }); @@ -161,7 +160,7 @@ export class AmplifyGeoOutputsAspect implements IAspect { (place): ResourceOutputs => { return { name: place.name, - api_key_name: place.resources.cfnResources.cfnAPIKey?.keyName, + apiKeyName: place.resources.cfnResources.cfnAPIKey?.keyName, }; }, ); diff --git a/packages/client-config/API.md b/packages/client-config/API.md index c48128fa6a4..495db16c573 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -11,7 +11,7 @@ import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client' import { S3Client } from '@aws-sdk/client-s3'; // @public -type AmazonCognitoStandardAttributes = "address" | "birthdate" | "email" | "family_name" | "gender" | "given_name" | "locale" | "middle_name" | "name" | "nickname" | "phone_number" | "picture" | "preferred_username" | "profile" | "sub" | "updated_at" | "website" | "zoneinfo"; +type AmazonCognitoStandardAttributes = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; // @public type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; @@ -65,7 +65,7 @@ interface AmazonLocationServiceConfig_6 { } // @public -type AmazonPinpointChannels = "IN_APP_MESSAGING" | "FCM" | "APNS" | "EMAIL" | "SMS"; +type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -83,7 +83,7 @@ type AmazonPinpointChannels_5 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | type AmazonPinpointChannels_6 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; // @public -type AmplifyStorageAccessActions = "read" | "get" | "list" | "write" | "delete"; +type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; // @public type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; @@ -286,19 +286,19 @@ interface AWSAmplifyBackendOutputs { require_symbols: boolean; }; oauth?: { - identity_providers: ("GOOGLE" | "FACEBOOK" | "LOGIN_WITH_AMAZON" | "SIGN_IN_WITH_APPLE")[]; + identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; domain: string; scopes: string[]; redirect_sign_in_uri: string[]; redirect_sign_out_uri: string[]; - response_type: "code" | "token"; + response_type: 'code' | 'token'; }; standard_required_attributes?: AmazonCognitoStandardAttributes[]; - username_attributes?: ("email" | "phone_number" | "username")[]; - user_verification_types?: ("email" | "phone_number")[]; + username_attributes?: ('email' | 'phone_number' | 'username')[]; + user_verification_types?: ('email' | 'phone_number')[]; unauthenticated_identities_enabled?: boolean; - mfa_configuration?: "NONE" | "OPTIONAL" | "REQUIRED"; - mfa_methods?: ("SMS" | "TOTP")[]; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; groups?: { [k: string]: AmplifyUserGroupConfig; }[]; @@ -345,7 +345,7 @@ interface AWSAmplifyBackendOutputs { bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: "1.5"; + version: '1.5'; } // @public @@ -744,7 +744,7 @@ interface AWSAmplifyBackendOutputs_6 { } // @public -type AwsAppsyncAuthorizationType = "AMAZON_COGNITO_USER_POOLS" | "API_KEY" | "AWS_IAM" | "AWS_LAMBDA" | "OPENID_CONNECT"; +type AwsAppsyncAuthorizationType = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; // @public type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; From cb01465e78d5d4de5a9c37f90a96f15c2efdaae9 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 14 Aug 2025 15:31:00 -0700 Subject: [PATCH 91/93] tackling changeset version issues --- .changeset/wild-deer-post.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/wild-deer-post.md diff --git a/.changeset/wild-deer-post.md b/.changeset/wild-deer-post.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/wild-deer-post.md @@ -0,0 +1,2 @@ +--- +--- From fb8040910302539be6672b929bc2407dc94fafd7 Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 14 Aug 2025 17:03:04 -0700 Subject: [PATCH 92/93] fixing node:crypto issues --- .../cdk/create_empty_cdk_project.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/integration-tests/src/test-project-setup/cdk/create_empty_cdk_project.ts b/packages/integration-tests/src/test-project-setup/cdk/create_empty_cdk_project.ts index b670766abae..8763623624b 100644 --- a/packages/integration-tests/src/test-project-setup/cdk/create_empty_cdk_project.ts +++ b/packages/integration-tests/src/test-project-setup/cdk/create_empty_cdk_project.ts @@ -34,5 +34,16 @@ export const createEmptyCdkProject = async ( force: true, }); + // Update tsconfig.json to include types for "node:" imports + const tsconfigPath = path.join(projectRoot, 'tsconfig.json'); + const tsconfig = JSON.parse(await fsp.readFile(tsconfigPath, 'utf-8')); + if (!tsconfig.compilerOptions.types) { + tsconfig.compilerOptions.types = []; + } + if (!tsconfig.compilerOptions.types.includes('node')) { + tsconfig.compilerOptions.types.push('node'); + } + await fsp.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2)); + return { projectName, projectRoot }; }; From 1b36c489e885347cee27498be6ad87ec4bdae8fd Mon Sep 17 00:00:00 2001 From: manavgurnani21 Date: Thu, 14 Aug 2025 17:09:21 -0700 Subject: [PATCH 93/93] updating APIs --- packages/backend-geo/API.md | 2 -- packages/client-config/API.md | 15 ++++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/backend-geo/API.md b/packages/backend-geo/API.md index 7ae4beb6bb8..6884de920a0 100644 --- a/packages/backend-geo/API.md +++ b/packages/backend-geo/API.md @@ -43,7 +43,6 @@ export type AmplifyMapFactoryProps = Omit; apiKeyProps?: GeoApiKeyProps; }; @@ -56,7 +55,6 @@ export type AmplifyPlaceFactoryProps = Omit; apiKeyProps?: GeoApiKeyProps; }; diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 8d0d376c48c..495db16c573 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -319,13 +319,10 @@ interface AWSAmplifyBackendOutputs { geo?: { aws_region: string; maps?: { - items?: { - name?: string; - key?: string; - [k: string]: unknown; - }[]; - default?: string; - required?: ['items', 'default']; + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; }; search_indices?: { items: { @@ -892,8 +889,8 @@ declare namespace clientConfigTypesV1_5 { AmplifyStorageAccessActions, AWSAmplifyBackendOutputs, AmplifyUserGroupConfig, - AmplifyStorageBucket, - AmazonLocationServiceConfig + AmazonLocationServiceConfig, + AmplifyStorageBucket } } export { clientConfigTypesV1_5 }