diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1990420..44eb4dd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,4 +4,7 @@ _Description of changes:_ _Target (OCI, Managed Runtime, both):_ +## Checklist +- [ ] I have run `npm install` to generate the `package-lock.json` correctly and included it in the PR. + By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.gitignore b/.gitignore index 40f6187..91f8d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ dist/ # Stores VSCode versions used for testing VSCode extensions .vscode-test + +deps/artifacts/ +deps/aws-lambda-cpp*/ +deps/curl*/ diff --git a/README.md b/README.md index 97d3fad..789180c 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,13 @@ Then, * to run integration tests: `make test-integ` * to run smoke tests: `make test-smoke` +### Raising a PR +When modifying dependencies (`package.json`), make sure to: +1. Run `npm install` to generate an updated `package-lock.json` +2. Commit both `package.json` and `package-lock.json` together + +We require package-lock.json to be checked in to ensure consistent installations across development environments. + ### Troubleshooting While running integration tests, you might encounter the Docker Hub rate limit error with the following body: diff --git a/package-lock.json b/package-lock.json index a755d3b..e632b9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aws-lambda-ric", - "version": "3.2.0", + "version": "3.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aws-lambda-ric", - "version": "3.2.0", + "version": "3.2.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/test/handlers/extensionless/esm-extensionless b/test/handlers/extensionless/esm-extensionless new file mode 100644 index 0000000..537c57b --- /dev/null +++ b/test/handlers/extensionless/esm-extensionless @@ -0,0 +1,6 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +// This should fail because it's ESM syntax in a CJS context +export const handler = async (event) => { + return "This should fail"; +}; diff --git a/test/handlers/extensionless/index b/test/handlers/extensionless/index new file mode 100644 index 0000000..00b048d --- /dev/null +++ b/test/handlers/extensionless/index @@ -0,0 +1,8 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; +// This is a CommonJS module without file extension + +module.exports.handler = async (event) => { + return "Hello from extensionless CJS"; +}; diff --git a/test/handlers/pkg-less/cjsAndMjs.js b/test/handlers/pkg-less/cjsAndMjs.js new file mode 100644 index 0000000..b3f2ec9 --- /dev/null +++ b/test/handlers/pkg-less/cjsAndMjs.js @@ -0,0 +1,9 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +import { someESMFunction } from './esmModule.js'; // ESM import + +module.exports.handler = async (event) => { // CJS export + return someESMFunction(event); +}; + +export const esm = 'This is ESM syntax'; // ESM export diff --git a/test/handlers/pkg-less/cjsImportCjs.js b/test/handlers/pkg-less/cjsImportCjs.js new file mode 100644 index 0000000..6ba9088 --- /dev/null +++ b/test/handlers/pkg-less/cjsImportCjs.js @@ -0,0 +1,9 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +const { getMessage } = require('./cjsModule.cjs') + +exports.handler = async (_event) => { + return getMessage(); +} diff --git a/test/handlers/pkg-less/cjsImportESM.cjs b/test/handlers/pkg-less/cjsImportESM.cjs new file mode 100644 index 0000000..76aa80f --- /dev/null +++ b/test/handlers/pkg-less/cjsImportESM.cjs @@ -0,0 +1,10 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +// This static import is not allowed in CJS +import { getMessage } from './esmModule'; + +module.exports.handler = async () => { + return getMessage(); +}; diff --git a/test/handlers/pkg-less/cjsInMjs.mjs b/test/handlers/pkg-less/cjsInMjs.mjs new file mode 100644 index 0000000..e331a10 --- /dev/null +++ b/test/handlers/pkg-less/cjsInMjs.mjs @@ -0,0 +1,8 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +// This should fail because it's CJS syntax in a ESM context +module.exports.handler = async (_event) => { + return 'This should fail'; +}; diff --git a/test/handlers/pkg-less/cjsModule.cjs b/test/handlers/pkg-less/cjsModule.cjs new file mode 100644 index 0000000..9ffea34 --- /dev/null +++ b/test/handlers/pkg-less/cjsModule.cjs @@ -0,0 +1,7 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +module.exports.getMessage = () => { + return "Hello from CJS!"; +}; diff --git a/test/handlers/pkg-less/esmImportCjs.mjs b/test/handlers/pkg-less/esmImportCjs.mjs new file mode 100644 index 0000000..6bccc87 --- /dev/null +++ b/test/handlers/pkg-less/esmImportCjs.mjs @@ -0,0 +1,7 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +import { getMessage } from './cjsModule.cjs'; + +export const handler = async (_event) => { + return getMessage(); +}; diff --git a/test/handlers/pkg-less/esmInCjs.cjs b/test/handlers/pkg-less/esmInCjs.cjs new file mode 100644 index 0000000..57f4769 --- /dev/null +++ b/test/handlers/pkg-less/esmInCjs.cjs @@ -0,0 +1,6 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +// This should fail because it's ESM syntax in a CJS context +export const handler = async (_event) => { + return 'This should fail'; +}; diff --git a/test/handlers/pkg-less/esmModule.js b/test/handlers/pkg-less/esmModule.js new file mode 100644 index 0000000..5c6aaf2 --- /dev/null +++ b/test/handlers/pkg-less/esmModule.js @@ -0,0 +1,9 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +export const handler = async (_event) => { + return 'Hello from ESM.js'; +}; + +export const getMessage = () => { + return "Hello from ESM!"; +}; diff --git a/test/handlers/pkg-less/esmRequireCjs.mjs b/test/handlers/pkg-less/esmRequireCjs.mjs new file mode 100644 index 0000000..e7d84f1 --- /dev/null +++ b/test/handlers/pkg-less/esmRequireCjs.mjs @@ -0,0 +1,7 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +const { getMessage } = require('./cjsModule.cjs') + +export const handler = async (_event) => { + return getMessage(); +}; diff --git a/test/handlers/pkg/type-cjs/cjs b/test/handlers/pkg/type-cjs/cjs new file mode 100644 index 0000000..00b048d --- /dev/null +++ b/test/handlers/pkg/type-cjs/cjs @@ -0,0 +1,8 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; +// This is a CommonJS module without file extension + +module.exports.handler = async (event) => { + return "Hello from extensionless CJS"; +}; diff --git a/test/handlers/pkg/type-cjs/cjsModule.js b/test/handlers/pkg/type-cjs/cjsModule.js new file mode 100644 index 0000000..0ef96cf --- /dev/null +++ b/test/handlers/pkg/type-cjs/cjsModule.js @@ -0,0 +1,7 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +module.exports.handler = async (_event) => { + return 'Hello from CJS.js'; +}; diff --git a/test/handlers/pkg/type-cjs/esm b/test/handlers/pkg/type-cjs/esm new file mode 100644 index 0000000..537c57b --- /dev/null +++ b/test/handlers/pkg/type-cjs/esm @@ -0,0 +1,6 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +// This should fail because it's ESM syntax in a CJS context +export const handler = async (event) => { + return "This should fail"; +}; diff --git a/test/handlers/pkg/type-cjs/esmModule.js b/test/handlers/pkg/type-cjs/esmModule.js new file mode 100644 index 0000000..57f4769 --- /dev/null +++ b/test/handlers/pkg/type-cjs/esmModule.js @@ -0,0 +1,6 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +// This should fail because it's ESM syntax in a CJS context +export const handler = async (_event) => { + return 'This should fail'; +}; diff --git a/test/handlers/pkg/type-cjs/package.json b/test/handlers/pkg/type-cjs/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/test/handlers/pkg/type-cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/handlers/pkg/type-esm/cjs b/test/handlers/pkg/type-esm/cjs new file mode 100644 index 0000000..00b048d --- /dev/null +++ b/test/handlers/pkg/type-esm/cjs @@ -0,0 +1,8 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; +// This is a CommonJS module without file extension + +module.exports.handler = async (event) => { + return "Hello from extensionless CJS"; +}; diff --git a/test/handlers/pkg/type-esm/cjsModule.js b/test/handlers/pkg/type-esm/cjsModule.js new file mode 100644 index 0000000..e331a10 --- /dev/null +++ b/test/handlers/pkg/type-esm/cjsModule.js @@ -0,0 +1,8 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +'use strict'; + +// This should fail because it's CJS syntax in a ESM context +module.exports.handler = async (_event) => { + return 'This should fail'; +}; diff --git a/test/handlers/pkg/type-esm/esm b/test/handlers/pkg/type-esm/esm new file mode 100644 index 0000000..537c57b --- /dev/null +++ b/test/handlers/pkg/type-esm/esm @@ -0,0 +1,6 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +// This should fail because it's ESM syntax in a CJS context +export const handler = async (event) => { + return "This should fail"; +}; diff --git a/test/handlers/pkg/type-esm/esmModule.js b/test/handlers/pkg/type-esm/esmModule.js new file mode 100644 index 0000000..e0f3750 --- /dev/null +++ b/test/handlers/pkg/type-esm/esmModule.js @@ -0,0 +1,5 @@ +/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +export const handler = async (_event) => { + return 'Hello from ESM.js'; +}; diff --git a/test/handlers/pkg/type-esm/package.json b/test/handlers/pkg/type-esm/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/handlers/pkg/type-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/unit/UserFunctionTest.js b/test/unit/UserFunctionTest.js index 6ffe4c0..d1657fc 100644 --- a/test/unit/UserFunctionTest.js +++ b/test/unit/UserFunctionTest.js @@ -10,6 +10,7 @@ const { HandlerNotFound, ImportModuleError, MalformedHandlerName, + UserCodeSyntaxError, } = require('lambda-runtime/Errors.js'); const UserFunction = require('lambda-runtime/UserFunction.js'); @@ -250,6 +251,166 @@ describe('UserFunction.load method', () => { response.should.be.resolvedWith('moon'); }); + + it('should successfully load a CJS handler from extensionless file (no package.json)', async () => { + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'extensionless'), + 'index.handler', + ); + const response = await handler('test event'); + + response.should.equal('Hello from extensionless CJS'); + }); + + it('should fail to load ESM syntax from extensionless file (no package.json)', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'extensionless'), + 'esm-extensionless.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should load CJS handler from extensionless file with type:commonjs', async () => { + // package.json is ignored in the case of extensionless + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'), + 'cjs.handler', + ); + const response = await handler('test event'); + + response.should.equal('Hello from extensionless CJS'); + }); + + it('should fail to load ESM handler from extensionless file with type:commonjs', async () => { + // package.json is ignored in the case of extensionless + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'), + 'esm.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should load CJS handler from extensionless file with type:module', async () => { + // package.json is ignored in the case of extensionless + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-esm'), + 'cjs.handler', + ); + const response = await handler('test event'); + + response.should.equal('Hello from extensionless CJS'); + }); + + it('should fail to load ESM handler from extensionless file with type:module', async () => { + // package.json is ignored in the case of extensionless + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-esm'), + 'esm.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should load CJS handler from JS file with type:commonjs', async () => { + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'), + 'cjsModule.handler', + ); + const response = await handler('test event'); + + response.should.equal('Hello from CJS.js'); + }); + + it('should fail to load ESM handler from JS file with type:commonjs', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'), + 'esmModule.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should load ESM handler from JS file with type:module', async () => { + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-esm'), + 'esmModule.handler', + ); + const response = await handler('test event'); + + response.should.equal('Hello from ESM.js'); + }); + + it('should fail to load CJS handler from JS file with type:module', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg', 'type-esm'), + 'cjsModule.handler', + ).should.be.rejectedWith( + ReferenceError, + /module is not defined in ES module scope/, + ); + }); + + it('should fail to load ESM handler from JS file without type context', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'esmModule.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should fail to load CJS handler from MJS file without type context', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'cjsInMjs.handler', + ).should.be.rejectedWith( + ReferenceError, + /module is not defined in ES module scope/, + ); + }); + + it('should fail to load ESM handler from CJS file without type context', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'esmInCjs.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should fail to load mixed context handler from JS file without type context', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'cjsAndMjs.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should successfully load ESM handler importing from CJS', async () => { + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'esmImportCjs.handler', + ); + + const response = await handler(); + response.should.equal('Hello from CJS!'); + }); + + it('should fail when CJS tries to import from ESM using static import', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'cjsImportESM.handler', + ).should.be.rejectedWith(UserCodeSyntaxError); + }); + + it('should successfully load CJS handler importing from CJS', async () => { + const handler = await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'cjsImportCjs.handler', + ); + + const response = await handler(); + response.should.equal('Hello from CJS!'); + }); + + it('should fail when using require in .mjs', async () => { + await UserFunction.load( + path.join(HANDLERS_ROOT, 'pkg-less'), + 'esmRequireCjs.handler', + ).should.be.rejectedWith( + ReferenceError, + /require is not defined in ES module scope/, + ); + }); }); describe('type guards HandlerFunction', () => {