Skip to content

Commit

Permalink
feat(lambda-nodejs): pnpm support (aws#14772)
Browse files Browse the repository at this point in the history
Closes aws#14757
  • Loading branch information
jogold authored and hollanddd committed Aug 26, 2021
1 parent 75d4cc2 commit 4c93beb
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 27 deletions.
14 changes: 7 additions & 7 deletions packages/@aws-cdk/aws-lambda-nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, `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.
Alternatively, you can specify the `depsLockFilePath` prop manually. In this
Expand Down Expand Up @@ -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.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
Docker container even if `esbuild` is available in your environment. This can be done by setting
Expand Down Expand Up @@ -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.
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
46 changes: 28 additions & 18 deletions packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
}
Expand Down
21 changes: 21 additions & 0 deletions packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.yaml', () => {
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);
});

Expand Down

0 comments on commit 4c93beb

Please sign in to comment.