aws / aws/aws-cdk

(lambda-nodejs): nodeModules + pnpm v11 empty pnpm-workspace.yaml blocks native module builds, beforeInstall cannot override it

Open
#37,898 8 comments 6 reactions 0 assignees View on GitHub
@aws-cdk/aws-lambda-nodejs bug effort/medium p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
1d 19h
Merged PRs (30d)
74

Description

### Describe the bug

When using `NodejsFunction` with `bundling.nodeModules` in a pnpm v11 workspace, the install step fails for packages that require native build scripts (e.g. `ssh2-sftp-client` → `ssh2` → `cpu-features`).

pnpm v11 requires `allowBuilds` entries in `pnpm-workspace.yaml` to explicitly permit install scripts. CDK unconditionally writes an **empty** `pnpm-workspace.yaml` into the output directory before running `pnpm install` (to prevent pnpm from walking up to the monorepo root, see #21910). This empty file strips any `allowBuilds` config and causes the install to fail.

The `beforeInstall` command hook cannot work around this. The execution order in `createBundlingCommand` is:

```
beforeBundling → esbuild → beforeInstall → depsCommand (writes empty .yaml + runs install) → afterBundling
```

`beforeInstall` fires before `depsCommand`, so anything written to `pnpm-workspace.yaml` there is immediately overwritten by CDK.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

The `beforeInstall` hook runs just before the actual `pnpm install` call, after CDK has written `pnpm-workspace.yaml`, allowing users to inject `allowBuilds` entries for packages that require native builds.

### Current Behavior

`beforeInstall` runs before `depsCommand`, which means CDK's empty `pnpm-workspace.yaml` write always wins. There is no hook that runs between CDK writing `pnpm-workspace.yaml` and CDK invoking `pnpm install`.

The install fails with:

```
ERR_PNPM_INSTALL_SCRIPTS_NOT_ALLOWED cpu-features@1.x.x is not allowed to run install scripts
```

### Reproduction Steps

1. Create a pnpm v11 monorepo workspace with `strictDepBuilds: true` in `pnpm-workspace.yaml`.
2. Add a `NodejsFunction` with a dependency that requires native builds (e.g. `ssh2-sftp-client`):

```typescript
new NodejsFunction(this, "MyFn", {
entry: "src/handler.ts",
runtime: Runtime.NODEJS_22_X,
bundling: {
nodeModules: ["ssh2-sftp-client"],
commandHooks: {
beforeInstall: (_inputDir, outputDir) => [
// Attempting to set allowBuilds — has no effect, CDK overwrites this file
`printf 'allowBuilds:\\n cpu-features: true\\n ssh2: true\\n' > "${outputDir}/pnpm-workspace.yaml"`,
],
},
},
});
```

3. Run `cdk synth` or `cdk deploy`.
4. Observe the `ERR_PNPM_INSTALL_SCRIPTS_NOT_ALLOWED` error for `cpu-features`.

### Possible Solution

_No response_

### Additional Information/Context

The workaround is to stop using `nodeModules` entirely and replicate CDK's install manually in `afterBundling`, where CDK no longer touches `pnpm-workspace.yaml`:

```typescript
bundling: {
externalModules: ["@aws-sdk/*", "ssh2-sftp-client"],
commandHooks: {
beforeBundling: () => [],
beforeInstall: () => [],
afterBundling: (_inputDir, outputDir) => [
`printf 'allowBuilds:\\n cpu-features: true\\n ssh2: true\\n' > "${outputDir}/pnpm-workspace.yaml"`,
`echo '{"dependencies":{"ssh2-sftp-client":"~9.0.4"}}' > "${outputDir}/package.json"`,
`cp "${path.resolve(__dirname, "../../../../pnpm-lock.yaml")}" "${outputDir}/pnpm-lock.yaml"`,
`cd "${outputDir}" && pnpm install --config.node-linker=hoisted --config.package-import-method=clone-or-copy --no-prefer-frozen-lockfile`,
`rm -f "${outputDir}/node_modules/.modules.yaml"`,
],
},
},
```

This is verbose and fragile (lockfile path is hardcoded relative), which is why it could be possible to handle this through the intended `nodeModules` + `beforeInstall` API.

The relevant code is in [`bundling.ts`](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts).

Related issues:
- #36567: same empty `pnpm-workspace.yaml` mechanism breaks `.npmrc` inheritance (open)
- #21910: original issue that introduced the empty `pnpm-workspace.yaml` write (closed)
- pnpm/pnpm#10988: pnpm side: `allowBuilds` not inherited in sub-directory workspaces (open)

### AWS CDK Library version (aws-cdk-lib)

2.211.0

### AWS CDK CLI version

2.1017.0

### Node.js Version

22.x

### OS

macOS

### Language

TypeScript

### Language Version

5.x

### Other information

This regression is specific to pnpm v11. pnpm v9 and earlier did not require `allowBuilds` for native build scripts, so the empty `pnpm-workspace.yaml` write was harmless. pnpm v11 made `strictDepBuilds` the effective default, making this a breaking change for any `nodeModules` usage involving native dependencies.

Contributor guide

Open the contributing guide

Research direction

Start in packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts, especially createBundlingCommand and the depsCommand ordering. Run the provided pnpm v11 native-module reproduction with cdk synth, then verify that beforeInstall can modify pnpm-workspace.yaml after CDK writes it and before pnpm install runs, allowing the build to complete.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.