From f1e644cca5a41552c31c02baf688402df1323cfc Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 19 May 2021 14:58:24 +0200 Subject: [PATCH 1/3] feat(lambda-nodejs): pnpm support Closes #14757 --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 14 +++--- .../@aws-cdk/aws-lambda-nodejs/lib/Dockerfile | 5 +- .../aws-lambda-nodejs/lib/function.ts | 46 +++++++++++-------- .../aws-lambda-nodejs/lib/package-manager.ts | 8 ++++ .../test/package-manager.test.ts | 9 +++- 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 981bf6755148e..05b225c4f7ff1 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -46,10 +46,9 @@ when working with the AWS SDK for JavaScript. Set the `awsSdkConnectionReuse` pr ## Lock file -The `NodejsFunction` requires a dependencies lock file (`yarn.lock` or -`package-lock.json`). When bundling in a Docker container, the path containing this -lock file is used as the source (`/asset-input`) for the volume mounted in the -container. +The `NodejsFunction` requires a dependencies lock file (`yarn.lock`, `package-lock.json` or +`pnpm-lock.yml`). When bundling in a Docker container, the path containing this lock file is +used as the source (`/asset-input`) for the volume mounted in the container. By default, the construct will try to automatically determine your project lock file. Alternatively, you can specify the `depsLockFilePath` prop manually. In this @@ -114,8 +113,9 @@ new lambda.NodejsFunction(this, 'my-handler', { ``` The modules listed in `nodeModules` must be present in the `package.json`'s dependencies or -installed. The same version will be used for installation. The lock file (`yarn.lock` or -`package-lock.json`) will be used along with the right installer (`yarn` or `npm`). +installed. The same version will be used for installation. The lock file (`yarn.lock`, +`pnpm-lock.yml` or `package-lock.json`) will be used along with the right installer (`yarn`, +`pnpm` or `npm`). When working with `nodeModules` using native dependencies, you might want to force bundling in a Docker container even if `esbuild` is available in your environment. This can be done by setting @@ -219,7 +219,7 @@ new lambda.NodejsFunction(this, 'my-handler', { ``` This image should have `esbuild` installed **globally**. If you plan to use `nodeModules` it -should also have `npm` or `yarn` depending on the lock file you're using. +should also have `npm`, `yarn` or `pnpm` depending on the lock file you're using. Use the [default image provided by `@aws-cdk/aws-lambda-nodejs`](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-nodejs/lib/Dockerfile) as a source of inspiration. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile index 27fe83af43c35..578b1b8135d5c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile @@ -1,11 +1,14 @@ # The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. -ARG IMAGE=public.ecr.aws/sam/build-nodejs12.x +ARG IMAGE=public.ecr.aws/sam/build-nodejs14.x FROM $IMAGE # Install yarn RUN npm install --global yarn@1.22.5 +# Install pnpm +RUN npm install --global pnpm + # Install esbuild # (unsafe-perm because esbuild has a postinstall script) ARG ESBUILD_VERSION=0 diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 476c1e45b595c..d5519dbc5da34 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -84,28 +84,11 @@ export class NodejsFunction extends lambda.Function { throw new Error('Only `NODEJS` runtimes are supported.'); } - // Find lock file - let depsLockFilePath: string; - if (props.depsLockFilePath) { - if (!fs.existsSync(props.depsLockFilePath)) { - throw new Error(`Lock file at ${props.depsLockFilePath} doesn't exist`); - } - if (!fs.statSync(props.depsLockFilePath).isFile()) { - throw new Error('`depsLockFilePath` should point to a file'); - } - depsLockFilePath = path.resolve(props.depsLockFilePath); - } else { - const lockFile = findUp(PackageManager.YARN.lockFile) ?? findUp(PackageManager.NPM.lockFile); - if (!lockFile) { - throw new Error('Cannot find a package lock file (`yarn.lock` or `package-lock.json`). Please specify it with `depsFileLockPath`.'); - } - depsLockFilePath = lockFile; - } - // Entry and defaults const entry = path.resolve(findEntry(id, props.entry)); const handler = props.handler ?? 'handler'; const runtime = props.runtime ?? lambda.Runtime.NODEJS_14_X; + const depsLockFilePath = findLockFile(props.depsLockFilePath); super(scope, id, { ...props, @@ -126,6 +109,33 @@ export class NodejsFunction extends lambda.Function { } } +/** + * Checks given lock file or searches for a lock file + */ +function findLockFile(depsLockFilePath?: string): string { + if (depsLockFilePath) { + if (!fs.existsSync(depsLockFilePath)) { + throw new Error(`Lock file at ${depsLockFilePath} doesn't exist`); + } + + if (!fs.statSync(depsLockFilePath).isFile()) { + throw new Error('`depsLockFilePath` should point to a file'); + } + + return path.resolve(depsLockFilePath); + } + + const lockFile = findUp(PackageManager.PNPM.lockFile) + ?? findUp(PackageManager.YARN.lockFile) + ?? findUp(PackageManager.NPM.lockFile); + + if (!lockFile) { + throw new Error('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock` or `package-lock.json`). Please specify it with `depsFileLockPath`.'); + } + + return lockFile; +} + /** * Searches for an entry file. Preference order is the following: * 1. Given entry file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts index dd5260f87838c..a95373bd6d45f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts @@ -23,6 +23,12 @@ export class PackageManager { runCommand: ['yarn', 'run'], }); + public static PNPM = new PackageManager({ + lockFile: 'pnpm-lock.yaml', + installCommand: ['pnpm', 'install'], + runCommand: ['pnpm', 'run'], + }); + public static fromLockFile(lockFilePath: string): PackageManager { const lockFile = path.basename(lockFilePath); @@ -31,6 +37,8 @@ export class PackageManager { return PackageManager.NPM; case PackageManager.YARN.lockFile: return PackageManager.YARN; + case PackageManager.PNPM.lockFile: + return PackageManager.PNPM; default: return PackageManager.NPM; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts index f561bce592f12..572f84501e49f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts @@ -15,8 +15,15 @@ test('from a yarn.lock', () => { expect(packageManager.runBinCommand('my-bin')).toBe('yarn run my-bin'); }); -test('defaults to NPM', () => { +test('from a pnpm-lock.yml', () => { const packageManager = PackageManager.fromLockFile('/path/to/pnpm-lock.yaml'); + expect(packageManager).toEqual(PackageManager.PNPM); + + expect(packageManager.runBinCommand('my-bin')).toBe('pnpm run my-bin'); +}); + +test('defaults to NPM', () => { + const packageManager = PackageManager.fromLockFile('/path/to/other.lock'); expect(packageManager).toEqual(PackageManager.NPM); }); From e37701fe06402f03f65b31aa1866c4f7f3022c3c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 19 May 2021 15:00:55 +0200 Subject: [PATCH 2/3] yaml not yml --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 6 +++--- .../@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 05b225c4f7ff1..359f914da040a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -46,8 +46,8 @@ when working with the AWS SDK for JavaScript. Set the `awsSdkConnectionReuse` pr ## Lock file -The `NodejsFunction` requires a dependencies lock file (`yarn.lock`, `package-lock.json` or -`pnpm-lock.yml`). When bundling in a Docker container, the path containing this lock file is +The `NodejsFunction` requires a dependencies lock file (`yarn.lock`, `pnpm-lock.yaml` or +`package-lock.json`). When bundling in a Docker container, the path containing this lock file is used as the source (`/asset-input`) for the volume mounted in the container. By default, the construct will try to automatically determine your project lock file. @@ -114,7 +114,7 @@ new lambda.NodejsFunction(this, 'my-handler', { The modules listed in `nodeModules` must be present in the `package.json`'s dependencies or installed. The same version will be used for installation. The lock file (`yarn.lock`, -`pnpm-lock.yml` or `package-lock.json`) will be used along with the right installer (`yarn`, +`pnpm-lock.yaml` or `package-lock.json`) will be used along with the right installer (`yarn`, `pnpm` or `npm`). When working with `nodeModules` using native dependencies, you might want to force bundling in a diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts index 572f84501e49f..e0721bbec3e38 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts @@ -15,7 +15,7 @@ test('from a yarn.lock', () => { expect(packageManager.runBinCommand('my-bin')).toBe('yarn run my-bin'); }); -test('from a pnpm-lock.yml', () => { +test('from a pnpm-lock.yaml', () => { const packageManager = PackageManager.fromLockFile('/path/to/pnpm-lock.yaml'); expect(packageManager).toEqual(PackageManager.PNPM); From 7ab655c3b7555cb0629704cba4fb1bc68866c8ef Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 20 May 2021 08:59:18 +0200 Subject: [PATCH 3/3] more tests --- .../aws-lambda-nodejs/test/bundling.test.ts | 21 +++++++++++++++++++ .../aws-lambda-nodejs/test/docker.test.ts | 13 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 7d79365da5077..5a2549e8fbfc6 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -231,6 +231,27 @@ test('Detects yarn.lock', () => { }); }); +test('Detects pnpm-lock.yaml', () => { + const pnpmLock = '/project/pnpm-lock.yaml'; + Bundling.bundle({ + entry: __filename, + depsLockFilePath: pnpmLock, + runtime: Runtime.NODEJS_12_X, + nodeModules: ['delay'], + forceDockerBundling: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(pnpmLock), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: expect.arrayContaining([ + expect.stringMatching(/pnpm-lock\.yaml.+pnpm install/), + ]), + }), + }); +}); + test('with Docker build args', () => { Bundling.bundle({ entry, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts index efdbd8b8aa4cc..d1b85dfcca2b8 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts @@ -39,6 +39,19 @@ test('can yarn install with non root user', () => { expect(proc.status).toEqual(0); }); +test('can pnpm install with non root user', () => { + const proc = spawnSync('docker', [ + 'run', '-u', '500:500', + 'esbuild', + 'bash', '-c', [ + 'mkdir /tmp/test', + 'cd /tmp/test', + 'pnpm add constructs', + ].join(' && '), + ]); + expect(proc.status).toEqual(0); +}); + test('cache folders have the right permissions', () => { const proc = spawnSync('docker', [ 'run', 'esbuild',