From ba8fc32171caa098885633ab87e775a19c08ba6a Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Thu, 13 Nov 2025 17:03:20 +0100 Subject: [PATCH 01/12] feat(cloudfront): add NodejsEdgeFunction construct Adds NodejsEdgeFunction construct that combines EdgeFunction's cross-region deployment capabilities with NodejsFunction's TypeScript bundling via esbuild. This addresses the feature request in issue #12671 by providing a convenient way to deploy TypeScript Lambda@Edge functions without manual bundling. Key features: - Extends EdgeFunction for automatic us-east-1 deployment - Reuses NodejsFunction's prepareBundling utility for esbuild integration - Validates runtime is Node.js family only - Supports all NodejsFunction bundling options (minify, sourceMap, etc.) - Includes integration test with AWS API assertions Closes #12671 --- ...integ.distribution-nodejs-edge-function.ts | 68 +++++++++++++++++++ .../test/nodejs-edge-handler/index.ts | 14 ++++ .../aws-cloudfront/lib/experimental/index.ts | 1 + .../lib/experimental/nodejs-edge-function.ts | 40 +++++++++++ 4 files changed, 123 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts create mode 100644 packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts new file mode 100644 index 0000000000000..2a28c838558a2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts @@ -0,0 +1,68 @@ +/// !cdk-integ * +import * as path from 'path'; +import * as cdk from 'aws-cdk-lib'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { ExpectedResult } from '@aws-cdk/integ-tests-alpha'; +import { TestOrigin } from './test-origin'; +import { STANDARD_NODEJS_RUNTIME } from '../../config'; + +const app = new cdk.App(); + +const region = 'eu-west-1'; +const stack = new cdk.Stack(app, 'integ-nodejs-edge-function', { + env: { region: region }, +}); + +// Test: NodejsEdgeFunction with cross-region deployment +const edgeFunction = new cloudfront.experimental.NodejsEdgeFunction(stack, 'NodejsEdge', { + entry: path.join(__dirname, 'nodejs-edge-handler', 'index.ts'), + runtime: STANDARD_NODEJS_RUNTIME, +}); + +// Attach to CloudFront to verify integration +const distribution = new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { + origin: new TestOrigin('www.example.com'), + cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, + edgeLambdas: [{ + functionVersion: edgeFunction.currentVersion, + eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST, + }], + }, +}); + +const integTest = new integ.IntegTest(app, 'cdk-integ-nodejs-edge-function', { + testCases: [stack], + diffAssets: true, +}); + +// Assertion 1: Verify Lambda exists in us-east-1 +integTest.assertions.awsApiCall('Lambda', 'getFunction', { + FunctionName: edgeFunction.functionName, +}).expect(ExpectedResult.objectLike({ + Configuration: { + FunctionName: edgeFunction.functionName, + }, +})).provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['lambda:GetFunction'], + Resource: '*', +}); + +// Assertion 2: Verify CloudFront distribution has the edge lambda attached +integTest.assertions.awsApiCall('CloudFront', 'getDistributionConfig', { + Id: distribution.distributionId, +}).expect(ExpectedResult.objectLike({ + DistributionConfig: { + DefaultCacheBehavior: { + LambdaFunctionAssociations: { + Quantity: 1, + }, + }, + }, +})).provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['cloudfront:GetDistributionConfig'], + Resource: '*', +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts new file mode 100644 index 0000000000000..05f50f12cdbbc --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts @@ -0,0 +1,14 @@ +import { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda'; + +export const handler = async (event: CloudFrontRequestEvent): Promise => { + const request = event.Records[0].cf.request; + + // Simple TypeScript logic to verify bundling works + const customHeader: string = 'x-custom-header'; + request.headers[customHeader] = [{ + key: customHeader, + value: 'test-value', + }]; + + return request; +}; diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/index.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/index.ts index a1362556b10ea..03045a1d17e25 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/index.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/index.ts @@ -1 +1,2 @@ export * from './edge-function'; +export * from './nodejs-edge-function'; diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts new file mode 100644 index 0000000000000..2a259d3609165 --- /dev/null +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -0,0 +1,40 @@ +import { Construct } from 'constructs'; +import { EdgeFunction } from './edge-function'; +import * as lambda from '../../../aws-lambda'; +import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; +import { prepareBundling } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; +import { ValidationError } from '../../../core'; + +/** + * Properties for a NodejsEdgeFunction + */ +export interface NodejsEdgeFunctionProps extends NodejsFunctionProps { + /** + * The stack ID of Lambda@Edge function. + * + * @default - `edge-lambda-stack-${region}` + */ + readonly stackId?: string; +} + +/** + * A Node.js Lambda@Edge function bundled using esbuild + * + * @resource AWS::Lambda::Function + */ +export class NodejsEdgeFunction extends EdgeFunction { + constructor(scope: Construct, id: string, props: NodejsEdgeFunctionProps = {}) { + if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) { + throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); + } + + const bundlingConfig = prepareBundling(scope, id, props, 'NodejsEdgeFunction'); + + super(scope, id, { + ...props, + runtime: bundlingConfig.runtime, + code: bundlingConfig.code, + handler: bundlingConfig.handler, + }); + } +} From be4c0a96516d28af2e832195f7ace370bd8896a8 Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 16:19:09 +0100 Subject: [PATCH 02/12] fix: restore helper functions removed by autofix Autofix incorrectly removed the helper functions (getRuntime, findLockFile, findEntry, findDefiningFile) and broke imports. Restored all functions and fixed import paths. --- .../lib/experimental/nodejs-edge-function.ts | 154 ++++++++++++++++-- 1 file changed, 144 insertions(+), 10 deletions(-) diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index 2a259d3609165..66124d4d6e178 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -1,10 +1,15 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { Construct } from 'constructs'; import { EdgeFunction } from './edge-function'; import * as lambda from '../../../aws-lambda'; +import { Architecture } from '../../../aws-lambda'; import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; -import { prepareBundling } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; -import { ValidationError } from '../../../core'; - +import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; +import { callsites, findUpMultiple } from '../../../aws-lambda-nodejs/lib/util'; +import { LockFile } from '../../../aws-lambda-nodejs/lib/package-manager'; +import { FeatureFlags, ValidationError } from '../../../core'; +import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../../cx-api'; /** * Properties for a NodejsEdgeFunction */ @@ -28,13 +33,142 @@ export class NodejsEdgeFunction extends EdgeFunction { throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); } - const bundlingConfig = prepareBundling(scope, id, props, 'NodejsEdgeFunction'); + const runtime = getRuntime(scope, props); + + if (props.code !== undefined) { + if (props.handler === undefined) { + throw new ValidationError( + 'Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.\n' + + 'The handler should be the name of the exported function to be invoked and the file containing that function.\n' + + 'For example, handler should be specified in the form `myFile.myFunction`', scope, + ); + } + + super(scope, id, { + ...props, + runtime, + code: props.code, + handler: props.handler, + }); + } else { + // Entry and defaults + const entry = path.resolve(findEntry(scope, id, props.entry)); + const architecture = props.architecture ?? Architecture.X86_64; + const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); + const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); + const handler = props.handler ?? 'handler'; + + super(scope, id, { + ...props, + runtime, + code: Bundling.bundle(scope, { + ...props.bundling ?? {}, + entry, + runtime, + architecture, + depsLockFilePath, + projectRoot, + }), + handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`, + }); + } + } +} + +/** + * Check if the feature flag is enabled and default to NODEJS_LATEST if so. + * Otherwise default to NODEJS_16_X. + */ +function getRuntime(scope: Construct, props: NodejsEdgeFunctionProps): lambda.Runtime { + const defaultRuntime = FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_USE_LATEST_RUNTIME) + ? lambda.Runtime.NODEJS_LATEST + : lambda.Runtime.NODEJS_16_X; + return props.runtime ?? defaultRuntime; +} + +/** + * Checks given lock file or searches for a lock file + */ +function findLockFile(scope: Construct, depsLockFilePath?: string): string { + if (depsLockFilePath) { + if (!fs.existsSync(depsLockFilePath)) { + throw new ValidationError(`Lock file at ${depsLockFilePath} doesn't exist`, scope); + } + + if (!fs.statSync(depsLockFilePath).isFile()) { + throw new ValidationError('`depsLockFilePath` should point to a file', scope); + } - super(scope, id, { - ...props, - runtime: bundlingConfig.runtime, - code: bundlingConfig.code, - handler: bundlingConfig.handler, - }); + return path.resolve(depsLockFilePath); } + + const lockFiles = findUpMultiple([ + LockFile.PNPM, + LockFile.YARN, + LockFile.NPM, + LockFile.BUN, + LockFile.BUN_LOCK, + ]); + + if (lockFiles.length === 0) { + throw new ValidationError('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`, `bun.lockb` or `bun.lock`). Please specify it with `depsLockFilePath`.', scope); + } + if (lockFiles.length > 1) { + throw new ValidationError(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsLockFilePath\`.`, scope); + } + + return lockFiles[0]; +} + +/** + * Searches for an entry file. Preference order is the following: + * 1. Given entry file + * 2. A .ts file named as the defining file with id as suffix (defining-file.id.ts) + * 3. A .js file name as the defining file with id as suffix (defining-file.id.js) + * 4. A .mjs file name as the defining file with id as suffix (defining-file.id.mjs) + * 5. A .mts file name as the defining file with id as suffix (defining-file.id.mts) + */ +function findEntry(scope: Construct, id: string, entry?: string): string { + if (entry) { + if (!/\.(jsx?|tsx?|mjsx?|mtsx?)$/.test(entry)) { + throw new ValidationError('Only JavaScript or TypeScript entry files are supported.', scope); + } + if (!fs.existsSync(entry)) { + throw new ValidationError(`Cannot find entry file at ${entry}`, scope); + } + return entry; + } + + const definingFile = findDefiningFile(); + const extname = path.extname(definingFile); + const basename = definingFile.replace(new RegExp(`${extname}$`), ''); + + for (const ext of ['.ts', '.js', '.mjs', '.mts']) { + const file = `${basename}.${id}${ext}`; + if (fs.existsSync(file)) { + return file; + } + } + + throw new ValidationError(`Cannot find entry file at ${basename}.${id}.ts, ${basename}.${id}.js, ${basename}.${id}.mjs, or ${basename}.${id}.mts`, scope); +} + +/** + * Finds the name of the file where the `NodejsEdgeFunction` is defined + */ +function findDefiningFile(): string { + let definingIndex; + const sites = callsites(); + for (const [index, site] of sites.entries()) { + if (site.getFunctionName() === 'NodejsEdgeFunction') { + definingIndex = index + 1; + break; + } + } + + if (!definingIndex || !sites[definingIndex]) { + throw new Error('Cannot find defining file.'); + } + + return sites[definingIndex].getFileName(); } From 0ebd0f25dfa2bd14749faa95a5b7455266d526ad Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 16:22:00 +0100 Subject: [PATCH 03/12] refactor: use prepareBundling from lambda-nodejs instead of duplicating logic The prepareBundling function and all helper functions already exist in aws-lambda-nodejs/lib/bundling-preparation. Use them directly instead of duplicating the code. --- .../lib/experimental/nodejs-edge-function.ts | 152 ++---------------- 1 file changed, 9 insertions(+), 143 deletions(-) diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index 66124d4d6e178..f5a95f70c93cd 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -1,15 +1,9 @@ -import * as fs from 'fs'; -import * as path from 'path'; import { Construct } from 'constructs'; import { EdgeFunction } from './edge-function'; import * as lambda from '../../../aws-lambda'; -import { Architecture } from '../../../aws-lambda'; import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; -import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; -import { callsites, findUpMultiple } from '../../../aws-lambda-nodejs/lib/util'; -import { LockFile } from '../../../aws-lambda-nodejs/lib/package-manager'; -import { FeatureFlags, ValidationError } from '../../../core'; -import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../../cx-api'; +import { prepareBundling } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; +import { ValidationError } from '../../../core'; /** * Properties for a NodejsEdgeFunction */ @@ -33,142 +27,14 @@ export class NodejsEdgeFunction extends EdgeFunction { throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); } - const runtime = getRuntime(scope, props); + const bundlingConfig = prepareBundling(scope, id, props, 'NodejsEdgeFunction'); - if (props.code !== undefined) { - if (props.handler === undefined) { - throw new ValidationError( - 'Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.\n' - + 'The handler should be the name of the exported function to be invoked and the file containing that function.\n' - + 'For example, handler should be specified in the form `myFile.myFunction`', scope, - ); - } - - super(scope, id, { - ...props, - runtime, - code: props.code, - handler: props.handler, - }); - } else { - // Entry and defaults - const entry = path.resolve(findEntry(scope, id, props.entry)); - const architecture = props.architecture ?? Architecture.X86_64; - const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); - const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); - const handler = props.handler ?? 'handler'; - - super(scope, id, { - ...props, - runtime, - code: Bundling.bundle(scope, { - ...props.bundling ?? {}, - entry, - runtime, - architecture, - depsLockFilePath, - projectRoot, - }), - handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`, - }); - } + super(scope, id, { + ...props, + runtime: bundlingConfig.runtime, + code: bundlingConfig.code, + handler: bundlingConfig.handler, + }); } } -/** - * Check if the feature flag is enabled and default to NODEJS_LATEST if so. - * Otherwise default to NODEJS_16_X. - */ -function getRuntime(scope: Construct, props: NodejsEdgeFunctionProps): lambda.Runtime { - const defaultRuntime = FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_USE_LATEST_RUNTIME) - ? lambda.Runtime.NODEJS_LATEST - : lambda.Runtime.NODEJS_16_X; - return props.runtime ?? defaultRuntime; -} - -/** - * Checks given lock file or searches for a lock file - */ -function findLockFile(scope: Construct, depsLockFilePath?: string): string { - if (depsLockFilePath) { - if (!fs.existsSync(depsLockFilePath)) { - throw new ValidationError(`Lock file at ${depsLockFilePath} doesn't exist`, scope); - } - - if (!fs.statSync(depsLockFilePath).isFile()) { - throw new ValidationError('`depsLockFilePath` should point to a file', scope); - } - - return path.resolve(depsLockFilePath); - } - - const lockFiles = findUpMultiple([ - LockFile.PNPM, - LockFile.YARN, - LockFile.NPM, - LockFile.BUN, - LockFile.BUN_LOCK, - ]); - - if (lockFiles.length === 0) { - throw new ValidationError('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`, `bun.lockb` or `bun.lock`). Please specify it with `depsLockFilePath`.', scope); - } - if (lockFiles.length > 1) { - throw new ValidationError(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsLockFilePath\`.`, scope); - } - - return lockFiles[0]; -} - -/** - * Searches for an entry file. Preference order is the following: - * 1. Given entry file - * 2. A .ts file named as the defining file with id as suffix (defining-file.id.ts) - * 3. A .js file name as the defining file with id as suffix (defining-file.id.js) - * 4. A .mjs file name as the defining file with id as suffix (defining-file.id.mjs) - * 5. A .mts file name as the defining file with id as suffix (defining-file.id.mts) - */ -function findEntry(scope: Construct, id: string, entry?: string): string { - if (entry) { - if (!/\.(jsx?|tsx?|mjsx?|mtsx?)$/.test(entry)) { - throw new ValidationError('Only JavaScript or TypeScript entry files are supported.', scope); - } - if (!fs.existsSync(entry)) { - throw new ValidationError(`Cannot find entry file at ${entry}`, scope); - } - return entry; - } - - const definingFile = findDefiningFile(); - const extname = path.extname(definingFile); - const basename = definingFile.replace(new RegExp(`${extname}$`), ''); - - for (const ext of ['.ts', '.js', '.mjs', '.mts']) { - const file = `${basename}.${id}${ext}`; - if (fs.existsSync(file)) { - return file; - } - } - - throw new ValidationError(`Cannot find entry file at ${basename}.${id}.ts, ${basename}.${id}.js, ${basename}.${id}.mjs, or ${basename}.${id}.mts`, scope); -} - -/** - * Finds the name of the file where the `NodejsEdgeFunction` is defined - */ -function findDefiningFile(): string { - let definingIndex; - const sites = callsites(); - for (const [index, site] of sites.entries()) { - if (site.getFunctionName() === 'NodejsEdgeFunction') { - definingIndex = index + 1; - break; - } - } - - if (!definingIndex || !sites[definingIndex]) { - throw new Error('Cannot find defining file.'); - } - - return sites[definingIndex].getFileName(); -} From b620ac3c0e5c6141f72fa1ea892a6d88bb16cddf Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 16:42:42 +0100 Subject: [PATCH 04/12] extract out helper function into separate file --- .../lib/experimental/nodejs-edge-function.ts | 51 +++++-- .../aws-lambda-nodejs/lib/function-helpers.ts | 135 +++++++++++++++++ .../aws-lambda-nodejs/lib/function.ts | 136 +----------------- 3 files changed, 182 insertions(+), 140 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index f5a95f70c93cd..516e97cb578d9 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -1,9 +1,13 @@ +import * as path from 'path'; import { Construct } from 'constructs'; import { EdgeFunction } from './edge-function'; import * as lambda from '../../../aws-lambda'; +import { Architecture } from '../../../aws-lambda'; import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; -import { prepareBundling } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; +import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; +import { getRuntime, findEntry, findLockFile } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; import { ValidationError } from '../../../core'; + /** * Properties for a NodejsEdgeFunction */ @@ -27,14 +31,45 @@ export class NodejsEdgeFunction extends EdgeFunction { throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); } - const bundlingConfig = prepareBundling(scope, id, props, 'NodejsEdgeFunction'); + const runtime = getRuntime(scope, props); + + if (props.code !== undefined) { + if (props.handler === undefined) { + throw new ValidationError( + 'Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.\n' + + 'The handler should be the name of the exported function to be invoked and the file containing that function.\n' + + 'For example, handler should be specified in the form `myFile.myFunction`', scope, + ); + } - super(scope, id, { - ...props, - runtime: bundlingConfig.runtime, - code: bundlingConfig.code, - handler: bundlingConfig.handler, - }); + super(scope, id, { + ...props, + runtime, + code: props.code, + handler: props.handler, + }); + } else { + // Entry and defaults + const entry = path.resolve(findEntry(scope, id, props.entry, 'NodejsEdgeFunction')); + const architecture = props.architecture ?? Architecture.X86_64; + const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); + const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); + const handler = props.handler ?? 'handler'; + + super(scope, id, { + ...props, + runtime, + code: Bundling.bundle(scope, { + ...props.bundling ?? {}, + entry, + runtime, + architecture, + depsLockFilePath, + projectRoot, + }), + handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`, + }); + } } } diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts new file mode 100644 index 0000000000000..7009b239437ef --- /dev/null +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts @@ -0,0 +1,135 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Construct } from 'constructs'; +import { LockFile } from './package-manager'; +import { callsites, findUpMultiple } from './util'; +import * as lambda from '../../aws-lambda'; +import { FeatureFlags, ValidationError } from '../../core'; +import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../cx-api'; +import { NodejsFunctionProps } from './function'; + +/** + * Check if the feature flag is enabled and default to NODEJS_LATEST if so. + * Otherwise default to NODEJS_16_X. + */ +export function getRuntime(scope: Construct, props: NodejsFunctionProps): lambda.Runtime { + const defaultRuntime = FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_USE_LATEST_RUNTIME) + ? lambda.Runtime.NODEJS_LATEST + : lambda.Runtime.NODEJS_16_X; + return props.runtime ?? defaultRuntime; +} + +/** + * Checks given lock file or searches for a lock file + */ +export function findLockFile(scope: Construct, depsLockFilePath?: string): string { + if (depsLockFilePath) { + if (!fs.existsSync(depsLockFilePath)) { + throw new ValidationError(`Lock file at ${depsLockFilePath} doesn't exist`, scope); + } + + if (!fs.statSync(depsLockFilePath).isFile()) { + throw new ValidationError('`depsLockFilePath` should point to a file', scope); + } + + return path.resolve(depsLockFilePath); + } + + const lockFiles = findUpMultiple([ + LockFile.PNPM, + LockFile.YARN, + LockFile.BUN_LOCK, + LockFile.BUN, + LockFile.NPM, + ]); + + if (lockFiles.length === 0) { + throw new ValidationError('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, `bun.lock` or `package-lock.json`). Please specify it with `depsLockFilePath`.', scope); + } + if (lockFiles.length > 1) { + throw new ValidationError(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsLockFilePath\`.`, scope); + } + + return lockFiles[0]; +} + +/** + * Searches for an entry file. Preference order is the following: + * 1. Given entry file + * 2. A .ts file named as the defining file with id as suffix (defining-file.id.ts) + * 3. A .js file name as the defining file with id as suffix (defining-file.id.js) + * 4. A .mjs file name as the defining file with id as suffix (defining-file.id.mjs) + * 5. A .mts file name as the defining file with id as suffix (defining-file.id.mts) + * 6. A .cts file name as the defining file with id as suffix (defining-file.id.cts) + * 7. A .cjs file name as the defining file with id as suffix (defining-file.id.cjs) + */ +export function findEntry(scope: Construct, id: string, entry?: string): string { + if (entry) { + if (!/\.(jsx?|tsx?|cjs|cts|mjs|mts)$/.test(entry)) { + throw new ValidationError('Only JavaScript or TypeScript entry files are supported.', scope); + } + if (!fs.existsSync(entry)) { + throw new ValidationError(`Cannot find entry file at ${entry}`, scope); + } + return entry; + } + + const definingFile = findDefiningFile(scope); + const extname = path.extname(definingFile); + + const tsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.ts`); + if (fs.existsSync(tsHandlerFile)) { + return tsHandlerFile; + } + + const jsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.js`); + if (fs.existsSync(jsHandlerFile)) { + return jsHandlerFile; + } + + const mjsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.mjs`); + if (fs.existsSync(mjsHandlerFile)) { + return mjsHandlerFile; + } + + const mtsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.mts`); + if (fs.existsSync(mtsHandlerFile)) { + return mtsHandlerFile; + } + + const ctsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.cts`); + if (fs.existsSync(ctsHandlerFile)) { + return ctsHandlerFile; + } + + const cjsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.cjs`); + if (fs.existsSync(cjsHandlerFile)) { + return cjsHandlerFile; + } + + throw new ValidationError(`Cannot find handler file ${tsHandlerFile}, ${jsHandlerFile}, ${mjsHandlerFile}, ${mtsHandlerFile}, ${ctsHandlerFile} or ${cjsHandlerFile}`, scope); +} + +/** + * Finds the name of the file where the `NodejsFunction` is defined + */ +export function findDefiningFile(scope: Construct): string { + let definingIndex; + const sites = callsites(); + for (const [index, site] of sites.entries()) { + if (site.getFunctionName() === 'NodejsFunction') { + // The next site is the site where the NodejsFunction was created + definingIndex = index + 1; + break; + } + } + + if (!definingIndex || !sites[definingIndex]) { + throw new ValidationError('Cannot find defining file.', scope); + } + + // Fixes issue #21630. + // ESM modules return a 'file://' prefix to the filenames, this should be removed for + // compatibility with the NodeJS filesystem functions. + return sites[definingIndex].getFileName().replace(/^file:\/\//, ''); +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts index eb63eac950a38..ef902340614f9 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts @@ -1,15 +1,13 @@ -import * as fs from 'fs'; import * as path from 'path'; import { Construct } from 'constructs'; import { Bundling } from './bundling'; -import { LockFile } from './package-manager'; import { BundlingOptions } from './types'; -import { callsites, findUpMultiple, isSdkV2Runtime } from './util'; +import { isSdkV2Runtime } from './util'; import { Architecture } from '../../aws-lambda'; import * as lambda from '../../aws-lambda'; -import { Annotations, FeatureFlags, ValidationError } from '../../core'; +import { Annotations, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; -import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../cx-api'; +import { findEntry, findLockFile, getRuntime } from './function-helpers'; /** * Properties for a NodejsFunction @@ -170,130 +168,4 @@ export class NodejsFunction extends lambda.Function { } } } -} - -/** - * Check if the feature flag is enabled and default to NODEJS_LATEST if so. - * Otherwise default to NODEJS_16_X. - */ -function getRuntime(scope: Construct, props: NodejsFunctionProps): lambda.Runtime { - const defaultRuntime = FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_USE_LATEST_RUNTIME) - ? lambda.Runtime.NODEJS_LATEST - : lambda.Runtime.NODEJS_16_X; - return props.runtime ?? defaultRuntime; -} - -/** - * Checks given lock file or searches for a lock file - */ -function findLockFile(scope: Construct, depsLockFilePath?: string): string { - if (depsLockFilePath) { - if (!fs.existsSync(depsLockFilePath)) { - throw new ValidationError(`Lock file at ${depsLockFilePath} doesn't exist`, scope); - } - - if (!fs.statSync(depsLockFilePath).isFile()) { - throw new ValidationError('`depsLockFilePath` should point to a file', scope); - } - - return path.resolve(depsLockFilePath); - } - - const lockFiles = findUpMultiple([ - LockFile.PNPM, - LockFile.YARN, - LockFile.BUN_LOCK, - LockFile.BUN, - LockFile.NPM, - ]); - - if (lockFiles.length === 0) { - throw new ValidationError('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, `bun.lock` or `package-lock.json`). Please specify it with `depsLockFilePath`.', scope); - } - if (lockFiles.length > 1) { - throw new ValidationError(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsLockFilePath\`.`, scope); - } - - return lockFiles[0]; -} - -/** - * Searches for an entry file. Preference order is the following: - * 1. Given entry file - * 2. A .ts file named as the defining file with id as suffix (defining-file.id.ts) - * 3. A .js file name as the defining file with id as suffix (defining-file.id.js) - * 4. A .mjs file name as the defining file with id as suffix (defining-file.id.mjs) - * 5. A .mts file name as the defining file with id as suffix (defining-file.id.mts) - * 6. A .cts file name as the defining file with id as suffix (defining-file.id.cts) - * 7. A .cjs file name as the defining file with id as suffix (defining-file.id.cjs) - */ -function findEntry(scope: Construct, id: string, entry?: string): string { - if (entry) { - if (!/\.(jsx?|tsx?|cjs|cts|mjs|mts)$/.test(entry)) { - throw new ValidationError('Only JavaScript or TypeScript entry files are supported.', scope); - } - if (!fs.existsSync(entry)) { - throw new ValidationError(`Cannot find entry file at ${entry}`, scope); - } - return entry; - } - - const definingFile = findDefiningFile(scope); - const extname = path.extname(definingFile); - - const tsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.ts`); - if (fs.existsSync(tsHandlerFile)) { - return tsHandlerFile; - } - - const jsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.js`); - if (fs.existsSync(jsHandlerFile)) { - return jsHandlerFile; - } - - const mjsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.mjs`); - if (fs.existsSync(mjsHandlerFile)) { - return mjsHandlerFile; - } - - const mtsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.mts`); - if (fs.existsSync(mtsHandlerFile)) { - return mtsHandlerFile; - } - - const ctsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.cts`); - if (fs.existsSync(ctsHandlerFile)) { - return ctsHandlerFile; - } - - const cjsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.cjs`); - if (fs.existsSync(cjsHandlerFile)) { - return cjsHandlerFile; - } - - throw new ValidationError(`Cannot find handler file ${tsHandlerFile}, ${jsHandlerFile}, ${mjsHandlerFile}, ${mtsHandlerFile}, ${ctsHandlerFile} or ${cjsHandlerFile}`, scope); -} - -/** - * Finds the name of the file where the `NodejsFunction` is defined - */ -function findDefiningFile(scope: Construct): string { - let definingIndex; - const sites = callsites(); - for (const [index, site] of sites.entries()) { - if (site.getFunctionName() === 'NodejsFunction') { - // The next site is the site where the NodejsFunction was created - definingIndex = index + 1; - break; - } - } - - if (!definingIndex || !sites[definingIndex]) { - throw new ValidationError('Cannot find defining file.', scope); - } - - // Fixes issue #21630. - // ESM modules return a 'file://' prefix to the filenames, this should be removed for - // compatibility with the NodeJS filesystem functions. - return sites[definingIndex].getFileName().replace(/^file:\/\//, ''); -} +} \ No newline at end of file From 0232fbfd8504a7cfc07d08e067b3b3f9b653196a Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 16:59:34 +0100 Subject: [PATCH 05/12] linter fix --- .../aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts | 4 ++-- packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts index 7009b239437ef..3e77697ba12b0 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts @@ -1,12 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; import { Construct } from 'constructs'; +import { NodejsFunctionProps } from './function'; import { LockFile } from './package-manager'; import { callsites, findUpMultiple } from './util'; import * as lambda from '../../aws-lambda'; import { FeatureFlags, ValidationError } from '../../core'; import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../cx-api'; -import { NodejsFunctionProps } from './function'; /** * Check if the feature flag is enabled and default to NODEJS_LATEST if so. @@ -132,4 +132,4 @@ export function findDefiningFile(scope: Construct): string { // ESM modules return a 'file://' prefix to the filenames, this should be removed for // compatibility with the NodeJS filesystem functions. return sites[definingIndex].getFileName().replace(/^file:\/\//, ''); -} \ No newline at end of file +} diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts index ef902340614f9..e9fd83fa36b1c 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts @@ -1,13 +1,13 @@ import * as path from 'path'; import { Construct } from 'constructs'; import { Bundling } from './bundling'; +import { findEntry, findLockFile, getRuntime } from './function-helpers'; import { BundlingOptions } from './types'; import { isSdkV2Runtime } from './util'; import { Architecture } from '../../aws-lambda'; import * as lambda from '../../aws-lambda'; import { Annotations, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; -import { findEntry, findLockFile, getRuntime } from './function-helpers'; /** * Properties for a NodejsFunction @@ -168,4 +168,4 @@ export class NodejsFunction extends lambda.Function { } } } -} \ No newline at end of file +} From ea67d05a5e0a3caeb3ffa5e65e47cf7679e532ad Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 17:51:15 +0100 Subject: [PATCH 06/12] fix issues --- .../test/integ.distribution-nodejs-edge-function.ts | 2 +- .../test/aws-cloudfront/test/nodejs-edge-handler/index.ts | 4 ++-- .../aws-cloudfront/lib/experimental/nodejs-edge-function.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts index 2a28c838558a2..6805e5b32f7f3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts @@ -10,7 +10,7 @@ import { STANDARD_NODEJS_RUNTIME } from '../../config'; const app = new cdk.App(); const region = 'eu-west-1'; -const stack = new cdk.Stack(app, 'integ-nodejs-edge-function', { +const stack = new cdk.Stack(app, 'integ-nodejs-edge-function', { env: { region: region }, }); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts index 05f50f12cdbbc..cdb9d849a6006 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts @@ -2,13 +2,13 @@ import { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda'; export const handler = async (event: CloudFrontRequestEvent): Promise => { const request = event.Records[0].cf.request; - + // Simple TypeScript logic to verify bundling works const customHeader: string = 'x-custom-header'; request.headers[customHeader] = [{ key: customHeader, value: 'test-value', }]; - + return request; }; diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index 516e97cb578d9..7c0e0845afcba 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -5,7 +5,7 @@ import * as lambda from '../../../aws-lambda'; import { Architecture } from '../../../aws-lambda'; import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; -import { getRuntime, findEntry, findLockFile } from '../../../aws-lambda-nodejs/lib/bundling-preparation'; +import { getRuntime, findEntry, findLockFile } from '../../../aws-lambda-nodejs/lib/function-helpers'; import { ValidationError } from '../../../core'; /** @@ -50,7 +50,7 @@ export class NodejsEdgeFunction extends EdgeFunction { }); } else { // Entry and defaults - const entry = path.resolve(findEntry(scope, id, props.entry, 'NodejsEdgeFunction')); + const entry = path.resolve(findEntry(scope, id, props.entry)); const architecture = props.architecture ?? Architecture.X86_64; const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); From 651e44c29caa84ba1521624d134bc275b2bcd3e5 Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 18:16:29 +0100 Subject: [PATCH 07/12] update handler function --- .../test/aws-cloudfront/test/nodejs-edge-handler/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts index cdb9d849a6006..03ad08f7100d6 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts @@ -1,6 +1,4 @@ -import { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda'; - -export const handler = async (event: CloudFrontRequestEvent): Promise => { +export const handler = async (event: any): Promise => { const request = event.Records[0].cf.request; // Simple TypeScript logic to verify bundling works From 38487b3b5cfddb7e15bcd9afb636160f6be2e3fd Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Fri, 14 Nov 2025 21:16:20 +0100 Subject: [PATCH 08/12] extract validation to a helper function --- .../lib/experimental/nodejs-edge-function.ts | 27 +++------- .../aws-lambda-nodejs/lib/function-helpers.ts | 52 +++++++++++++++++++ .../aws-lambda-nodejs/lib/function.ts | 21 +++----- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index 7c0e0845afcba..4cc53b64db2c0 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -1,11 +1,8 @@ -import * as path from 'path'; import { Construct } from 'constructs'; import { EdgeFunction } from './edge-function'; -import * as lambda from '../../../aws-lambda'; -import { Architecture } from '../../../aws-lambda'; import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; -import { getRuntime, findEntry, findLockFile } from '../../../aws-lambda-nodejs/lib/function-helpers'; +import { getRuntime, resolveBundlingConfig, validateNodejsRuntime } from '../../../aws-lambda-nodejs/lib/function-helpers'; import { ValidationError } from '../../../core'; /** @@ -27,9 +24,7 @@ export interface NodejsEdgeFunctionProps extends NodejsFunctionProps { */ export class NodejsEdgeFunction extends EdgeFunction { constructor(scope: Construct, id: string, props: NodejsEdgeFunctionProps = {}) { - if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) { - throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); - } + validateNodejsRuntime(scope, props.runtime); const runtime = getRuntime(scope, props); @@ -49,27 +44,21 @@ export class NodejsEdgeFunction extends EdgeFunction { handler: props.handler, }); } else { - // Entry and defaults - const entry = path.resolve(findEntry(scope, id, props.entry)); - const architecture = props.architecture ?? Architecture.X86_64; - const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); - const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); - const handler = props.handler ?? 'handler'; + const config = resolveBundlingConfig(scope, id, props); super(scope, id, { ...props, runtime, code: Bundling.bundle(scope, { ...props.bundling ?? {}, - entry, + entry: config.entry, runtime, - architecture, - depsLockFilePath, - projectRoot, + architecture: config.architecture, + depsLockFilePath: config.depsLockFilePath, + projectRoot: config.projectRoot, }), - handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`, + handler: config.handler.indexOf('.') !== -1 ? `${config.handler}` : `index.${config.handler}`, }); } } } - diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts index 3e77697ba12b0..0d455b3ffe9c2 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts @@ -133,3 +133,55 @@ export function findDefiningFile(scope: Construct): string { // compatibility with the NodeJS filesystem functions. return sites[definingIndex].getFileName().replace(/^file:\/\//, ''); } + +/** + * Validates that the runtime is Node.js family + */ +export function validateNodejsRuntime(scope: Construct, runtime?: lambda.Runtime): void { + if (runtime && runtime.family !== lambda.RuntimeFamily.NODEJS) { + throw new ValidationError('Only `NODEJS` runtimes are supported.', scope); + } +} + +/** + * Validates that handler is provided when code is specified + */ +export function validateHandlerWithCode(scope: Construct, code?: lambda.Code, handler?: string): void { + if (code !== undefined && handler === undefined) { + throw new ValidationError( + 'Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.\n' + + 'The handler should be the name of the exported function to be invoked and the file containing that function.\n' + + 'For example, handler should be specified in the form `myFile.myFunction`', scope, + ); + } +} + +/** + * Configuration values resolved for bundling + */ +export interface BundlingConfig { + readonly entry: string; + readonly handler: string; + readonly architecture: lambda.Architecture; + readonly depsLockFilePath: string; + readonly projectRoot: string; +} + +/** + * Resolves bundling configuration from props + */ +export function resolveBundlingConfig(scope: Construct, id: string, props: NodejsFunctionProps): BundlingConfig { + const entry = path.resolve(findEntry(scope, id, props.entry)); + const handler = props.handler ?? 'handler'; + const architecture = props.architecture ?? lambda.Architecture.X86_64; + const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); + const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); + + return { + entry, + handler, + architecture, + depsLockFilePath, + projectRoot, + }; +} diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts index e9fd83fa36b1c..261d939011a94 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts @@ -1,10 +1,8 @@ -import * as path from 'path'; import { Construct } from 'constructs'; import { Bundling } from './bundling'; -import { findEntry, findLockFile, getRuntime } from './function-helpers'; +import { getRuntime, resolveBundlingConfig } from './function-helpers'; import { BundlingOptions } from './types'; import { isSdkV2Runtime } from './util'; -import { Architecture } from '../../aws-lambda'; import * as lambda from '../../aws-lambda'; import { Annotations, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -132,25 +130,20 @@ export class NodejsFunction extends lambda.Function { handler: props.handler, }); } else { - // Entry and defaults - const entry = path.resolve(findEntry(scope, id, props.entry)); - const architecture = props.architecture ?? Architecture.X86_64; - const depsLockFilePath = findLockFile(scope, props.depsLockFilePath); - const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); - const handler = props.handler ?? 'handler'; + const config = resolveBundlingConfig(scope, id, props); super(scope, id, { ...props, runtime, code: Bundling.bundle(scope, { ...props.bundling ?? {}, - entry, + entry: config.entry, runtime, - architecture, - depsLockFilePath, - projectRoot, + architecture: config.architecture, + depsLockFilePath: config.depsLockFilePath, + projectRoot: config.projectRoot, }), - handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`, + handler: config.handler.indexOf('.') !== -1 ? `${config.handler}` : `index.${config.handler}`, }); } // Enhanced CDK Analytics Telemetry From 7290bedec1eea5254120b57ab7406331884ea2ce Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Mon, 17 Nov 2025 13:53:13 +0100 Subject: [PATCH 09/12] update integ tests and docs --- .../integ.distribution-lambda-cross-region.ts | 4 ++++ .../integ.distribution-nodejs-edge-function.ts | 15 +++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts index 2ad8ad7104543..c7ef90d3c1532 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts @@ -47,4 +47,8 @@ new cloudfront.Distribution(stack, 'Dist', { new integ.IntegTest(app, 'cdk-integ-distribution-lambda-cross-region', { testCases: [stack], diffAssets: true, + // Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. + // They must be disassociated from distributions and replicas cleared (takes hours). + // See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html + stackUpdateWorkflow: false, }); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts index 6805e5b32f7f3..8423bebc13bb7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts @@ -3,7 +3,6 @@ import * as path from 'path'; import * as cdk from 'aws-cdk-lib'; import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; import * as integ from '@aws-cdk/integ-tests-alpha'; -import { ExpectedResult } from '@aws-cdk/integ-tests-alpha'; import { TestOrigin } from './test-origin'; import { STANDARD_NODEJS_RUNTIME } from '../../config'; @@ -35,14 +34,18 @@ const distribution = new cloudfront.Distribution(stack, 'Dist', { const integTest = new integ.IntegTest(app, 'cdk-integ-nodejs-edge-function', { testCases: [stack], diffAssets: true, + // Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. + // They must be disassociated from distributions and replicas cleared (takes hours). + // See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html + stackUpdateWorkflow: false, }); -// Assertion 1: Verify Lambda exists in us-east-1 +// Verify the Lambda function was deployed to us-east-1 integTest.assertions.awsApiCall('Lambda', 'getFunction', { FunctionName: edgeFunction.functionName, -}).expect(ExpectedResult.objectLike({ +}).expect(integ.ExpectedResult.objectLike({ Configuration: { - FunctionName: edgeFunction.functionName, + Runtime: STANDARD_NODEJS_RUNTIME.name, }, })).provider.addToRolePolicy({ Effect: 'Allow', @@ -50,10 +53,10 @@ integTest.assertions.awsApiCall('Lambda', 'getFunction', { Resource: '*', }); -// Assertion 2: Verify CloudFront distribution has the edge lambda attached +// Verify CloudFront distribution has the edge lambda attached integTest.assertions.awsApiCall('CloudFront', 'getDistributionConfig', { Id: distribution.distributionId, -}).expect(ExpectedResult.objectLike({ +}).expect(integ.ExpectedResult.objectLike({ DistributionConfig: { DefaultCacheBehavior: { LambdaFunctionAssociations: { From 425bee904132d0a23dba3e963c21a2f590127e6b Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Tue, 18 Nov 2025 10:13:33 +0100 Subject: [PATCH 10/12] update snapshots --- .../index.js | 38 + .../cdk.out | 1 + ...efaultTestDeployAssert9510BB28.assets.json | 20 + ...aultTestDeployAssert9510BB28.template.json | 36 + ...057b3956c3eab412773b201b3f0a8d.assets.json | 36 + ...7b3956c3eab412773b201b3f0a8d.template.json | 134 +++ .../integ-nodejs-edge-function.assets.json | 36 + .../integ-nodejs-edge-function.template.json | 164 ++++ .../integ.json | 15 + .../manifest.json | 796 ++++++++++++++++++ .../tree.json | 1 + ...integ.distribution-nodejs-edge-function.ts | 41 +- 12 files changed, 1282 insertions(+), 36 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/asset.c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff/index.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/tree.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/asset.c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff/index.js b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/asset.c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff/index.js new file mode 100644 index 0000000000000..3f70ff54527ed --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/asset.c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff/index.js @@ -0,0 +1,38 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/nodejs-edge-handler/index.ts +var index_exports = {}; +__export(index_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(index_exports); +var handler = async (event) => { + const request = event.Records[0].cf.request; + const customHeader = "x-custom-header"; + request.headers[customHeader] = [{ + key: customHeader, + value: "test-value" + }]; + return request; +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets.json new file mode 100644 index 0000000000000..8f11b1fc8ed1e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28 Template", + "source": { + "path": "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets.json new file mode 100644 index 0000000000000..5aaabaa8ed830 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets.json @@ -0,0 +1,36 @@ +{ + "version": "48.0.0", + "files": { + "c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff": { + "displayName": "NodejsEdge/Code", + "source": { + "path": "asset.c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-1-976814cd": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + }, + "3f2d9119a7e9d2431da97ecfe5e2ca281ead6a3d5f92c6a698e157b295375c8c": { + "displayName": "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d Template", + "source": { + "path": "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1-276ec5cc": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "3f2d9119a7e9d2431da97ecfe5e2ca281ead6a3d5f92c6a698e157b295375c8c.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json new file mode 100644 index 0000000000000..932a857ba9d37 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json @@ -0,0 +1,134 @@ +{ + "Resources": { + "NodejsEdgeServiceRoleC35E85DD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "edgelambda.amazonaws.com", + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "NodejsEdgeBE7A686C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1" + }, + "S3Key": "c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff.zip" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "NodejsEdgeServiceRoleC35E85DD", + "Arn" + ] + }, + "Runtime": "nodejs18.x" + }, + "DependsOn": [ + "NodejsEdgeServiceRoleC35E85DD" + ] + }, + "NodejsEdgeLogGroupDECAE9D4": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Ref": "NodejsEdgeBE7A686C" + } + ] + ] + }, + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "NodejsEdgeCurrentVersion1CD8234384abcc4c4bea8fefcd2f6f5388252d25": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "NodejsEdgeBE7A686C" + } + }, + "Metadata": { + "aws:cdk:do-not-refactor": true + } + }, + "NodejsEdgeParameterF7F11F91": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": "/cdk/EdgeFunctionArn/eu-west-1/integ-nodejs-edge-function/NodejsEdge", + "Type": "String", + "Value": { + "Ref": "NodejsEdgeCurrentVersion1CD8234384abcc4c4bea8fefcd2f6f5388252d25" + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.assets.json new file mode 100644 index 0000000000000..a40c3710e8895 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.assets.json @@ -0,0 +1,36 @@ +{ + "version": "48.0.0", + "files": { + "d909d61e7c5632a3a518620d3c4a3b041a92d95adc65b8ff76f3ae885e3e4ad1": { + "displayName": "integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider Code", + "source": { + "path": "asset.d909d61e7c5632a3a518620d3c4a3b041a92d95adc65b8ff76f3ae885e3e4ad1", + "packaging": "zip" + }, + "destinations": { + "current_account-eu-west-1-28fdf4b1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1", + "objectKey": "d909d61e7c5632a3a518620d3c4a3b041a92d95adc65b8ff76f3ae885e3e4ad1.zip", + "region": "eu-west-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-eu-west-1" + } + } + }, + "1213e6db6dbaa59ee60336f0863cdae6bea1f14dfe7bbef0c38869e1b7e3051e": { + "displayName": "integ-nodejs-edge-function Template", + "source": { + "path": "integ-nodejs-edge-function.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-eu-west-1-632f5f8c": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1", + "objectKey": "1213e6db6dbaa59ee60336f0863cdae6bea1f14dfe7bbef0c38869e1b7e3051e.json", + "region": "eu-west-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-eu-west-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.template.json new file mode 100644 index 0000000000000..49c50161652e8 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ-nodejs-edge-function.template.json @@ -0,0 +1,164 @@ +{ + "Resources": { + "NodejsEdgeArnReaderB0F5976E": { + "Type": "Custom::CrossRegionStringParameterReader", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionStringParameterReaderCustomResourceProviderHandler65B5F33A", + "Arn" + ] + }, + "Region": "us-east-1", + "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-nodejs-edge-function/NodejsEdge", + "RefreshToken": "NodejsEdgeCurrentVersion1CD8234384abcc4c4bea8fefcd2f6f5388252d25" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionStringParameterReaderCustomResourceProviderRole71CD6825": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:us-east-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/cdk/EdgeFunctionArn/*" + ] + ] + }, + "Action": [ + "ssm:GetParameter" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionStringParameterReaderCustomResourceProviderHandler65B5F33A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1" + }, + "S3Key": "d909d61e7c5632a3a518620d3c4a3b041a92d95adc65b8ff76f3ae885e3e4ad1.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionStringParameterReaderCustomResourceProviderRole71CD6825", + "Arn" + ] + }, + "Runtime": "nodejs22.x" + }, + "DependsOn": [ + "CustomCrossRegionStringParameterReaderCustomResourceProviderRole71CD6825" + ] + }, + "DistB3B78991": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad", + "Compress": true, + "LambdaFunctionAssociations": [ + { + "EventType": "viewer-request", + "LambdaFunctionARN": { + "Fn::GetAtt": [ + "NodejsEdgeArnReaderB0F5976E", + "FunctionArn" + ] + } + } + ], + "TargetOriginId": "integnodejsedgefunctionDistOrigin16D6129A3", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integnodejsedgefunctionDistOrigin16D6129A3" + } + ] + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ.json new file mode 100644 index 0000000000000..4450061297f56 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/integ.json @@ -0,0 +1,15 @@ +{ + "version": "48.0.0", + "testCases": { + "cdk-integ-nodejs-edge-function/DefaultTest": { + "stacks": [ + "integ-nodejs-edge-function" + ], + "diffAssets": true, + "stackUpdateWorkflow": false, + "assertionStack": "cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert", + "assertionStackName": "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28" + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/manifest.json new file mode 100644 index 0000000000000..2a7fd5d0a2cfe --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/manifest.json @@ -0,0 +1,796 @@ +{ + "version": "48.0.0", + "artifacts": { + "integ-nodejs-edge-function.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-nodejs-edge-function.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-nodejs-edge-function": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/eu-west-1", + "properties": { + "templateFile": "integ-nodejs-edge-function.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-eu-west-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-eu-west-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-eu-west-1/1213e6db6dbaa59ee60336f0863cdae6bea1f14dfe7bbef0c38869e1b7e3051e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-nodejs-edge-function.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-eu-west-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d", + "integ-nodejs-edge-function.assets" + ], + "metadata": { + "/integ-nodejs-edge-function/NodejsEdge": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/integ-nodejs-edge-function/NodejsEdge/ArnReader": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + } + ], + "/integ-nodejs-edge-function/NodejsEdge/ArnReader/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeArnReaderB0F5976E" + } + ], + "/integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider": [ + { + "type": "aws:cdk:is-custom-resource-handler-customResourceProvider", + "data": true + } + ], + "/integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionStringParameterReaderCustomResourceProviderRole71CD6825" + } + ], + "/integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionStringParameterReaderCustomResourceProviderHandler65B5F33A" + } + ], + "/integ-nodejs-edge-function/Dist": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "defaultBehavior": { + "origin": "*", + "cachePolicy": "*", + "edgeLambdas": [ + { + "functionVersion": "*", + "eventType": "viewer-request" + } + ] + } + } + } + ], + "/integ-nodejs-edge-function/Dist/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DistB3B78991" + } + ], + "/integ-nodejs-edge-function/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-nodejs-edge-function/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-nodejs-edge-function" + }, + "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-1", + "properties": { + "templateFile": "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1/3f2d9119a7e9d2431da97ecfe5e2ca281ead6a3d5f92c6a698e157b295375c8c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d.assets" + ], + "metadata": { + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "runtime": "*", + "code": "*", + "handler": "*" + } + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole": [ + { + "type": "aws:cdk:warning", + "data": "Failed to add construct metadata for node [ServiceRole]. Reason: ValidationError: The result of fromAwsManagedPolicyName can not be used in this API [ack: @aws-cdk/core:addConstructMetadataFailed]" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeServiceRoleC35E85DD" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeBE7A686C" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/LogGroup": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "logGroupName": "*" + } + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeLogGroupDECAE9D4" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/CurrentVersion": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "lambda": "*" + } + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/CurrentVersion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeCurrentVersion1CD8234384abcc4c4bea8fefcd2f6f5388252d25" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Parameter": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "parameterName": "*", + "stringValue": "*" + } + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Parameter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NodejsEdgeParameterF7F11F91" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d" + }, + "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "cdkintegnodejsedgefunctionDefaultTestDeployAssert9510BB28.assets" + ], + "metadata": { + "/cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "userValue": true, + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "userValue": true, + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + }, + "@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": { + "recommendedValue": true, + "explanation": "When enabled, Network Load Balancer will be created with a security group by default." + }, + "@aws-cdk/aws-stepfunctions-tasks:httpInvokeDynamicJsonPathEndpoint": { + "recommendedValue": true, + "explanation": "When enabled, allows using a dynamic apiEndpoint with JSONPath format in HttpInvoke tasks.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": { + "recommendedValue": true, + "explanation": "When enabled, ECS patterns will generate unique target group IDs to prevent conflicts during load balancer replacement" + } + } + } + } + }, + "minimumCliVersion": "2.1031.2" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/tree.json new file mode 100644 index 0000000000000..1e2c6ee143862 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"integ-nodejs-edge-function":{"id":"integ-nodejs-edge-function","path":"integ-nodejs-edge-function","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"NodejsEdge":{"id":"NodejsEdge","path":"integ-nodejs-edge-function/NodejsEdge","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudfront.experimental.NodejsEdgeFunction","version":"0.0.0","metadata":["*"]},"children":{"ArnReader":{"id":"ArnReader","path":"integ-nodejs-edge-function/NodejsEdge/ArnReader","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0","metadata":["*","*","*"]},"children":{"Default":{"id":"Default","path":"integ-nodejs-edge-function/NodejsEdge/ArnReader/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}},"Custom::CrossRegionStringParameterReaderCustomResourceProvider":{"id":"Custom::CrossRegionStringParameterReaderCustomResourceProvider","path":"integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider","constructInfo":{"fqn":"aws-cdk-lib.CustomResourceProviderBase","version":"0.0.0"},"children":{"Staging":{"id":"Staging","path":"integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider/Staging","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"Role":{"id":"Role","path":"integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider/Role","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}},"Handler":{"id":"Handler","path":"integ-nodejs-edge-function/Custom::CrossRegionStringParameterReaderCustomResourceProvider/Handler","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}},"Dist":{"id":"Dist","path":"integ-nodejs-edge-function/Dist","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudfront.Distribution","version":"0.0.0","metadata":[{"defaultBehavior":{"origin":"*","cachePolicy":"*","edgeLambdas":[{"functionVersion":"*","eventType":"viewer-request"}]}}]},"children":{"Origin1":{"id":"Origin1","path":"integ-nodejs-edge-function/Dist/Origin1","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"Resource":{"id":"Resource","path":"integ-nodejs-edge-function/Dist/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudfront.CfnDistribution","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudFront::Distribution","aws:cdk:cloudformation:props":{"distributionConfig":{"enabled":true,"origins":[{"domainName":"www.example.com","id":"integnodejsedgefunctionDistOrigin16D6129A3","customOriginConfig":{"originProtocolPolicy":"https-only"}}],"defaultCacheBehavior":{"pathPattern":"*","targetOriginId":"integnodejsedgefunctionDistOrigin16D6129A3","cachePolicyId":"4135ea2d-6df8-44a3-9df3-4b5a84be39ad","compress":true,"viewerProtocolPolicy":"allow-all","lambdaFunctionAssociations":[{"lambdaFunctionArn":{"Fn::GetAtt":["NodejsEdgeArnReaderB0F5976E","FunctionArn"]},"eventType":"viewer-request"}]},"httpVersion":"http2","ipv6Enabled":true}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"integ-nodejs-edge-function/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"integ-nodejs-edge-function/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d":{"id":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"NodejsEdge":{"id":"NodejsEdge","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0","metadata":[{"runtime":"*","code":"*","handler":"*"}]},"children":{"ServiceRole":{"id":"ServiceRole","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":["edgelambda.amazonaws.com","lambda.amazonaws.com"]}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]}]}}}}},"Code":{"id":"Code","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0","metadata":[]}}}},"Resource":{"id":"Resource","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1"},"s3Key":"c9f353fdef14350a9b0b6dfb80be0e0dea1614eafbd6b3250bb4ce993a3488ff.zip"},"handler":"index.handler","role":{"Fn::GetAtt":["NodejsEdgeServiceRoleC35E85DD","Arn"]},"runtime":"nodejs18.x"}}},"LogGroup":{"id":"LogGroup","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/LogGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.LogGroup","version":"0.0.0","metadata":[{"logGroupName":"*"}]},"children":{"Resource":{"id":"Resource","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/LogGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.CfnLogGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Logs::LogGroup","aws:cdk:cloudformation:props":{"logGroupName":{"Fn::Join":["",["/aws/lambda/",{"Ref":"NodejsEdgeBE7A686C"}]]},"retentionInDays":731}}}}},"CurrentVersion":{"id":"CurrentVersion","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/CurrentVersion","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Version","version":"0.0.0","metadata":[{"lambda":"*"}]},"children":{"Resource":{"id":"Resource","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/CurrentVersion/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnVersion","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Version","aws:cdk:cloudformation:props":{"functionName":{"Ref":"NodejsEdgeBE7A686C"}}}}}},"Parameter":{"id":"Parameter","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Parameter","constructInfo":{"fqn":"aws-cdk-lib.aws_ssm.StringParameter","version":"0.0.0","metadata":[{"parameterName":"*","stringValue":"*"}]},"children":{"Resource":{"id":"Resource","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/NodejsEdge/Parameter/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ssm.CfnParameter","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::SSM::Parameter","aws:cdk:cloudformation:props":{"name":"/cdk/EdgeFunctionArn/eu-west-1/integ-nodejs-edge-function/NodejsEdge","type":"String","value":{"Ref":"NodejsEdgeCurrentVersion1CD8234384abcc4c4bea8fefcd2f6f5388252d25"}}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"edge-lambda-stack-c89048be7abf057b3956c3eab412773b201b3f0a8d/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"cdk-integ-nodejs-edge-function":{"id":"cdk-integ-nodejs-edge-function","path":"cdk-integ-nodejs-edge-function","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"cdk-integ-nodejs-edge-function/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"cdk-integ-nodejs-edge-function/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"cdk-integ-nodejs-edge-function/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts index 8423bebc13bb7..e4e04114d5595 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-nodejs-edge-function.ts @@ -19,8 +19,7 @@ const edgeFunction = new cloudfront.experimental.NodejsEdgeFunction(stack, 'Node runtime: STANDARD_NODEJS_RUNTIME, }); -// Attach to CloudFront to verify integration -const distribution = new cloudfront.Distribution(stack, 'Dist', { +new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin: new TestOrigin('www.example.com'), cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, @@ -31,41 +30,11 @@ const distribution = new cloudfront.Distribution(stack, 'Dist', { }, }); -const integTest = new integ.IntegTest(app, 'cdk-integ-nodejs-edge-function', { +// Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. +// They must be disassociated from distributions and replicas cleared (takes hours). +// See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html +new integ.IntegTest(app, 'cdk-integ-nodejs-edge-function', { testCases: [stack], diffAssets: true, - // Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. - // They must be disassociated from distributions and replicas cleared (takes hours). - // See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html stackUpdateWorkflow: false, }); - -// Verify the Lambda function was deployed to us-east-1 -integTest.assertions.awsApiCall('Lambda', 'getFunction', { - FunctionName: edgeFunction.functionName, -}).expect(integ.ExpectedResult.objectLike({ - Configuration: { - Runtime: STANDARD_NODEJS_RUNTIME.name, - }, -})).provider.addToRolePolicy({ - Effect: 'Allow', - Action: ['lambda:GetFunction'], - Resource: '*', -}); - -// Verify CloudFront distribution has the edge lambda attached -integTest.assertions.awsApiCall('CloudFront', 'getDistributionConfig', { - Id: distribution.distributionId, -}).expect(integ.ExpectedResult.objectLike({ - DistributionConfig: { - DefaultCacheBehavior: { - LambdaFunctionAssociations: { - Quantity: 1, - }, - }, - }, -})).provider.addToRolePolicy({ - Effect: 'Allow', - Action: ['cloudfront:GetDistributionConfig'], - Resource: '*', -}); From 65d6bc9877f3fe580fb488ede35f49717702b075 Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Thu, 27 Nov 2025 12:10:54 +0100 Subject: [PATCH 11/12] remove unused function. --- .../test/integ.distribution-lambda-cross-region.ts | 6 +++--- .../aws-lambda-nodejs/lib/function-helpers.ts | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts index c7ef90d3c1532..ff1441f647179 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts @@ -44,11 +44,11 @@ new cloudfront.Distribution(stack, 'Dist', { }, }); +// Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. +// They must be disassociated from distributions and replicas cleared (takes hours). +// See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html new integ.IntegTest(app, 'cdk-integ-distribution-lambda-cross-region', { testCases: [stack], diffAssets: true, - // Lambda@Edge functions cannot be immediately deleted due to CloudFront replication. - // They must be disassociated from distributions and replicas cleared (takes hours). - // See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html stackUpdateWorkflow: false, }); diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts index 0d455b3ffe9c2..7707317ad5fe4 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/function-helpers.ts @@ -143,19 +143,6 @@ export function validateNodejsRuntime(scope: Construct, runtime?: lambda.Runtime } } -/** - * Validates that handler is provided when code is specified - */ -export function validateHandlerWithCode(scope: Construct, code?: lambda.Code, handler?: string): void { - if (code !== undefined && handler === undefined) { - throw new ValidationError( - 'Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.\n' - + 'The handler should be the name of the exported function to be invoked and the file containing that function.\n' - + 'For example, handler should be specified in the form `myFile.myFunction`', scope, - ); - } -} - /** * Configuration values resolved for bundling */ From 2fb9dfd5793db8ec2a1eb0ec88db91753751467c Mon Sep 17 00:00:00 2001 From: Abid Hasan Date: Thu, 27 Nov 2025 17:40:06 +0100 Subject: [PATCH 12/12] add cdk telemetry --- .../aws-cloudfront/lib/experimental/nodejs-edge-function.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts index 4cc53b64db2c0..6a5de4a8d5425 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/nodejs-edge-function.ts @@ -4,6 +4,7 @@ import { NodejsFunctionProps } from '../../../aws-lambda-nodejs'; import { Bundling } from '../../../aws-lambda-nodejs/lib/bundling'; import { getRuntime, resolveBundlingConfig, validateNodejsRuntime } from '../../../aws-lambda-nodejs/lib/function-helpers'; import { ValidationError } from '../../../core'; +import { addConstructMetadata } from '../../../core/lib/metadata-resource'; /** * Properties for a NodejsEdgeFunction @@ -60,5 +61,8 @@ export class NodejsEdgeFunction extends EdgeFunction { handler: config.handler.indexOf('.') !== -1 ? `${config.handler}` : `index.${config.handler}`, }); } + + // Enhanced CDK Analytics Telemetry + addConstructMetadata(this, props); } }