diff --git a/packages/aws-cdk-lib/core/lib/private/asset-staging.ts b/packages/aws-cdk-lib/core/lib/private/asset-staging.ts index 0113f4c0f0e77..e388802b2342a 100644 --- a/packages/aws-cdk-lib/core/lib/private/asset-staging.ts +++ b/packages/aws-cdk-lib/core/lib/private/asset-staging.ts @@ -214,6 +214,15 @@ export function dockerExec(args: string[], options?: SpawnSyncOptions) { }); if (proc.error) { + if ((proc.error as NodeJS.ErrnoException).code === 'ENOENT') { + throw new ExecutionError( + lit`DockerNotFound`, + `Failed to run bundling with Docker: ${prog} is not installed or not in PATH. ` + + 'If you are bundling a Lambda function, consider installing esbuild locally ' + + '(npm install --save-dev esbuild) to avoid Docker entirely. ' + + `Otherwise, install Docker and make sure "${prog}" is available on your PATH.`, + ); + } throw proc.error; } diff --git a/packages/aws-cdk-lib/core/test/private/asset-staging.test.ts b/packages/aws-cdk-lib/core/test/private/asset-staging.test.ts index c275e0370f5e9..5c99b92150de9 100644 --- a/packages/aws-cdk-lib/core/test/private/asset-staging.test.ts +++ b/packages/aws-cdk-lib/core/test/private/asset-staging.test.ts @@ -1,7 +1,7 @@ import * as child_process from 'child_process'; import * as sinon from 'sinon'; import { AssetStaging, DockerImage } from '../../lib'; -import { AssetBundlingBindMount, AssetBundlingVolumeCopy } from '../../lib/private/asset-staging'; +import { AssetBundlingBindMount, AssetBundlingVolumeCopy, dockerExec } from '../../lib/private/asset-staging'; const DOCKER_CMD = process.env.CDK_DOCKER ?? 'docker'; @@ -85,6 +85,28 @@ describe('bundling', () => { ]), { encoding: 'utf-8', stdio: ['ignore', process.stderr, 'inherit'] })).toEqual(true); }); + test('dockerExec throws a helpful error when docker is not found (ENOENT)', () => { + const enoentError = new Error('spawnSync docker ENOENT') as NodeJS.ErrnoException; + enoentError.code = 'ENOENT'; + + sinon.stub(child_process, 'spawnSync').returns({ + status: null, + stderr: Buffer.from(''), + stdout: Buffer.from(''), + pid: 0, + output: [], + signal: null, + error: enoentError, + }); + + expect(() => dockerExec(['run', 'hello'])).toThrow( + /docker is not installed or not in PATH/, + ); + expect(() => dockerExec(['run', 'hello'])).toThrow( + /consider installing esbuild/, + ); + }); + test('AssetBundlingBindMount bundles with bind mount ', () => { // GIVEN sinon.stub(process, 'platform').value('darwin');