From 08c59fba7bf1ee68ca103520b3e0b7ea5359a925 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Thu, 19 May 2022 09:15:13 +1200 Subject: [PATCH] feat: copyDir supports more complex scenarios --- API.md | 162 ++++++++++++++++++++++++++++++++----------- src/bundler.ts | 89 ++++++++++++++++++++---- test/bundler.test.ts | 127 +++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+), 57 deletions(-) diff --git a/API.md b/API.md index a6aabbfa..12d2c8c3 100644 --- a/API.md +++ b/API.md @@ -39,15 +39,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -58,14 +58,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `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. -Copy additional files to the output directory, before the build runs. +* 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. -Files copied like this will be overwritten by esbuild if they share the same name as any of the outputs. +Therefore the following values for `copyDir` are all equivalent: +```ts +{ 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 `..`. --- @@ -805,15 +818,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -824,14 +837,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `string` +- *Type:* `string` | `string`[] | {[ key: string ]: `string` | `string`[]} -Copy additional files to the output directory, before the build runs. +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. -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: +```ts +{ 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 `..`. --- @@ -893,15 +919,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -912,14 +938,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `string` +- *Type:* `string` | `string`[] | {[ key: string ]: `string` | `string`[]} -Copy additional files to the output directory, before the build runs. +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. -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: +```ts +{ 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 `..`. --- @@ -975,15 +1014,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -994,14 +1033,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `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. -Copy additional files to the output directory, before the build runs. +* 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. -Files copied like this will be overwritten by esbuild if they share the same name as any of the outputs. +Therefore the following values for `copyDir` are all equivalent: +```ts +{ 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 `..`. --- @@ -1495,15 +1547,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -1514,14 +1566,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `string` +- *Type:* `string` | `string`[] | {[ key: string ]: `string` | `string`[]} -Copy additional files to the output directory, before the build runs. +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. -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: +```ts +{ 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 `..`. --- @@ -1577,15 +1642,15 @@ public readonly buildOptions: BuildOptions; Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. -`buildOptions.outdir: string` +* `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` +* `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` +* `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). @@ -1596,14 +1661,27 @@ If paths cannot be found, a good starting point is to look at the concatenation ##### `copyDir`Optional ```typescript -public readonly copyDir: string; +public readonly copyDir: string | string[] | {[ key: string ]: string | string[]}; ``` -- *Type:* `string` +- *Type:* `string` | `string`[] | {[ key: string ]: `string` | `string`[]} -Copy additional files to the output directory, before the build runs. +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. -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: +```ts +{ 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 `..`. --- diff --git a/src/bundler.ts b/src/bundler.ts index 66437fb3..7b81562c 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -1,4 +1,5 @@ -import { join, normalize, resolve, posix } from 'path'; +import { mkdirSync } from 'fs'; +import { join, normalize, relative, resolve, posix, isAbsolute } from 'path'; import { BundlingOptions, DockerImage, @@ -23,15 +24,15 @@ export interface BundlerProps { /** * Build options passed on to esbuild. Please refer to the esbuild Build API docs for details. * - * - `buildOptions.outdir: string` + * * `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` + * * `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` + * * `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). * @@ -41,12 +42,27 @@ export interface BundlerProps { readonly buildOptions?: BuildOptions; /** - * Copy additional files to the output directory, before the build runs. + * 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: + * ```ts + * { 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 `..`. + * * @stability stable */ - readonly copyDir?: string; + readonly copyDir?: string | string[] | Record; /** @@ -111,17 +127,28 @@ export class EsbuildBundler { this.local = { tryBundle: (outputDir: string, _options: BundlingOptions): boolean => { - try { - if (this.props.copyDir) { - FileSystem.copyDirectory( - resolve( - this.props?.buildOptions?.absWorkingDir ?? process.cwd(), - this.props.copyDir, - ), - outputDir, + + if (this.props.copyDir) { + const copyDir = this.getCopyDirList(this.props.copyDir); + + copyDir.forEach(([dest, src]) => { + const srcDir = resolve( + this.props?.buildOptions?.absWorkingDir ?? process.cwd(), + src, ); - } + const destDir = resolve(outputDir, dest) ; + const destToOutput = relative(outputDir, destDir); + if (destToOutput.startsWith('..') || isAbsolute(destToOutput)) { + throw new Error('Cannot copy files to outside of the asset staging directory. See docs for details.'); + } + + mkdirSync(destDir, { recursive: true }); + FileSystem.copyDirectory(srcDir, destDir); + }); + } + + try { const buildResult: BuildResult = buildFn({ entryPoints, ...(this.props?.buildOptions || {}), @@ -138,6 +165,38 @@ export class EsbuildBundler { }; } + private getCopyDirList(copyDir: BundlerProps['copyDir']): Array<[string, string]> { + // Nothing to copy + if (!copyDir) { + return []; + } + + // List of strings + if (Array.isArray(copyDir)) { + return copyDir.map((src: string) => ['.', src]); + } + + // A map + if ( + typeof copyDir === 'object' && + !Array.isArray(copyDir) && + copyDir !== null + ) { + return Object + .entries(copyDir) + .flatMap(([dest, sources]) => { + if (Array.isArray(sources)) { + return sources.map((src) => [dest, src]) as Array<[string, string]>; + } + + return [[dest, sources]]; + }); + } + + // A single string + return [['.', copyDir as string]]; + } + private getOutputOptions( cdkOutputDir: string, path: Pick = posix, diff --git a/test/bundler.test.ts b/test/bundler.test.ts index a9fc71d9..00196274 100644 --- a/test/bundler.test.ts +++ b/test/bundler.test.ts @@ -1,3 +1,4 @@ +import { FileSystem } from 'aws-cdk-lib'; import { mocked } from 'jest-mock'; import { EsbuildBundler } from '../src/bundler'; import { BuildOptions, BuildResult } from '../src/esbuild-types'; @@ -127,4 +128,130 @@ describe('bundling', () => { ); }); }); + + describe('Given a copyDir', () => { + const assetOutputDir = 'cdk.out/asset.123456'; + const originalCopyDirectory = FileSystem.copyDirectory; + const copyDirectoryMock = jest.fn(); + + beforeEach(() => { + FileSystem.copyDirectory = copyDirectoryMock; + }); + afterEach(() => { + copyDirectoryMock.mockReset(); + FileSystem.copyDirectory = originalCopyDirectory; + }); + + describe('and it is a string', () => { + it('should copy one directory into the root of the asset output directory', () => { + const bundler = new EsbuildBundler( + ['index.ts'], + { + buildOptions: { absWorkingDir: '/project' }, + copyDir: 'additionalData', + }, + ); + + bundler.local?.tryBundle(assetOutputDir, bundler); + + expect(copyDirectoryMock).toHaveBeenCalledTimes(1); + expect(copyDirectoryMock).toHaveBeenCalledWith( + '/project/additionalData', + expect.stringMatching(assetOutputDir), + ); + }); + }); + + describe('and it is an array', () => { + it('should copy all listed directories into the root of the asset output directory', () => { + const bundler = new EsbuildBundler( + ['index.ts'], + { + buildOptions: { absWorkingDir: '/project' }, + copyDir: ['one', 'two', 'three'], + }, + ); + + bundler.local?.tryBundle(assetOutputDir, bundler); + + expect(copyDirectoryMock).toHaveBeenCalledTimes(3); + expect(copyDirectoryMock).toHaveBeenLastCalledWith( + '/project/three', + expect.stringMatching(assetOutputDir), + ); + }); + }); + + describe('and it is a map', () => { + it('should copy all listed copy sources to their respective targets within the asset output directory', () => { + const bundler = new EsbuildBundler( + ['index.ts'], + { + buildOptions: { absWorkingDir: '/project' }, + copyDir: { + '.': ['one'], + 'nested/directory': ['two', 'three'], + 'complex/../nesting': 'nested/dir/four', + }, + }, + ); + + bundler.local?.tryBundle(assetOutputDir, bundler); + + expect(copyDirectoryMock).toHaveBeenCalledTimes(4); + expect(copyDirectoryMock.mock.calls).toEqual([ + ['/project/one', expect.stringMatching(assetOutputDir)], + ['/project/two', expect.stringMatching(assetOutputDir + '/nested/directory')], + ['/project/three', expect.stringMatching(assetOutputDir + '/nested/directory')], + ['/project/nested/dir/four', expect.stringMatching(assetOutputDir + '/nesting')], + ]); + }); + + describe('and we are actually copying data', () => { + beforeEach(() => { + FileSystem.copyDirectory = originalCopyDirectory; + }); + + it('should copy all listed copy sources to their respective targets within the asset output directory', () => { + const bundler = new EsbuildBundler( + ['index.ts'], + { + copyDir: { + '.': ['src'], + 'nested/directory': ['src'], + 'complex/../nesting': 'src', + }, + }, + ); + + expect(() => { + bundler.local?.tryBundle(assetOutputDir, bundler); + }).not.toThrowError(); + + expect(copyDirectoryMock).toHaveBeenCalledTimes(0); + }); + }); + + it('should throw if we are trying to copy files outside of the asset outpur dir', () => { + const bundler = new EsbuildBundler( + ['index.ts'], + { + buildOptions: { absWorkingDir: '/project' }, + copyDir: { + '..': 'not/allowed', + }, + }, + ); + + expect(() => { + bundler.local?.tryBundle(assetOutputDir, bundler); + }).toThrowError( + 'Cannot copy files to outside of the asset staging directory. See docs for details.', + ); + + expect(copyDirectoryMock).toHaveBeenCalledTimes(0); + }); + + }); + }); });