diff --git a/action-items.md b/action-items.md new file mode 100644 index 00000000000..eed119fcede --- /dev/null +++ b/action-items.md @@ -0,0 +1,31 @@ +# Action Items — 2026-05-01 + +Issues solved: #14517, #14487, #14813 + +## Before creating PRs + +- [ ] Review each branch: `git log -p fix/gen2-migration-14517`, `git log -p fix/gen2-migration-14487`, `git log -p fix/gen2-migration-14813` +- [ ] Review PR message files: `pr-message-14517.md`, `pr-message-14487.md`, `pr-message-14813.md` +- [ ] **Rebase #14813 onto #14517** — both modify `function.renderer.ts`, `function.generator.test.ts`, and the `product-catalog/lowstockproducts/resource.ts` snapshot. After rebase, resolve conflicts and run `UPDATE_SNAPSHOTS=1 yarn test` in the package. +- [ ] **Rebase #14813 onto #14487** (or merge order accordingly) — both modify `data.generator.test.ts`. Conflict is minor (both add new test cases at the end of the describe block). +- [ ] After rebasing, run full test suite: `cd packages/amplify-cli && yarn test` + +## File conflicts + +| File | Branches | +|------|----------| +| `function.renderer.ts` | #14517, #14813 | +| `function.generator.test.ts` | #14517, #14813 | +| `product-catalog/.../lowstockproducts/resource.ts` | #14517, #14813 | +| `data.generator.test.ts` | #14487, #14813 | + +## Snapshot conflicts + +- **product-catalog** app: `lowstockproducts/resource.ts` snapshot updated by both #14517 (adds `secret()` calls) and #14813 (removes `REGION` env var). The later branch needs rebasing + `UPDATE_SNAPSHOTS=1 yarn test`. +- **9 other apps** updated by #14813 only (REGION removal) — no conflicts. + +## Suggested merge order + +1. `fix/gen2-migration-14487` — standalone fix to `data.generator.ts`, no overlap with #14517 +2. `fix/gen2-migration-14517` — function secrets, touches `function.renderer.ts` and one snapshot +3. `fix/gen2-migration-14813` — rebase onto both #14487 and #14517 first, then run `UPDATE_SNAPSHOTS=1 yarn test` to reconcile snapshots and resolve test file conflicts diff --git a/amplify-migration-apps/backend-only/_snapshot.post.generate/amplify/function/quotegeneratorbe/resource.ts b/amplify-migration-apps/backend-only/_snapshot.post.generate/amplify/function/quotegeneratorbe/resource.ts index 42cb3e6e835..1cd539c0f1f 100644 --- a/amplify-migration-apps/backend-only/_snapshot.post.generate/amplify/function/quotegeneratorbe/resource.ts +++ b/amplify-migration-apps/backend-only/_snapshot.post.generate/amplify/function/quotegeneratorbe/resource.ts @@ -8,7 +8,7 @@ export const quotegeneratorbe = defineFunction({ name: `quotegeneratorbe-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/activityTrigger/resource.ts b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/activityTrigger/resource.ts index 54da2dfcc7c..511168a33c8 100644 --- a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/activityTrigger/resource.ts +++ b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/activityTrigger/resource.ts @@ -11,7 +11,7 @@ export const activityTrigger = defineFunction({ name: `activityTrigger-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/fetchuseractivity/resource.ts b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/fetchuseractivity/resource.ts index 8664bdc4c12..3e532975936 100644 --- a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/fetchuseractivity/resource.ts +++ b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/fetchuseractivity/resource.ts @@ -9,7 +9,7 @@ export const fetchuseractivity = defineFunction({ name: `fetchuseractivity-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/recorduseractivity/resource.ts b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/recorduseractivity/resource.ts index a562bc46144..de05d79369a 100644 --- a/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/recorduseractivity/resource.ts +++ b/amplify-migration-apps/discussions/_snapshot.post.generate/amplify/function/recorduseractivity/resource.ts @@ -11,7 +11,7 @@ export const recorduseractivity = defineFunction({ name: `recorduseractivity-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/admin/resource.ts b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/admin/resource.ts index 2284bf6f866..9cba670a32d 100644 --- a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/admin/resource.ts +++ b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/admin/resource.ts @@ -9,7 +9,7 @@ export const admin = defineFunction({ name: `admin-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/fitnesstracker33f5545533f55455PreSignup/resource.ts b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/fitnesstracker33f5545533f55455PreSignup/resource.ts index 9e9aca66e92..6a191c68abc 100644 --- a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/fitnesstracker33f5545533f55455PreSignup/resource.ts +++ b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/fitnesstracker33f5545533f55455PreSignup/resource.ts @@ -11,7 +11,7 @@ export const fitnesstracker33f5545533f55455PreSignup = defineFunction({ environment: { ENV: `${branchName}`, MODULES: 'email-filter-allowlist', - REGION: 'us-east-1', + REGION: process.env.AWS_REGION ?? '', DOMAINALLOWLIST: 'amazon.com', DOMAINBLACKLIST: '', }, diff --git a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/lognutrition/resource.ts b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/lognutrition/resource.ts index f91b15e0335..ad5c4134c76 100644 --- a/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/lognutrition/resource.ts +++ b/amplify-migration-apps/fitness-tracker/_snapshot.post.generate/amplify/function/lognutrition/resource.ts @@ -8,7 +8,7 @@ export const lognutrition = defineFunction({ name: `lognutrition-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/imported-resources/_snapshot.post.generate/amplify/function/importedresourcequotegenerator/resource.ts b/amplify-migration-apps/imported-resources/_snapshot.post.generate/amplify/function/importedresourcequotegenerator/resource.ts index 307ff1daa50..e6edfd49ccd 100644 --- a/amplify-migration-apps/imported-resources/_snapshot.post.generate/amplify/function/importedresourcequotegenerator/resource.ts +++ b/amplify-migration-apps/imported-resources/_snapshot.post.generate/amplify/function/importedresourcequotegenerator/resource.ts @@ -8,7 +8,7 @@ export const importedresourcequotegenerator = defineFunction({ name: `importedresourcequotegenerator-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/addusertogroup/resource.ts b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/addusertogroup/resource.ts index 11412e7bd42..631e13fe0bc 100644 --- a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/addusertogroup/resource.ts +++ b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/addusertogroup/resource.ts @@ -9,7 +9,7 @@ export const addusertogroup = defineFunction({ name: `addusertogroup-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/removeuserfromgroup/resource.ts b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/removeuserfromgroup/resource.ts index cd3b74d82c4..e5e3ff88336 100644 --- a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/removeuserfromgroup/resource.ts +++ b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/removeuserfromgroup/resource.ts @@ -9,7 +9,7 @@ export const removeuserfromgroup = defineFunction({ name: `removeuserfromgroup-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/thumbnailgen/resource.ts b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/thumbnailgen/resource.ts index ab621943884..f466aca2b77 100644 --- a/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/thumbnailgen/resource.ts +++ b/amplify-migration-apps/media-vault/_snapshot.post.generate/amplify/function/thumbnailgen/resource.ts @@ -8,7 +8,7 @@ export const thumbnailgen = defineFunction({ name: `thumbnailgen-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardGetRandomEmoji/resource.ts b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardGetRandomEmoji/resource.ts index f1196ea8fe6..e2a27ee5d31 100644 --- a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardGetRandomEmoji/resource.ts +++ b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardGetRandomEmoji/resource.ts @@ -8,7 +8,7 @@ export const moodboardGetRandomEmoji = defineFunction({ name: `moodboardGetRandomEmoji-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisReader/resource.ts b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisReader/resource.ts index ad79e142c29..1988556bbe5 100644 --- a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisReader/resource.ts +++ b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisReader/resource.ts @@ -10,7 +10,7 @@ export const moodboardKinesisReader = defineFunction({ name: `moodboardKinesisReader-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisTrigger/resource.ts b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisTrigger/resource.ts index 0a275cc4282..746ca8d3b48 100644 --- a/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisTrigger/resource.ts +++ b/amplify-migration-apps/mood-board/_snapshot.post.generate/amplify/function/moodboardKinesisTrigger/resource.ts @@ -12,7 +12,7 @@ export const moodboardKinesisTrigger = defineFunction({ name: `moodboardKinesisTrigger-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/S3Trigger1ef46783/resource.ts b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/S3Trigger1ef46783/resource.ts index f8dc77387ba..2b8861bb33d 100644 --- a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/S3Trigger1ef46783/resource.ts +++ b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/S3Trigger1ef46783/resource.ts @@ -8,7 +8,7 @@ export const S3Trigger1ef46783 = defineFunction({ name: `S3Trigger1ef46783-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts index 1f940607d11..bb4618174a5 100644 --- a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts +++ b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts @@ -10,7 +10,7 @@ export const lowstockproducts = defineFunction({ memoryMB: 128, environment: { ENV: `${branchName}`, - REGION: 'us-east-1', + REGION: process.env.AWS_REGION ?? '', LOW_STOCK_THRESHOLD: '5', PRODUCT_CATALOG_SECRET: '/amplify/productcatalog/x/AMPLIFY_lowstockproducts_PRODUCT_CATALOG_SECRET', diff --git a/amplify-migration-apps/project-boards/_snapshot.post.generate/amplify/function/quotegenerator/resource.ts b/amplify-migration-apps/project-boards/_snapshot.post.generate/amplify/function/quotegenerator/resource.ts index 4e265c41dc9..86aa24c55dc 100644 --- a/amplify-migration-apps/project-boards/_snapshot.post.generate/amplify/function/quotegenerator/resource.ts +++ b/amplify-migration-apps/project-boards/_snapshot.post.generate/amplify/function/quotegenerator/resource.ts @@ -8,7 +8,7 @@ export const quotegenerator = defineFunction({ name: `quotegenerator-${branchName}`, timeoutSeconds: 25, memoryMB: 128, - environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, + environment: { ENV: `${branchName}`, REGION: process.env.AWS_REGION ?? '' }, runtime: 22, }); diff --git a/amplify-migration-apps/store-locator/_snapshot.post.generate/amplify/function/storelocator41a9495f41a9495fPostConfirmation/resource.ts b/amplify-migration-apps/store-locator/_snapshot.post.generate/amplify/function/storelocator41a9495f41a9495fPostConfirmation/resource.ts index 1dbc4562961..dc432bc0623 100644 --- a/amplify-migration-apps/store-locator/_snapshot.post.generate/amplify/function/storelocator41a9495f41a9495fPostConfirmation/resource.ts +++ b/amplify-migration-apps/store-locator/_snapshot.post.generate/amplify/function/storelocator41a9495f41a9495fPostConfirmation/resource.ts @@ -11,7 +11,7 @@ export const storelocator41a9495f41a9495fPostConfirmation = defineFunction({ environment: { ENV: `${branchName}`, MODULES: 'add-to-group', - REGION: 'us-east-1', + REGION: process.env.AWS_REGION ?? '', GROUP: 'storeLocatorAdmin', }, runtime: 22, diff --git a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/data/data.generator.test.ts b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/data/data.generator.test.ts index 6c5c6295122..a4e53286da3 100644 --- a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/data/data.generator.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/data/data.generator.test.ts @@ -475,6 +475,7 @@ describe('DataGenerator', () => { expect(writtenFile('resource.ts')).toMatchInlineSnapshot(` "import { defineData } from '@aws-amplify/backend'; import type { Backend } from '../backend'; + import { myAuthFn } from '../function/myAuthFn/resource'; const schema = \`type Todo @model { id: ID! }\`; diff --git a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts index 46f70701cf8..e275ec6ec23 100644 --- a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts @@ -393,6 +393,106 @@ describe('FunctionGenerator', () => { `); }); + it('renders REGION env var as dynamic process.env.AWS_REGION reference', async () => { + const gen1App = await createGen1App({ + providers: { awscloudformation: { StackName: 'amplify-test-main-123456', Region: 'us-east-1' } }, + function: { + myFunc: { + service: 'Lambda', + output: { Name: 'myFunc-main-abc', Arn: 'arn:aws:lambda:us-east-1:123:function:myFunc-main-abc' }, + }, + }, + }); + jest.spyOn(gen1App, 'resourceMetaOutput').mockReturnValue('myFunc-main-abc'); + jest.spyOn(gen1App, 'json').mockReturnValue({ Resources: {} }); + jest.spyOn(gen1App, 'file').mockReturnValue('{}'); + jest.spyOn(gen1App, 'fileExists').mockReturnValue(false); + jest.spyOn(gen1App.aws, 'fetchFunctionConfig').mockResolvedValue({ + FunctionName: 'myFunc-main-abc', + Handler: 'index.handler', + Timeout: 3, + MemorySize: 128, + Runtime: 'nodejs18.x', + Environment: { Variables: { ENV: 'main', REGION: 'us-east-1' } }, + }); + jest.spyOn(gen1App.aws, 'fetchFunctionSchedule').mockResolvedValue(undefined); + + const generator = createFunctionGenerator({ gen1App, backendGenerator, packageJsonGenerator, outputDir }); + const ops = await generator.plan(); + await ops[0].execute(); + + expect(writtenFile('resource.ts')).toMatchInlineSnapshot(` + "import { defineFunction } from '@aws-amplify/backend'; + import type { Backend } from '../../backend'; + + const branchName = process.env.AWS_BRANCH ?? 'sandbox'; + + export const myFunc = defineFunction({ + entry: './index.js', + name: \`myFunc-\${branchName}\`, + timeoutSeconds: 3, + memoryMB: 128, + environment: { ENV: \`\${branchName}\`, REGION: process.env.AWS_REGION ?? '' }, + runtime: 18, + }); + + export function applyEscapeHatches(backend: Backend) { + backend.myFunc.resources.cfnResources.cfnFunction.functionName = \`myFunc-\${branchName}\`; + } + " + `); + }); + + it('renders environment block when REGION is the only env var', async () => { + const gen1App = await createGen1App({ + providers: { awscloudformation: { StackName: 'amplify-test-main-123456', Region: 'us-east-1' } }, + function: { + myFunc: { + service: 'Lambda', + output: { Name: 'myFunc-main-abc', Arn: 'arn:aws:lambda:us-east-1:123:function:myFunc-main-abc' }, + }, + }, + }); + jest.spyOn(gen1App, 'resourceMetaOutput').mockReturnValue('myFunc-main-abc'); + jest.spyOn(gen1App, 'json').mockReturnValue({ Resources: {} }); + jest.spyOn(gen1App, 'file').mockReturnValue('{}'); + jest.spyOn(gen1App, 'fileExists').mockReturnValue(false); + jest.spyOn(gen1App.aws, 'fetchFunctionConfig').mockResolvedValue({ + FunctionName: 'myFunc-main-abc', + Handler: 'index.handler', + Timeout: 3, + MemorySize: 128, + Runtime: 'nodejs18.x', + Environment: { Variables: { REGION: 'us-east-1' } }, + }); + jest.spyOn(gen1App.aws, 'fetchFunctionSchedule').mockResolvedValue(undefined); + + const generator = createFunctionGenerator({ gen1App, backendGenerator, packageJsonGenerator, outputDir }); + const ops = await generator.plan(); + await ops[0].execute(); + + expect(writtenFile('resource.ts')).toMatchInlineSnapshot(` + "import { defineFunction } from '@aws-amplify/backend'; + import type { Backend } from '../../backend'; + + const branchName = process.env.AWS_BRANCH ?? 'sandbox'; + + export const myFunc = defineFunction({ + entry: './index.js', + name: \`myFunc-\${branchName}\`, + timeoutSeconds: 3, + memoryMB: 128, + environment: { REGION: process.env.AWS_REGION ?? '' }, + runtime: 18, + }); + + export function applyEscapeHatches(backend: Backend) { + backend.myFunc.resources.cfnResources.cfnFunction.functionName = \`myFunc-\${branchName}\`; + } + " + `); + }); + it('renders API_KEY as secret when it matches SSM pattern', async () => { const gen1App = await createGen1App({ providers: { awscloudformation: { StackName: 'amplify-test-main-123456', Region: 'us-east-1' } }, diff --git a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/data/data.renderer.ts b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/data/data.renderer.ts index 7d2d7a9dcd6..d3955cd2caa 100644 --- a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/data/data.renderer.ts +++ b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/data/data.renderer.ts @@ -47,6 +47,11 @@ export class DataRenderer { const nodes: ts.Node[] = [this.renderNamedImport('defineData', '@aws-amplify/backend'), this.renderBackendTypeImport()]; + const lambdaFunctionName = this.extractLambdaFunctionName(opts.authorizationModes); + if (lambdaFunctionName) { + nodes.push(this.renderNamedImport(lambdaFunctionName, `../function/${lambdaFunctionName}/resource`)); + } + const escapeHatchResult = this.renderApplyEscapeHatches(opts); for (const imp of escapeHatchResult.additionalImports) { nodes.push(imp); @@ -320,6 +325,23 @@ export class DataRenderer { }; } + /** + * Extracts the Lambda function name from the authorization modes config, + * if a Lambda authorizer is configured. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- untyped JSON from amplify-meta.json authConfig + private extractLambdaFunctionName(authorizationModes?: any): string | undefined { + if (!authorizationModes) return undefined; + const defaultLambda = authorizationModes.defaultAuthentication?.lambdaAuthorizerConfig?.lambdaFunction; + if (defaultLambda) return defaultLambda; + for (const provider of authorizationModes.additionalAuthenticationProviders ?? []) { + if (provider.authenticationType === 'AWS_LAMBDA' && provider.lambdaAuthorizerConfig?.lambdaFunction) { + return provider.lambdaAuthorizerConfig.lambdaFunction; + } + } + return undefined; + } + /** Extracts additional auth providers from the raw AppSync API object. */ private extractAdditionalAuthProviders(graphqlApi: GraphqlApi): Record[] | undefined { return graphqlApi.additionalAuthenticationProviders?.map((provider) => ({ diff --git a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts index 7388512f5c9..42f58079389 100644 --- a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts +++ b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts @@ -308,27 +308,53 @@ export class FunctionRenderer { ): void { if (!opts.literalEnvVars || Object.keys(opts.literalEnvVars).length === 0) return; - const envProps = Object.entries(opts.literalEnvVars).map(([key, value]) => { + const envProps: ts.PropertyAssignment[] = []; + for (const [key, value] of Object.entries(opts.literalEnvVars)) { + // REGION: resolve dynamically from AWS_REGION (auto-injected by Lambda) instead of hardcoding. + if (key === 'REGION') { + envProps.push( + factory.createPropertyAssignment( + key, + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createPropertyAccessExpression(factory.createIdentifier('process'), 'env'), + 'AWS_REGION', + ), + ts.SyntaxKind.QuestionQuestionToken, + factory.createStringLiteral(''), + ), + ), + ); + continue; + } + if (key === 'API_KEY' && value.startsWith(`/amplify/${this.appId}/${this.backendEnvironmentName}`)) { namedImports['@aws-amplify/backend'].add('secret'); - return factory.createPropertyAssignment( - key, - factory.createCallExpression(factory.createIdentifier('secret'), undefined, [factory.createStringLiteral('API_KEY')]), + envProps.push( + factory.createPropertyAssignment( + key, + factory.createCallExpression(factory.createIdentifier('secret'), undefined, [factory.createStringLiteral('API_KEY')]), + ), ); + continue; } if (key === 'ENV') { - return factory.createPropertyAssignment( - key, - factory.createTemplateExpression(factory.createTemplateHead(''), [ - factory.createTemplateSpan(factory.createIdentifier('branchName'), factory.createTemplateTail('')), - ]), + envProps.push( + factory.createPropertyAssignment( + key, + factory.createTemplateExpression(factory.createTemplateHead(''), [ + factory.createTemplateSpan(factory.createIdentifier('branchName'), factory.createTemplateTail('')), + ]), + ), ); + continue; } - return factory.createPropertyAssignment(key, factory.createStringLiteral(value)); - }); + envProps.push(factory.createPropertyAssignment(key, factory.createStringLiteral(value))); + } + if (envProps.length === 0) return; target.push(factory.createPropertyAssignment('environment', factory.createObjectLiteralExpression(envProps))); } diff --git a/pr-message-14487.md b/pr-message-14487.md new file mode 100644 index 00000000000..9fa036fe941 --- /dev/null +++ b/pr-message-14487.md @@ -0,0 +1,53 @@ +Solves #14487. + +## Issue Summary + +The `generate` command sometimes omits `migratedAmplifyGen1DynamoDbTableMappings` from the data resource. This is caused by the model-detection regex `type\s+(\w+)\s+@model` failing when `@model` is not the first directive on a type (e.g., `type Todo @auth(...) @model`). + +## Reasoning + +1. The issue describes the bug as "sporadic," but investigation revealed it's actually deterministic based on directive ordering in the GraphQL schema. +2. Read `data.generator.ts` — found `createTableMappings()` which uses `type\s+(\w+)\s+@model` to find model types. This regex requires `@model` to immediately follow the type name, so `type Todo @auth(...) @model` doesn't match. +3. The compiled `build/schema.graphql` (produced by the Amplify transformer) is more reliable — it expands each `@model` type into a `ModelConnection` type. Matching `type Model(\w+)Connection` against the build schema is directive-order-independent. +4. The fix reads `build/schema.graphql` when available and falls back to the raw schema regex when it's not. + +## Solution + +- **`data.generator.ts`** — `createTableMappings()` now first checks for `build/schema.graphql` in the API resource directory. If present, it extracts model names by matching `type Model(\w+)Connection` patterns. Falls back to the original `@model` regex when the build schema is absent. +- **`data.generator.test.ts`** — Added test `'renders table mappings when @model is not the first directive'` with a schema where `@auth` and `@key` precede `@model`, verifying both types are correctly mapped. + +## Example + +**Input (Gen 1 — schema.graphql):** +```graphql +type Todo @auth(rules: [{ allow: public }]) @model { + id: ID! +} +type Post @key(name: "byUser") @model { + id: ID! +} +``` + +**Output — before fix (resource.ts):** +```ts +export const data = defineData({ + // migratedAmplifyGen1DynamoDbTableMappings is MISSING + schema, +}); +``` + +**Output — after fix (resource.ts):** +```ts +export const data = defineData({ + migratedAmplifyGen1DynamoDbTableMappings: [ + { + branchName: 'main', + modelNameToTableNameMapping: { + Todo: 'Todo-abc-main', + Post: 'Post-abc-main', + }, + }, + ], + schema, +}); +``` diff --git a/pr-message-14517.md b/pr-message-14517.md new file mode 100644 index 00000000000..ad079193509 --- /dev/null +++ b/pr-message-14517.md @@ -0,0 +1,56 @@ +Solves #14517. + +## Issue Summary + +When a Gen 1 function has secrets stored as SSM `SecureString` parameters, the `generate` command outputs the raw SSM path as a string literal in the `environment` block instead of using Gen 2's `secret()` API. The fix generalizes the existing `API_KEY`-only secret detection to recognize any env var whose value matches the SSM secret path pattern. + +## Reasoning + +1. In Gen 1, secrets are stored as SSM SecureString parameters at `/amplify/{appId}/{env}/AMPLIFY_{funcName}_{KEY}`. The path is set as the env var value, and the function fetches it at runtime via `@aws-sdk/client-ssm`. +2. In Gen 2, `secret('KEY')` handles this automatically — the value is fetched at function load time and available via `process.env.KEY`. +3. Read `function.renderer.ts` — found `renderEnvironment()` which already had a special case for `API_KEY` that checked if the value starts with the SSM prefix and emits `secret('API_KEY')`. But it was hardcoded to only match the key `API_KEY`. +4. The fix generalizes the condition: any env var whose value starts with `/amplify/{appId}/{envName}/` is treated as a secret, using the env var's own key name in the `secret()` call. + +## Solution + +- **`function.renderer.ts`** — Changed the secret detection from `key === 'API_KEY' && value.startsWith(...)` to just `value.startsWith(ssmSecretPrefix)`. The `secret()` call now uses the actual key name instead of hardcoded `'API_KEY'`. +- **`function.generator.test.ts`** — Added test `'renders SSM secret env vars as secret() calls'` that mocks a function with two SSM-path env vars and one regular env var, verifying the output uses `secret('MY_SECRET')` and `secret('ANOTHER_SECRET')` while keeping `DB_HOST: 'localhost'` as a literal. +- **product-catalog snapshot** — Updated `lowstockproducts/resource.ts` golden snapshot (the function had an SSM-path env var that now renders as `secret()`). + +## Example + +**Input (Gen 1 — deployed function config):** +```json +{ + "Environment": { + "Variables": { + "MY_SECRET": "/amplify/d1abc2def3/main/AMPLIFY_myFunc_MY_SECRET", + "DB_HOST": "localhost" + } + } +} +``` + +**Output — before fix (resource.ts):** +```ts +import { defineFunction } from '@aws-amplify/backend'; + +export const myFunc = defineFunction({ + environment: { + MY_SECRET: '/amplify/d1abc2def3/main/AMPLIFY_myFunc_MY_SECRET', + DB_HOST: 'localhost', + }, +}); +``` + +**Output — after fix (resource.ts):** +```ts +import { defineFunction, secret } from '@aws-amplify/backend'; + +export const myFunc = defineFunction({ + environment: { + MY_SECRET: secret('MY_SECRET'), + DB_HOST: 'localhost', + }, +}); +``` diff --git a/pr-message-14813.md b/pr-message-14813.md new file mode 100644 index 00000000000..2d077685bad --- /dev/null +++ b/pr-message-14813.md @@ -0,0 +1,102 @@ +Solves #14813. + +## Issue Summary + +Two bugs in the Gen1-to-Gen2 migration `generate` step: + +1. **Missing Lambda authorizer import**: When a GraphQL API uses a Lambda authorizer as an additional auth mode, the generated `amplify/data/resource.ts` references the function variable in `lambdaAuthorizationMode` without importing it, causing a build failure. + +2. **Hardcoded REGION env var**: The generated `amplify/function/*/resource.ts` files hardcode `REGION` to the literal resolved value (e.g., `'us-east-1'`) from the deployed CloudFormation template, instead of resolving dynamically. This produces incorrect behavior when deployed to a different region. + +## Reasoning + +**Bug (a):** The `DataRenderer.addLambdaConfig()` method creates `factory.createIdentifier(lambdaFunction)` to reference the function variable in the `lambdaAuthorizationMode` config, but the `render()` method never adds a corresponding import statement. The function is defined in `amplify/function//resource.ts` and needs to be imported into `data/resource.ts`. + +**Bug (b):** The `FunctionRenderer.renderEnvironment()` method handles `ENV` specially (converting it to `${branchName}`) but treats `REGION` as a plain literal string. When the deployed CFN template has `{"Ref": "AWS::Region"}`, the resolved value (e.g., `us-east-1`) gets passed as a literal. Since AWS Lambda automatically provides `process.env.AWS_REGION` in every execution environment, the `REGION` env var should be omitted entirely. + +## Solution + +**`data.renderer.ts`** — Added `extractLambdaFunctionName()` private method that reads the `authorizationModes` config (both default and additional auth providers) and returns the Lambda function name if a Lambda authorizer is configured. The `render()` method now calls this and emits `import { } from '../function//resource'` when present. + +**`function.renderer.ts`** — Updated `renderEnvironment()` to skip `REGION` entries entirely. Also added a guard so the `environment` block is omitted when all env vars are filtered out (e.g., when REGION was the only one). Changed from `.map()` to a `for...of` loop with `continue` for cleaner control flow. + +**Tests** — Updated the inline snapshot in `data.generator.test.ts` for the "renders Lambda auth mode" test. Added two new tests in `function.generator.test.ts`: one verifying REGION is omitted alongside other env vars, and one verifying the environment block is omitted entirely when REGION is the only env var. + +**Golden snapshots** — Updated 18 migration app snapshot files across 9 apps where function `resource.ts` files previously contained `REGION: 'us-east-1'`. + +## Example + +**Input (Gen 1 / pre-generate):** + +`cli-inputs.json`: +```json +{ + "additionalAuthTypes": [ + { + "mode": "AWS_LAMBDA", + "lambdaFunction": "graphQlLambdaAuthorizerd351d098", + "ttlSeconds": "300" + } + ] +} +``` + +Lambda CFN template: +```json +"Environment": { + "Variables": { + "ENV": { "Ref": "env" }, + "REGION": { "Ref": "AWS::Region" } + } +} +``` + +**Output — before fix (post-generate):** + +`data/resource.ts`: +```typescript +import { defineData } from '@aws-amplify/backend'; +// ❌ Missing: import { graphQlLambdaAuthorizerd351d098 } from '../function/graphQlLambdaAuthorizerd351d098/resource'; + +export const data = defineData({ + authorizationModes: { + lambdaAuthorizationMode: { + function: graphQlLambdaAuthorizerd351d098, // ❌ undefined + timeToLiveInSeconds: 300, + }, + }, + schema, +}); +``` + +`function/graphQlLambdaAuthorizerd351d098/resource.ts`: +```typescript +export const graphQlLambdaAuthorizerd351d098 = defineFunction({ + environment: { ENV: `${branchName}`, REGION: 'us-east-1' }, // ❌ hardcoded +}); +``` + +**Output — after fix (post-generate):** + +`data/resource.ts`: +```typescript +import { defineData } from '@aws-amplify/backend'; +import { graphQlLambdaAuthorizerd351d098 } from '../function/graphQlLambdaAuthorizerd351d098/resource'; + +export const data = defineData({ + authorizationModes: { + lambdaAuthorizationMode: { + function: graphQlLambdaAuthorizerd351d098, // ✅ imported + timeToLiveInSeconds: 300, + }, + }, + schema, +}); +``` + +`function/graphQlLambdaAuthorizerd351d098/resource.ts`: +```typescript +export const graphQlLambdaAuthorizerd351d098 = defineFunction({ + environment: { ENV: `${branchName}` }, // ✅ REGION omitted — AWS_REGION is auto-available +}); +```