(lambda-nodejs): nodeModules + pnpm v11 empty pnpm-workspace.yaml blocks native module builds, beforeInstall cannot override it
- 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
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