From 1eb235a140146c98f1e56dccb715956a62be87ac Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 11:59:56 +0200 Subject: [PATCH 01/13] test(maintenance): add esm output to e2e tests --- .../src/resources/TestNodejsFunction.ts | 18 +++++++++++++++++- packages/testing/src/types.ts | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/resources/TestNodejsFunction.ts b/packages/testing/src/resources/TestNodejsFunction.ts index 5be3597caa..9b54a3cd2b 100644 --- a/packages/testing/src/resources/TestNodejsFunction.ts +++ b/packages/testing/src/resources/TestNodejsFunction.ts @@ -1,6 +1,10 @@ import { CfnOutput, Duration } from 'aws-cdk-lib'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; -import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { + NodejsFunction, + BundlingOptions, + OutputFormat, +} from 'aws-cdk-lib/aws-lambda-nodejs'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import { randomUUID } from 'node:crypto'; import { TEST_RUNTIMES, TEST_ARCHITECTURES } from '../constants.js'; @@ -23,6 +27,17 @@ class TestNodejsFunction extends NodejsFunction { props: TestNodejsFunctionProps, extraProps: ExtraTestProps ) { + const isESM = extraProps.outputFormat === 'ESM' ? true : false; + const bundling: BundlingOptions = { + minify: true, + mainFields: isESM ? ['module', 'main'] : ['main', 'module'], + sourceMap: true, + format: isESM ? OutputFormat.ESM : OutputFormat.CJS, + banner: isESM + ? `import { createRequire } from 'module';const require = createRequire(import.meta.url);` + : '', + }; + super(stack.stack, `fn-${randomUUID().substring(0, 5)}`, { timeout: Duration.seconds(30), memorySize: 256, @@ -35,6 +50,7 @@ class TestNodejsFunction extends NodejsFunction { runtime: TEST_RUNTIMES[getRuntimeKey()], architecture: TEST_ARCHITECTURES[getArchitectureKey()], logRetention: RetentionDays.ONE_DAY, + bundling, }); new CfnOutput(this, extraProps.nameSuffix, { diff --git a/packages/testing/src/types.ts b/packages/testing/src/types.ts index ce11c0fc41..1732e2d3b6 100644 --- a/packages/testing/src/types.ts +++ b/packages/testing/src/types.ts @@ -13,6 +13,12 @@ interface ExtraTestProps { * Note that the maximum length of the name is 64 characters, so the suffix might be truncated. */ nameSuffix: string; + /** + * The output format of the bundled code. + * + * @default 'CJS' + */ + outputFormat?: 'CJS' | 'ESM'; } type TestDynamodbTableProps = Omit< @@ -27,8 +33,13 @@ type TestDynamodbTableProps = Omit< type TestNodejsFunctionProps = Omit< NodejsFunctionProps, - 'logRetention' | 'runtime' | 'functionName' ->; + 'logRetention' | 'runtime' | 'functionName' | 'bundling' +> & { + bundling?: Omit< + NodejsFunctionProps['bundling'], + 'minify' | 'mainFields' | 'sourceMap' | 'format' | 'banner' + >; +}; type InvokeTestFunctionOptions = { functionName: string; From 61433add08ff5d04c8ce543ec5320763bfecf664 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 12:00:35 +0200 Subject: [PATCH 02/13] test: switch 50% tests to esm --- packages/logger/tests/e2e/basicFeatures.middy.test.ts | 1 + packages/logger/tests/e2e/sampleRate.decorator.test.ts | 1 + packages/metrics/tests/e2e/basicFeatures.decorators.test.ts | 1 + packages/parameters/tests/e2e/appConfigProvider.class.test.ts | 1 + packages/parameters/tests/e2e/secretsProvider.class.test.ts | 1 + 5 files changed, 5 insertions(+) diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.ts index 5f7a67eb35..efdb9668ed 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.ts @@ -48,6 +48,7 @@ describe(`Logger E2E tests, basic functionalities middy usage`, () => { { logGroupOutputKey: STACK_OUTPUT_LOG_GROUP, nameSuffix: 'BasicFeatures', + outputFormat: 'ESM', } ); diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index d6db70c8c0..ff4b5fae30 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -52,6 +52,7 @@ describe(`Logger E2E tests, sample rate and injectLambdaContext()`, () => { { logGroupOutputKey: STACK_OUTPUT_LOG_GROUP, nameSuffix: 'BasicFeatures', + outputFormat: 'ESM', } ); diff --git a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts index 5bf1a38809..7ad7258916 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -50,6 +50,7 @@ describe(`Metrics E2E tests, basic features decorator usage`, () => { }, { nameSuffix: 'BasicFeatures', + outputFormat: 'ESM', } ); diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index cfbf7cd58d..c44d29c405 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -120,6 +120,7 @@ describe(`Parameters E2E tests, AppConfig provider`, () => { }, { nameSuffix: 'appConfigProvider', + outputFormat: 'ESM', } ); diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.ts index 9a8bffa9b7..32bcf1a5a0 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.ts @@ -60,6 +60,7 @@ describe(`Parameters E2E tests, Secrets Manager provider`, () => { }, { nameSuffix: 'secretsProvider', + outputFormat: 'ESM', } ); From 1a00bbf978ba16a34739d3742b672a237d6f32fd Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 12:03:26 +0200 Subject: [PATCH 03/13] test: switch 50% tests to esm --- packages/idempotency/tests/e2e/idempotentDecorator.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/idempotency/tests/e2e/idempotentDecorator.test.ts b/packages/idempotency/tests/e2e/idempotentDecorator.test.ts index 83a863afaa..40146a8f8c 100644 --- a/packages/idempotency/tests/e2e/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/e2e/idempotentDecorator.test.ts @@ -63,6 +63,7 @@ describe('Idempotency e2e test decorator, default settings', () => { }, { nameSuffix: 'defaultParallel', + outputFormat: 'ESM', } ); @@ -79,6 +80,7 @@ describe('Idempotency e2e test decorator, default settings', () => { }, { nameSuffix: 'timeout', + outputFormat: 'ESM', } ); @@ -95,6 +97,7 @@ describe('Idempotency e2e test decorator, default settings', () => { }, { nameSuffix: 'expired', + outputFormat: 'ESM', } ); @@ -110,6 +113,7 @@ describe('Idempotency e2e test decorator, default settings', () => { }, { nameSuffix: 'dataIndex', + outputFormat: 'ESM', } ); @@ -131,6 +135,7 @@ describe('Idempotency e2e test decorator, default settings', () => { }, { nameSuffix: 'customConfig', + outputFormat: 'ESM', } ); From f6a6dbf5787adcfeaaf84fb741789e5f30b55876 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 12:07:10 +0200 Subject: [PATCH 04/13] test: switch 50% tests to esm --- packages/tracer/tests/e2e/allFeatures.decorator.test.ts | 4 ++++ packages/tracer/tests/e2e/allFeatures.middy.test.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts index 0dfeb15b79..7d00633067 100644 --- a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts +++ b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts @@ -75,6 +75,7 @@ describe(`Tracer E2E tests, all features with decorator instantiation`, () => { }, { nameSuffix: 'AllFlagsOn', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnAllFlagsEnabled); @@ -95,6 +96,7 @@ describe(`Tracer E2E tests, all features with decorator instantiation`, () => { }, { nameSuffix: 'NoCaptureErrOrResp', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnNoCaptureErrorOrResponse); @@ -114,6 +116,7 @@ describe(`Tracer E2E tests, all features with decorator instantiation`, () => { }, { nameSuffix: 'TracerDisabled', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnTracerDisabled); @@ -133,6 +136,7 @@ describe(`Tracer E2E tests, all features with decorator instantiation`, () => { }, { nameSuffix: 'CaptureResponseOff', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnCaptureResponseOff); diff --git a/packages/tracer/tests/e2e/allFeatures.middy.test.ts b/packages/tracer/tests/e2e/allFeatures.middy.test.ts index 23d2fdfc23..b9488bf6ed 100644 --- a/packages/tracer/tests/e2e/allFeatures.middy.test.ts +++ b/packages/tracer/tests/e2e/allFeatures.middy.test.ts @@ -75,6 +75,7 @@ describe(`Tracer E2E tests, all features with middy instantiation`, () => { }, { nameSuffix: 'AllFlagsOn', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnAllFlagsEnabled); @@ -95,6 +96,7 @@ describe(`Tracer E2E tests, all features with middy instantiation`, () => { }, { nameSuffix: 'NoCaptureErrOrResp', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnNoCaptureErrorOrResponse); @@ -114,6 +116,7 @@ describe(`Tracer E2E tests, all features with middy instantiation`, () => { }, { nameSuffix: 'TracerDisabled', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnTracerDisabled); @@ -133,6 +136,7 @@ describe(`Tracer E2E tests, all features with middy instantiation`, () => { }, { nameSuffix: 'CaptureResponseOff', + outputFormat: 'ESM', } ); testTable.grantWriteData(fnCaptureResponseOff); From a35ec845cd656336bcc9a95aa52f1eca030ef9e4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 13:02:26 +0200 Subject: [PATCH 05/13] test: refactor layers test to deploy cjs & esm --- layers/src/layer-publisher-stack.ts | 2 - layers/tests/e2e/layerPublisher.test.ts | 132 +++++++++++++----------- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/layers/src/layer-publisher-stack.ts b/layers/src/layer-publisher-stack.ts index b2bb6f90e6..299189f268 100644 --- a/layers/src/layer-publisher-stack.ts +++ b/layers/src/layer-publisher-stack.ts @@ -101,9 +101,7 @@ export class LayerPublisherStack extends Stack { 'node_modules/@aws-lambda-powertools/*/lib/**/*.d.ts', 'node_modules/@aws-lambda-powertools/*/lib/**/*.d.ts.map', 'node_modules/@aws-sdk/*/dist-types', - 'node_modules/@aws-sdk/*/dist-es', 'node_modules/@smithy/*/dist-types', - 'node_modules/@smithy/*/dist-es', 'node_modules/@smithy/**/README.md ', 'node_modules/@aws-sdk/**/README.md ', ]; diff --git a/layers/tests/e2e/layerPublisher.test.ts b/layers/tests/e2e/layerPublisher.test.ts index 9f5535c9c0..7deaffdc96 100644 --- a/layers/tests/e2e/layerPublisher.test.ts +++ b/layers/tests/e2e/layerPublisher.test.ts @@ -17,21 +17,29 @@ import { RESOURCE_NAME_PREFIX, SETUP_TIMEOUT, TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT, } from './constants'; import { join } from 'node:path'; import packageJson from '../../package.json'; jest.spyOn(console, 'log').mockImplementation(); +// eslint-disable-next-line func-style -- type assertions can't be arrow functions +function assertLogs( + logs: TestInvocationLogs | undefined +): asserts logs is TestInvocationLogs { + if (!logs) { + throw new Error('Function logs are not available'); + } +} + /** * This test has two stacks: * 1. LayerPublisherStack - publishes a layer version using the LayerPublisher construct and containing the Powertools utilities from the repo - * 2. TestStack - uses the layer published in the first stack and contains a lambda function that uses the Powertools utilities from the layer + * 2. TestStack - uses the layer published in the first stack and contains two lambda functions that use the Powertools utilities from the layer * * The lambda function is invoked once and the logs are collected. The goal of the test is to verify that the layer creation and usage works as expected. */ -describe(`Layers E2E tests, publisher stack`, () => { +describe(`Layers E2E tests`, () => { const testStack = new TestStack({ stackNameProps: { stackNamePrefix: RESOURCE_NAME_PREFIX, @@ -39,7 +47,9 @@ describe(`Layers E2E tests, publisher stack`, () => { }, }); - let invocationLogs: TestInvocationLogs; + const cases = ['CJS', 'ESM'] as const; + const invocationLogsMap: Map<(typeof cases)[number], TestInvocationLogs> = + new Map(); const ssmParameterLayerName = generateTestUniqueName({ testPrefix: `${RESOURCE_NAME_PREFIX}`, @@ -75,76 +85,79 @@ describe(`Layers E2E tests, publisher stack`, () => { }); beforeAll(async () => { + // Deploy the stack that publishes the layer await testLayerStack.deploy(); + // Import the layer version from the stack outputs into the test stack const layerVersion = LayerVersion.fromLayerVersionArn( testStack.stack, 'LayerVersionArnReference', testLayerStack.findAndGetStackOutputValue('LatestLayerArn') ); - new TestNodejsFunction( - testStack, - { - entry: lambdaFunctionCodeFilePath, - environment: { - LAYERS_PATH: '/opt/nodejs/node_modules', - POWERTOOLS_PACKAGE_VERSION: powerToolsPackageVersion, - POWERTOOLS_SERVICE_NAME: 'LayerPublisherStack', - }, - bundling: { - externalModules: [ - '@aws-lambda-powertools/commons', - '@aws-lambda-powertools/logger', - '@aws-lambda-powertools/metrics', - '@aws-lambda-powertools/tracer', - '@aws-lambda-powertools/parameter', - '@aws-lambda-powertools/idempotency', - '@aws-lambda-powertools/batch', - ], + + // Add a lambda function for each output format to the test stack + cases.forEach((outputFormat) => { + new TestNodejsFunction( + testStack, + { + entry: lambdaFunctionCodeFilePath, + environment: { + LAYERS_PATH: '/opt/nodejs/node_modules', + POWERTOOLS_PACKAGE_VERSION: powerToolsPackageVersion, + POWERTOOLS_SERVICE_NAME: 'LayerPublisherStack', + }, + bundling: { + externalModules: ['@aws-lambda-powertools/*', 'aws-xray-sdk-node'], + }, + layers: [layerVersion], }, - layers: [layerVersion], - }, - { - nameSuffix: 'testFn', - } - ); + { + nameSuffix: `test${outputFormat}Fn`, + ...(outputFormat === 'ESM' && { outputFormat: 'ESM' }), + } + ); + }); + // Deploy the test stack await testStack.deploy(); - const functionName = testStack.findAndGetStackOutputValue('testFn'); - - invocationLogs = await invokeFunctionOnce({ - functionName, - }); + // Invoke the lambda function once for each output format and collect the logs + for await (const outputFormat of cases) { + invocationLogsMap.set( + outputFormat, + await invokeFunctionOnce({ + functionName: testStack.findAndGetStackOutputValue( + `test${outputFormat}Fn` + ), + }) + ); + } }, SETUP_TIMEOUT); - describe('package version and path check', () => { - it( - 'should have no errors in the logs, which indicates the pacakges version matches the expected one', - () => { + describe.each(cases)( + 'utilities tests for %s output format', + (outputFormat) => { + let invocationLogs: TestInvocationLogs; + beforeAll(() => { + const maybeInvocationLogs = invocationLogsMap.get(outputFormat); + assertLogs(maybeInvocationLogs); + invocationLogs = maybeInvocationLogs; + }); + + it('should have no errors in the logs, which indicates the pacakges version matches the expected one', () => { const logs = invocationLogs.getFunctionLogs('ERROR'); expect(logs.length).toBe(0); - }, - TEST_CASE_TIMEOUT - ); - }); + }); - describe('utilities usage', () => { - it( - 'should have one warning related to missing Metrics namespace', - () => { + it('should have one warning related to missing Metrics namespace', () => { const logs = invocationLogs.getFunctionLogs('WARN'); expect(logs.length).toBe(1); expect(logs[0]).toContain('Namespace should be defined, default used'); - }, - TEST_CASE_TIMEOUT - ); + }); - it( - 'should have one info log related to coldstart metric', - () => { + it('should have one info log related to coldstart metric', () => { const logs = invocationLogs.getFunctionLogs(); const emfLogEntry = logs.find((log) => log.match( @@ -153,13 +166,9 @@ describe(`Layers E2E tests, publisher stack`, () => { ); expect(emfLogEntry).toBeDefined(); - }, - TEST_CASE_TIMEOUT - ); + }); - it( - 'should have one debug log with tracer subsegment info', - () => { + it('should have one debug log with tracer subsegment info', () => { const logs = invocationLogs.getFunctionLogs('DEBUG'); expect(logs.length).toBe(1); @@ -182,10 +191,9 @@ describe(`Layers E2E tests, publisher stack`, () => { trace_id: traceIdFromLog, }) ); - }, - TEST_CASE_TIMEOUT - ); - }); + }); + } + ); afterAll(async () => { if (!process.env.DISABLE_TEARDOWN) { From a5ee07aa4f12679b53676ee00141ddbe10219276 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 15:11:09 +0200 Subject: [PATCH 06/13] chore: marked @aws-sdk as external --- layers/tests/e2e/layerPublisher.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/layers/tests/e2e/layerPublisher.test.ts b/layers/tests/e2e/layerPublisher.test.ts index 7deaffdc96..a248a3de2c 100644 --- a/layers/tests/e2e/layerPublisher.test.ts +++ b/layers/tests/e2e/layerPublisher.test.ts @@ -107,7 +107,11 @@ describe(`Layers E2E tests`, () => { POWERTOOLS_SERVICE_NAME: 'LayerPublisherStack', }, bundling: { - externalModules: ['@aws-lambda-powertools/*', 'aws-xray-sdk-node'], + externalModules: [ + '@aws-lambda-powertools/*', + '@aws-sdk/*', + 'aws-xray-sdk-node', + ], }, layers: [layerVersion], }, From 6c0464d525273d685221acbc9d9ae433f25de5cf Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Apr 2024 15:13:40 +0200 Subject: [PATCH 07/13] chore: remove redundant boolean literals --- packages/testing/src/resources/TestNodejsFunction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/resources/TestNodejsFunction.ts b/packages/testing/src/resources/TestNodejsFunction.ts index 9b54a3cd2b..cd910a454f 100644 --- a/packages/testing/src/resources/TestNodejsFunction.ts +++ b/packages/testing/src/resources/TestNodejsFunction.ts @@ -27,7 +27,7 @@ class TestNodejsFunction extends NodejsFunction { props: TestNodejsFunctionProps, extraProps: ExtraTestProps ) { - const isESM = extraProps.outputFormat === 'ESM' ? true : false; + const isESM = extraProps.outputFormat === 'ESM'; const bundling: BundlingOptions = { minify: true, mainFields: isESM ? ['module', 'main'] : ['main', 'module'], From f86dccb9cba92c3a2542a6c6712871d82f92322d Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 08:26:33 +0200 Subject: [PATCH 08/13] chore: add max-parallel to avoid timeouts --- .github/workflows/run-e2e-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 88654c6cc9..81aa1d7b9c 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -22,6 +22,7 @@ jobs: contents: read strategy: matrix: + max-parallel: 30 package: [ layers, From 6d70302c23ba591c2313a4aa86ffb0a8fe31eaa8 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 08:35:08 +0200 Subject: [PATCH 09/13] chore: adjust max-parallel --- .github/workflows/run-e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 81aa1d7b9c..e99b01260d 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -21,8 +21,8 @@ jobs: id-token: write # needed to interact with GitHub's OIDC Token endpoint. contents: read strategy: + max-parallel: 30 matrix: - max-parallel: 30 package: [ layers, From 7e964c03b44a71a07f0923e9f40d61b3f4d33f5c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 13:34:15 +0200 Subject: [PATCH 10/13] chore: aws-sdk workaround --- .../testing/src/resources/TestNodejsFunction.ts | 9 ++++++--- packages/tracer/tests/helpers/resources.ts | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/testing/src/resources/TestNodejsFunction.ts b/packages/testing/src/resources/TestNodejsFunction.ts index cd910a454f..2049916117 100644 --- a/packages/testing/src/resources/TestNodejsFunction.ts +++ b/packages/testing/src/resources/TestNodejsFunction.ts @@ -28,7 +28,10 @@ class TestNodejsFunction extends NodejsFunction { extraProps: ExtraTestProps ) { const isESM = extraProps.outputFormat === 'ESM'; - const bundling: BundlingOptions = { + const { bundling, ...restProps } = props; + + const customBundling: BundlingOptions = { + ...bundling, minify: true, mainFields: isESM ? ['module', 'main'] : ['main', 'module'], sourceMap: true, @@ -42,7 +45,7 @@ class TestNodejsFunction extends NodejsFunction { timeout: Duration.seconds(30), memorySize: 256, tracing: Tracing.ACTIVE, - ...props, + ...restProps, functionName: concatenateResourceName({ testName: stack.testName, resourceName: extraProps.nameSuffix, @@ -50,7 +53,7 @@ class TestNodejsFunction extends NodejsFunction { runtime: TEST_RUNTIMES[getRuntimeKey()], architecture: TEST_ARCHITECTURES[getArchitectureKey()], logRetention: RetentionDays.ONE_DAY, - bundling, + bundling: customBundling, }); new CfnOutput(this, extraProps.nameSuffix, { diff --git a/packages/tracer/tests/helpers/resources.ts b/packages/tracer/tests/helpers/resources.ts index edd4faa769..7265034ddc 100644 --- a/packages/tracer/tests/helpers/resources.ts +++ b/packages/tracer/tests/helpers/resources.ts @@ -1,4 +1,8 @@ -import type { TestStack } from '@aws-lambda-powertools/testing-utils'; +import { + TEST_RUNTIMES, + getRuntimeKey, + type TestStack, +} from '@aws-lambda-powertools/testing-utils'; import type { ExtraTestProps, TestNodejsFunctionProps, @@ -27,6 +31,17 @@ class TracerTestNodejsFunction extends TestNodejsFunction { ), ...props.environment, }, + bundling: { + /** + * For Node.js 16.x, we need to set `externalModules` to an empty array + * so that the `aws-sdk` is bundled with the function. + * + * @see https://github.com/aws/aws-sdk-js-v3/issues/3230#issuecomment-1561973247 + */ + ...(TEST_RUNTIMES[getRuntimeKey()] === TEST_RUNTIMES.nodejs16x && { + externalModules: [], + }), + }, }, extraProps ); From b6526430fe78c74537e7ef10b81ff1d22dbfe877 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 14:53:28 +0200 Subject: [PATCH 11/13] chore: override nodejs16 to bundle aws-sdk when working with esm --- .../src/resources/TestNodejsFunction.ts | 30 ++++++++----------- packages/tracer/tests/helpers/resources.ts | 23 +++++++------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/testing/src/resources/TestNodejsFunction.ts b/packages/testing/src/resources/TestNodejsFunction.ts index 2049916117..5ce138e53e 100644 --- a/packages/testing/src/resources/TestNodejsFunction.ts +++ b/packages/testing/src/resources/TestNodejsFunction.ts @@ -1,10 +1,6 @@ import { CfnOutput, Duration } from 'aws-cdk-lib'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; -import { - NodejsFunction, - BundlingOptions, - OutputFormat, -} from 'aws-cdk-lib/aws-lambda-nodejs'; +import { NodejsFunction, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import { randomUUID } from 'node:crypto'; import { TEST_RUNTIMES, TEST_ARCHITECTURES } from '../constants.js'; @@ -30,21 +26,20 @@ class TestNodejsFunction extends NodejsFunction { const isESM = extraProps.outputFormat === 'ESM'; const { bundling, ...restProps } = props; - const customBundling: BundlingOptions = { - ...bundling, - minify: true, - mainFields: isESM ? ['module', 'main'] : ['main', 'module'], - sourceMap: true, - format: isESM ? OutputFormat.ESM : OutputFormat.CJS, - banner: isESM - ? `import { createRequire } from 'module';const require = createRequire(import.meta.url);` - : '', - }; - super(stack.stack, `fn-${randomUUID().substring(0, 5)}`, { timeout: Duration.seconds(30), - memorySize: 256, + memorySize: 512, tracing: Tracing.ACTIVE, + bundling: { + ...bundling, + minify: true, + mainFields: isESM ? ['module', 'main'] : ['main', 'module'], + sourceMap: false, + format: isESM ? OutputFormat.ESM : OutputFormat.CJS, + banner: isESM + ? `import { createRequire } from 'module';const require = createRequire(import.meta.url);` + : '', + }, ...restProps, functionName: concatenateResourceName({ testName: stack.testName, @@ -53,7 +48,6 @@ class TestNodejsFunction extends NodejsFunction { runtime: TEST_RUNTIMES[getRuntimeKey()], architecture: TEST_ARCHITECTURES[getArchitectureKey()], logRetention: RetentionDays.ONE_DAY, - bundling: customBundling, }); new CfnOutput(this, extraProps.nameSuffix, { diff --git a/packages/tracer/tests/helpers/resources.ts b/packages/tracer/tests/helpers/resources.ts index 7265034ddc..7acd9730db 100644 --- a/packages/tracer/tests/helpers/resources.ts +++ b/packages/tracer/tests/helpers/resources.ts @@ -1,5 +1,4 @@ import { - TEST_RUNTIMES, getRuntimeKey, type TestStack, } from '@aws-lambda-powertools/testing-utils'; @@ -16,6 +15,9 @@ class TracerTestNodejsFunction extends TestNodejsFunction { props: TestNodejsFunctionProps, extraProps: ExtraTestProps ) { + const isEsm = extraProps.outputFormat === 'ESM'; + const isNodejs16x = getRuntimeKey() === 'nodejs16x'; + super( scope, { @@ -31,16 +33,17 @@ class TracerTestNodejsFunction extends TestNodejsFunction { ), ...props.environment, }, + /** + * For Node.js 16.x, we need to set `externalModules` to an empty array + * so that the `aws-sdk` is bundled with the function. + * + * @see https://github.com/aws/aws-sdk-js-v3/issues/3230#issuecomment-1561973247 + */ bundling: { - /** - * For Node.js 16.x, we need to set `externalModules` to an empty array - * so that the `aws-sdk` is bundled with the function. - * - * @see https://github.com/aws/aws-sdk-js-v3/issues/3230#issuecomment-1561973247 - */ - ...(TEST_RUNTIMES[getRuntimeKey()] === TEST_RUNTIMES.nodejs16x && { - externalModules: [], - }), + ...(isEsm && + isNodejs16x && { + externalModules: [], + }), }, }, extraProps From 9d7faf8736f96ca8a8de59a17d6dbe4693e50d0c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 15:27:53 +0200 Subject: [PATCH 12/13] chore: exclude ESM on Node.js 16 from layer tests --- layers/tests/e2e/layerPublisher.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/layers/tests/e2e/layerPublisher.test.ts b/layers/tests/e2e/layerPublisher.test.ts index a248a3de2c..821fdd7a13 100644 --- a/layers/tests/e2e/layerPublisher.test.ts +++ b/layers/tests/e2e/layerPublisher.test.ts @@ -11,6 +11,7 @@ import { TestInvocationLogs, invokeFunctionOnce, generateTestUniqueName, + getRuntimeKey, } from '@aws-lambda-powertools/testing-utils'; import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; import { @@ -47,7 +48,13 @@ describe(`Layers E2E tests`, () => { }, }); - const cases = ['CJS', 'ESM'] as const; + /** + * Node.js 16.x does not support importing ESM modules from Lambda Layers reliably. + * + * The feature is available in Node.js 18.x and later. + * @see https://aws.amazon.com/blogs/compute/node-js-18-x-runtime-now-available-in-aws-lambda/ + */ + const cases = getRuntimeKey() === 'nodejs16x' ? ['CJS'] : ['CJS', 'ESM']; const invocationLogsMap: Map<(typeof cases)[number], TestInvocationLogs> = new Map(); From 57475bebee5ab769e4e757f2ef8ef5daffb315b6 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Apr 2024 15:32:51 +0200 Subject: [PATCH 13/13] docs: add callout about ESM & Layers in docs --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.md b/docs/index.md index 9f1e262b4f..a4ecfa7db8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -254,6 +254,8 @@ You can use Powertools for AWS Lambda (TypeScript) by installing it with your fa [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a `.zip` file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. We compile and optimize [all dependencies](#install) to achieve an optimal build. +You can use the Lambda Layer both with CommonJS and ESM (ECMAScript modules) for Node.js 18.x and newer runtimes. **If you are using the managed Node.js 16.x runtime and cannot upgrade, you should use the CommonJS version only**. + ??? note "Click to expand and copy any regional Lambda Layer ARN" | Region | Layer ARN | | ---------------- | ------------------------------------------------------------------------------------------------------------- |