diff --git a/API.md b/API.md
index 582e9369..23ba197b 100644
--- a/API.md
+++ b/API.md
@@ -288,6 +288,155 @@ For example, `s3://bucket/key`
---
+### TypeScriptCodeCollection
+
+Manages multiple Lambda function entry points that share the same build configuration.
+
+Creates a {@link TypeScriptCode} asset for each entry point, allowing related
+functions to be organized with common build options. This is primarily a
+convenience construct for managing multiple Lambda functions that share
+the same esbuild configuration.
+
+#### Initializers
+
+```typescript
+import { TypeScriptCodeCollection } from '@mrgrain/cdk-esbuild'
+
+new TypeScriptCodeCollection(scope: Construct, id: string, props: TypeScriptCodeCollectionProps)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| scope | constructs.Construct | *No description.* |
+| id | string | *No description.* |
+| props | TypeScriptCodeCollectionProps | *No description.* |
+
+---
+
+##### `scope`Required
+
+- *Type:* constructs.Construct
+
+---
+
+##### `id`Required
+
+- *Type:* string
+
+---
+
+##### `props`Required
+
+- *Type:* TypeScriptCodeCollectionProps
+
+---
+
+#### Methods
+
+| **Name** | **Description** |
+| --- | --- |
+| toString | Returns a string representation of this construct. |
+| getCode | Get the bundled TypeScript code for a specific function. |
+
+---
+
+##### `toString`
+
+```typescript
+public toString(): string
+```
+
+Returns a string representation of this construct.
+
+##### `getCode`
+
+```typescript
+public getCode(functionName: string): TypeScriptCode
+```
+
+Get the bundled TypeScript code for a specific function.
+
+###### `functionName`Required
+
+- *Type:* string
+
+The logical function name (key from entryPoints).
+
+---
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| isConstruct | Checks if `x` is a construct. |
+
+---
+
+##### ~~`isConstruct`~~
+
+```typescript
+import { TypeScriptCodeCollection } from '@mrgrain/cdk-esbuild'
+
+TypeScriptCodeCollection.isConstruct(x: any)
+```
+
+Checks if `x` is a construct.
+
+###### `x`Required
+
+- *Type:* any
+
+Any object.
+
+---
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| node | constructs.Node | The tree node. |
+| allCodes | {[ key: string ]: TypeScriptCode} | All bundled code instances. |
+| functionNames | string[] | The names of all functions in this collection. |
+
+---
+
+##### `node`Required
+
+```typescript
+public readonly node: Node;
+```
+
+- *Type:* constructs.Node
+
+The tree node.
+
+---
+
+##### `allCodes`Required
+
+```typescript
+public readonly allCodes: {[ key: string ]: TypeScriptCode};
+```
+
+- *Type:* {[ key: string ]: TypeScriptCode}
+
+All bundled code instances.
+
+---
+
+##### `functionNames`Required
+
+```typescript
+public readonly functionNames: string[];
+```
+
+- *Type:* string[]
+
+The names of all functions in this collection.
+
+---
+
+
## Structs
### BuildOptions
@@ -3888,6 +4037,130 @@ Examples:
---
+### TypeScriptCodeCollectionProps
+
+Properties for TypeScriptCodeCollection.
+
+#### Initializer
+
+```typescript
+import { TypeScriptCodeCollectionProps } from '@mrgrain/cdk-esbuild'
+
+const typeScriptCodeCollectionProps: TypeScriptCodeCollectionProps = { ... }
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| buildOptions | BuildOptions | Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. |
+| buildProvider | IBuildProvider | The esbuild Build API implementation to be used. |
+| copyDir | string \| string[] \| {[ key: string ]: string \| string[]} | Copy additional files to the code [asset staging directory](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.AssetStaging.html#absolutestagedpath), before the build runs. Files copied like this will be overwritten by esbuild if they share the same name as any of the outputs. |
+| entryPoints | {[ key: string ]: string} | Entry points to bundle as a collection. |
+| assetHash | string | A hash of this asset, which is available at construction time. |
+
+---
+
+##### `buildOptions`Optional
+
+```typescript
+public readonly buildOptions: BuildOptions;
+```
+
+- *Type:* BuildOptions
+
+Build options passed on to esbuild. Please refer to the esbuild Build API docs for details.
+
+* `buildOptions.outdir: string`
+The actual path for the output directory is defined by CDK. However setting this option allows to write files into a subdirectory. \
+For example `{ outdir: 'js' }` will create an asset with a single directory called `js`, which contains all built files. This approach can be useful for static website deployments, where JavaScript code should be placed into a subdirectory. \
+*Cannot be used together with `outfile`*.
+* `buildOptions.outfile: string`
+Relative path to a file inside the CDK asset output directory.
+For example `{ outfile: 'js/index.js' }` will create an asset with a single directory called `js`, which contains a single file `index.js`. This can be useful to rename the entry point. \
+*Cannot be used with multiple entryPoints or together with `outdir`.*
+* `buildOptions.absWorkingDir: string`
+Absolute path to the [esbuild working directory](https://esbuild.github.io/api/#working-directory) and defaults to the [current working directory](https://en.wikipedia.org/wiki/Working_directory). \
+If paths cannot be found, a good starting point is to look at the concatenation of `absWorkingDir + entryPoint`. It must always be a valid absolute path pointing to the entry point. When needed, the probably easiest way to set absWorkingDir is to use a combination of `resolve` and `__dirname` (see "Library authors" section in the documentation).
+
+> [https://esbuild.github.io/api/#build-api](https://esbuild.github.io/api/#build-api)
+
+---
+
+##### `buildProvider`Optional
+
+```typescript
+public readonly buildProvider: IBuildProvider;
+```
+
+- *Type:* IBuildProvider
+- *Default:* new EsbuildProvider()
+
+The esbuild Build API implementation to be used.
+
+Configure the default `EsbuildProvider` for more options or
+provide a custom `IBuildProvider` as an escape hatch.
+
+---
+
+##### `copyDir`Optional
+
+```typescript
+public readonly copyDir: string | string[] | {[ key: string ]: string | string[]};
+```
+
+- *Type:* string | string[] | {[ key: string ]: string | string[]}
+
+Copy additional files to the code [asset staging directory](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.AssetStaging.html#absolutestagedpath), before the build runs. Files copied like this will be overwritten by esbuild if they share the same name as any of the outputs.
+
+* When provided with a `string` or `array`, all files are copied to the root of asset staging directory.
+* When given a `map`, the key indicates the destination relative to the asset staging directory and the value is a list of all sources to be copied.
+
+Therefore the following values for `copyDir` are all equivalent:
+```
+{ copyDir: "path/to/source" }
+{ copyDir: ["path/to/source"] }
+{ copyDir: { ".": "path/to/source" } }
+{ copyDir: { ".": ["path/to/source"] } }
+```
+The destination cannot be outside of the asset staging directory.
+If you are receiving the error "Cannot copy files to outside of the asset staging directory."
+you are likely using `..` or an absolute path as key on the `copyDir` map.
+Instead use only relative paths and avoid `..`.
+
+---
+
+##### `entryPoints`Required
+
+```typescript
+public readonly entryPoints: {[ key: string ]: string};
+```
+
+- *Type:* {[ key: string ]: string}
+
+Entry points to bundle as a collection.
+
+Key: logical function name (used to retrieve the code later).
+Value: path to the entry point file.
+
+---
+
+##### `assetHash`Optional
+
+```typescript
+public readonly assetHash: string;
+```
+
+- *Type:* string
+
+A hash of this asset, which is available at construction time.
+
+As this is a plain string, it can be used in construct IDs in order to enforce creation of a new resource when the content hash has changed.
+
+Defaults to a hash of all files in the resulting bundle.
+
+---
+
### TypeScriptCodeProps
#### Initializer
diff --git a/examples/typescript/multi-lambda/app.ts b/examples/typescript/multi-lambda/app.ts
new file mode 100644
index 00000000..9726da7e
--- /dev/null
+++ b/examples/typescript/multi-lambda/app.ts
@@ -0,0 +1,41 @@
+import * as cdk from 'aws-cdk-lib';
+import * as lambda from 'aws-cdk-lib/aws-lambda';
+import { TypeScriptCodeCollection } from '@mrgrain/cdk-esbuild';
+
+export class MultiLambdaStack extends cdk.Stack {
+ constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
+ super(scope, id, props);
+
+ const codeCollection = new TypeScriptCodeCollection(this, 'MultiLambda', {
+ entryPoints: {
+ 'api': './src/api.ts',
+ 'auth': './src/auth.ts',
+ 'notifications': './src/notifications.ts',
+ },
+ buildOptions: {
+ minify: true,
+ },
+ });
+
+ new lambda.Function(this, 'ApiFunction', {
+ runtime: lambda.Runtime.NODEJS_18_X,
+ handler: 'api.handler',
+ code: codeCollection.getCode('api'),
+ });
+
+ new lambda.Function(this, 'AuthFunction', {
+ runtime: lambda.Runtime.NODEJS_18_X,
+ handler: 'auth.handler',
+ code: codeCollection.getCode('auth'),
+ });
+
+ new lambda.Function(this, 'NotificationsFunction', {
+ runtime: lambda.Runtime.NODEJS_18_X,
+ handler: 'notifications.handler',
+ code: codeCollection.getCode('notifications'),
+ });
+ }
+}
+
+const app = new cdk.App();
+new MultiLambdaStack(app, 'MultiLambdaStack');
\ No newline at end of file
diff --git a/examples/typescript/multi-lambda/package.json b/examples/typescript/multi-lambda/package.json
new file mode 100644
index 00000000..bd3423bf
--- /dev/null
+++ b/examples/typescript/multi-lambda/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "multi-lambda-example",
+ "version": "1.0.0",
+ "description": "Example of building multiple Lambda functions with cdk-esbuild",
+ "main": "app.ts",
+ "scripts": {
+ "deploy": "cdk deploy",
+ "synth": "cdk synth"
+ },
+ "dependencies": {
+ "aws-cdk-lib": "^2.0.0",
+ "constructs": "^10.0.0",
+ "@mrgrain/cdk-esbuild": "^5.0.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.0.0"
+ }
+}
\ No newline at end of file
diff --git a/examples/typescript/multi-lambda/src/api.ts b/examples/typescript/multi-lambda/src/api.ts
new file mode 100644
index 00000000..4e20b110
--- /dev/null
+++ b/examples/typescript/multi-lambda/src/api.ts
@@ -0,0 +1,6 @@
+export async function handler(event: any) {
+ return {
+ statusCode: 200,
+ body: JSON.stringify({ message: 'API Handler' }),
+ };
+}
\ No newline at end of file
diff --git a/examples/typescript/multi-lambda/src/auth.ts b/examples/typescript/multi-lambda/src/auth.ts
new file mode 100644
index 00000000..8b6a7211
--- /dev/null
+++ b/examples/typescript/multi-lambda/src/auth.ts
@@ -0,0 +1,6 @@
+export async function handler(event: any) {
+ return {
+ statusCode: 200,
+ body: JSON.stringify({ message: 'Auth Handler' }),
+ };
+}
\ No newline at end of file
diff --git a/examples/typescript/multi-lambda/src/notifications.ts b/examples/typescript/multi-lambda/src/notifications.ts
new file mode 100644
index 00000000..4d84aec3
--- /dev/null
+++ b/examples/typescript/multi-lambda/src/notifications.ts
@@ -0,0 +1,6 @@
+export async function handler(event: any) {
+ return {
+ statusCode: 200,
+ body: JSON.stringify({ message: 'Notifications Handler' }),
+ };
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 2d80477e..912f88ae 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -49,3 +49,8 @@ export {
TypeScriptSource,
TypeScriptSourceProps,
} from './source';
+
+export {
+ TypeScriptCodeCollection,
+ TypeScriptCodeCollectionProps,
+} from './typescript-code-collection';
diff --git a/src/typescript-code-collection.ts b/src/typescript-code-collection.ts
new file mode 100644
index 00000000..1c591dfe
--- /dev/null
+++ b/src/typescript-code-collection.ts
@@ -0,0 +1,95 @@
+import { Construct } from 'constructs';
+import { BundlerProps } from './bundler';
+import { TypeScriptCode, TypeScriptCodeProps } from './code';
+
+/**
+ * Properties for TypeScriptCodeCollection
+ *
+ * @stability stable
+ */
+export interface TypeScriptCodeCollectionProps extends BundlerProps {
+ /**
+ * Entry points to bundle as a collection.
+ *
+ * Key: logical function name (used to retrieve the code later).
+ * Value: path to the entry point file.
+ *
+ * @stability stable
+ */
+ readonly entryPoints: Record;
+
+ /**
+ * A hash of this asset, which is available at construction time.
+ *
+ * As this is a plain string, it can be used in construct IDs in order to enforce creation of a new resource when the content hash has changed.
+ *
+ * Defaults to a hash of all files in the resulting bundle.
+ *
+ * @stability stable
+ */
+ readonly assetHash?: string;
+}
+
+/**
+ * Manages multiple Lambda function entry points that share the same build configuration.
+ *
+ * Creates a {@link TypeScriptCode} asset for each entry point, allowing related
+ * functions to be organized with common build options. This is primarily a
+ * convenience construct for managing multiple Lambda functions that share
+ * the same esbuild configuration.
+ *
+ * @stability stable
+ */
+export class TypeScriptCodeCollection extends Construct {
+ private readonly codes: { [name: string]: TypeScriptCode } = {};
+
+ constructor(scope: Construct, id: string, props: TypeScriptCodeCollectionProps) {
+ super(scope, id);
+
+ const { entryPoints, assetHash, ...bundlerProps } = props;
+
+ // Create individual TypeScriptCode instances for each entry point
+ Object.entries(entryPoints).forEach(([name, entryPoint]) => {
+ const codeProps: TypeScriptCodeProps = {
+ ...bundlerProps,
+ assetHash,
+ };
+
+ this.codes[name] = new TypeScriptCode(entryPoint, codeProps);
+ });
+ }
+
+ /**
+ * Get the bundled TypeScript code for a specific function.
+ *
+ * @param functionName The logical function name (key from entryPoints)
+ * @returns TypeScriptCode instance that can be used with Lambda.Function
+ *
+ * @stability stable
+ */
+ public getCode(functionName: string): TypeScriptCode {
+ const code = this.codes[functionName];
+ if (!code) {
+ throw new Error(`No entry point found for function: ${functionName}`);
+ }
+ return code;
+ }
+
+ /**
+ * The names of all functions in this collection.
+ *
+ * @stability stable
+ */
+ public get functionNames(): string[] {
+ return Object.keys(this.codes);
+ }
+
+ /**
+ * All bundled code instances.
+ *
+ * @stability stable
+ */
+ public get allCodes(): { [name: string]: TypeScriptCode } {
+ return { ...this.codes };
+ }
+}
diff --git a/test/integ/integ-multi-lambda.py b/test/integ/integ-multi-lambda.py
new file mode 100644
index 00000000..6938be65
--- /dev/null
+++ b/test/integ/integ-multi-lambda.py
@@ -0,0 +1,74 @@
+import aws_cdk as cdk
+import aws_cdk.aws_lambda as lambda_
+import aws_cdk.integ_tests_alpha as integ
+from constructs import Construct
+from mrgrain.cdk_esbuild import (
+ BuildOptions,
+ TypeScriptCodeCollection,
+ EsbuildProvider,
+ EsbuildSource,
+)
+
+
+class MultiLambdaStack(cdk.Stack):
+ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
+ super().__init__(scope, construct_id, **kwargs)
+
+ # Set a new default
+ EsbuildProvider.override_default_provider(
+ EsbuildProvider(esbuild_module_path=EsbuildSource.install())
+ )
+
+ # Use TypeScriptCodeCollection to build multiple functions
+ code_collection = TypeScriptCodeCollection(
+ self,
+ "MultiLambda",
+ entry_points={
+ "ts_handler": "test/fixtures/handlers/ts-handler.ts",
+ "js_handler": "test/fixtures/handlers/js-handler.js",
+ },
+ )
+
+ lambda_.Function(
+ self,
+ "TsHandlerFunction",
+ runtime=lambda_.Runtime.NODEJS_18_X,
+ handler="index.handler",
+ code=code_collection.get_code("ts_handler"),
+ )
+
+ lambda_.Function(
+ self,
+ "JsHandlerFunction",
+ runtime=lambda_.Runtime.NODEJS_18_X,
+ handler="index.handler",
+ code=code_collection.get_code("js_handler"),
+ )
+
+ # Use TypeScriptCodeCollection with build options
+ code_collection_with_options = TypeScriptCodeCollection(
+ self,
+ "MultiLambdaWithOptions",
+ entry_points={
+ "colors": "test/fixtures/handlers/colors.ts",
+ },
+ build_options=BuildOptions(
+ external=["aws-sdk"],
+ ),
+ )
+
+ lambda_.Function(
+ self,
+ "ColorsFunction",
+ runtime=lambda_.Runtime.NODEJS_18_X,
+ handler="index.handler",
+ code=code_collection_with_options.get_code("colors"),
+ )
+
+
+app = cdk.App()
+stack = MultiLambdaStack(app, "MultiLambda")
+
+integ.IntegTest(app, "MultiLambdaFunctions", test_cases=[stack])
+
+app.synth()
diff --git a/test/typescript-code-collection.test.ts b/test/typescript-code-collection.test.ts
new file mode 100644
index 00000000..208820ea
--- /dev/null
+++ b/test/typescript-code-collection.test.ts
@@ -0,0 +1,302 @@
+import { resolve } from 'path';
+import { Stack } from 'aws-cdk-lib';
+import { Function, Runtime as LambdaRuntime } from 'aws-cdk-lib/aws-lambda';
+import { EsbuildProvider } from '../src/provider';
+import { TypeScriptCodeCollection } from '../src/typescript-code-collection';
+
+const buildProvider = new EsbuildProvider();
+const buildSyncSpy = jest.spyOn(buildProvider, 'buildSync');
+
+describe('TypeScriptCodeCollection', () => {
+ describe('basic instantiation', () => {
+ it('should create a collection with multiple entry points', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ handler2: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler1'),
+ });
+
+ new Function(stack, 'Function2', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler2'),
+ });
+ }).not.toThrow();
+ });
+
+ it('should create a collection with a single entry point', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler: 'fixtures/handlers/ts-handler.ts',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler'),
+ });
+ }).not.toThrow();
+ });
+ });
+
+ describe('getCode()', () => {
+ it('should return TypeScriptCode for a valid function name', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ handler2: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ const code = collection.getCode('handler1');
+ expect(code).toBeDefined();
+ });
+
+ it('should throw an error for non-existent function name', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ expect(() => collection.getCode('nonExistent')).toThrow(
+ 'No entry point found for function: nonExistent',
+ );
+ });
+ });
+
+ describe('functionNames', () => {
+ it('should return all function names', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ api: 'fixtures/handlers/ts-handler.ts',
+ auth: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ const names = collection.functionNames;
+ expect(names).toHaveLength(2);
+ expect(names).toContain('api');
+ expect(names).toContain('auth');
+ });
+
+ it('should return empty array when no entry points', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {},
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ expect(collection.functionNames).toHaveLength(0);
+ });
+ });
+
+ describe('allCodes', () => {
+ it('should return all code instances', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ api: 'fixtures/handlers/ts-handler.ts',
+ auth: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ const allCodes = collection.allCodes;
+ expect(Object.keys(allCodes)).toHaveLength(2);
+ expect(allCodes.api).toBeDefined();
+ expect(allCodes.auth).toBeDefined();
+ });
+
+ it('should return a copy (not a reference to internal state)', () => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ api: 'fixtures/handlers/ts-handler.ts',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ const allCodes = collection.allCodes;
+ // Modifying the returned object should not affect internal state
+ delete allCodes.api;
+ expect(collection.getCode('api')).toBeDefined();
+ });
+ });
+
+ describe('shared build configuration', () => {
+ it('should pass build options to all code instances', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ handler2: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: {
+ absWorkingDir: resolve(__dirname),
+ minify: true,
+ sourcemap: true,
+ },
+ buildProvider,
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler1'),
+ });
+
+ new Function(stack, 'Function2', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler2'),
+ });
+ }).not.toThrow();
+
+ expect(buildSyncSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ entryPoints: ['fixtures/handlers/ts-handler.ts'],
+ minify: true,
+ sourcemap: true,
+ }),
+ );
+
+ expect(buildSyncSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ entryPoints: ['fixtures/handlers/js-handler.js'],
+ minify: true,
+ sourcemap: true,
+ }),
+ );
+ });
+ });
+
+ describe('custom build provider', () => {
+ it('should use the provided build provider for all entry points', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ buildProvider,
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler1'),
+ });
+ }).not.toThrow();
+
+ expect(buildSyncSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ entryPoints: ['fixtures/handlers/ts-handler.ts'],
+ }),
+ );
+ });
+ });
+
+ describe('using a custom asset hash', () => {
+ it('should not throw', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ handler1: 'fixtures/handlers/ts-handler.ts',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ assetHash: 'customhash1234567890',
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('handler1'),
+ });
+ }).not.toThrow();
+ });
+ });
+
+ describe('Lambda functions with collection', () => {
+ it('should create separate Lambda functions from the same collection', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ api: 'fixtures/handlers/ts-handler.ts',
+ auth: 'fixtures/handlers/js-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ new Function(stack, 'ApiFunction', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'api.handler',
+ code: collection.getCode('api'),
+ });
+
+ new Function(stack, 'AuthFunction', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'auth.handler',
+ code: collection.getCode('auth'),
+ });
+ }).not.toThrow();
+ });
+ });
+
+ describe('error handling', () => {
+ it('should report build failures for invalid entry points', () => {
+ expect(() => {
+ const stack = new Stack();
+
+ const collection = new TypeScriptCodeCollection(stack, 'Collection', {
+ entryPoints: {
+ invalid: 'fixtures/handlers/invalid-handler.js',
+ },
+ buildOptions: { absWorkingDir: resolve(__dirname) },
+ });
+
+ new Function(stack, 'Function1', {
+ runtime: LambdaRuntime.NODEJS_18_X,
+ handler: 'index.handler',
+ code: collection.getCode('invalid'),
+ });
+ }).toThrow('Esbuild failed to bundle fixtures/handlers/invalid-handler.js');
+ });
+ });
+});