Skip to content

fix(lambda-nodejs): use direct spawn for local bundling#37292

Merged
mergify[bot] merged 7 commits intomainfrom
fix-bundling-cmd
Mar 25, 2026
Merged

fix(lambda-nodejs): use direct spawn for local bundling#37292
mergify[bot] merged 7 commits intomainfrom
fix-bundling-cmd

Conversation

@Abogical
Copy link
Copy Markdown
Member

@Abogical Abogical commented Mar 19, 2026

Reason for this change

The local bundling path builds a single shell command string and executes it via spawnSync("bash", ["-c", command]). User-controlled bundling properties are interpolated into this string without sanitization. Using direct spawnSync with argument arrays is the idiomatic Node.js approach and avoids shell interpretation entirely.

Description of changes

Replace shell-based command execution in the local bundling path with direct spawnSync calls using argument arrays.

  • PackageManager.runBinCommand() returns string[] instead of a joined string. Docker-path callers use .join(" ").
  • New BundlingStep discriminated union type: shell (commandHooks), spawn (esbuild/tsc/install), fs (file operations).
  • createLocalBundlingSteps() builds the step sequence for local bundling using toCliArgsArray() and getTsconfigCompilerOptionsArray() (no shell quoting needed).
  • tryBundle() executes steps sequentially by type.
  • Docker bundling path's command is escaped, the default command relies on bash already existing so we can escape it with the bash syntax.
  • commandHooks remain shell-executed (user-provided by contract).

Describe any new or updated permissions being added

N/A

Description of how you validated changes

  • All 125 existing aws-lambda-nodejs tests pass (6 suites).
  • Updated Local bundling test to assert direct spawn calls.
  • Added 6 new tests: esbuild options via spawn, nodeModules with fs operations, commandHooks via shell, preCompilation with tsc spawn, shell metacharacter handling, and pnpm workspace/cleanup.
  • Full lerna run build --scope=aws-cdk-lib passes.
  • Locally created a stack comparing the docker and local asset output

Checklist

  • My code adheres to the CONTRIBUTING GUIDE and DESIGN GUIDELINES

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

### Issue #

Closes cdklabs/cdk-ops#4931.

### Reason for this change

The local bundling path builds a single shell command string and
executes it via `spawnSync("bash", ["-c", command])`. User-controlled
bundling properties are interpolated into this string without
sanitization. Using direct `spawnSync` with argument arrays is the
idiomatic Node.js approach and avoids shell interpretation entirely.

### Description of changes

Replace shell-based command execution in the local bundling path with
direct `spawnSync` calls using argument arrays.

- `PackageManager.runBinCommand()` returns `string[]` instead of a
  joined string. Docker-path callers use `.join(" ")`.
- New `BundlingStep` discriminated union type: `shell` (commandHooks),
  `spawn` (esbuild/tsc/install), `fs` (file operations).
- `createLocalBundlingSteps()` builds the step sequence for local
  bundling using `toCliArgsArray()` and
  `getTsconfigCompilerOptionsArray()` (no shell quoting needed).
- `tryBundle()` executes steps sequentially by type.
- Docker bundling path is completely unchanged.
- `commandHooks` remain shell-executed (user-provided by contract).

### Describe any new or updated permissions being added

N/A

### Description of how you validated changes

- All 125 existing aws-lambda-nodejs tests pass (6 suites).
- Updated `Local bundling` test to assert direct spawn calls.
- Added 6 new tests: esbuild options via spawn, nodeModules with
  fs operations, commandHooks via shell, preCompilation with tsc
  spawn, shell metacharacter handling, and pnpm workspace/cleanup.
- Full `lerna run build --scope=aws-cdk-lib` passes.

### Checklist
- [x] My code adheres to the CONTRIBUTING GUIDE and DESIGN GUIDELINES

---

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions github-actions bot added the p2 label Mar 19, 2026
@aws-cdk-automation aws-cdk-automation requested a review from a team March 19, 2026 16:09
@github-actions github-actions bot added the star-contributor [Pilot] contributed between 25-49 PRs to the CDK label Mar 19, 2026
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Mar 19, 2026
Copy link
Copy Markdown
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This review is outdated)

@aws-cdk-automation aws-cdk-automation added the pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes. label Mar 19, 2026
@Abogical Abogical changed the title fix(aws-lambda-nodejs): use direct spawn for local bundling fix(lambda-nodejs): use direct spawn for local bundling Mar 19, 2026
@Abogical Abogical added the pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. label Mar 19, 2026
@Abogical Abogical removed the pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. label Mar 19, 2026
@Abogical Abogical added the pr-linter/exempt-integ-test The PR linter will not require integ test changes label Mar 19, 2026
@aws-cdk-automation aws-cdk-automation dismissed their stale review March 19, 2026 18:14

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@aws-cdk-automation aws-cdk-automation added the pr/needs-maintainer-review This PR needs a review from a Core Team Member label Mar 19, 2026
case 'shell':
for (const cmd of step.commands) {
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically speaking you should use process.env.COMSPEC as your shell on Windows.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added process.env.COMSPEC

/**
* Creates structured bundling steps for local execution via direct spawn (no shell).
*/
private createLocalBundlingSteps(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new function giatn is not matched by an equally giant function disappearing.

I would expect this to be a rewrite of something else?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local bundling steps are different than the one using docker. So both functions will remain, one will not replace the other.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. So we will have 2 functions like this:

function esBuildCommandForLocalExecution(): string[];
function esBuildCommandForDockerExecution(): string;

With mostly the same logic, but a different return type?

If that's true, then next time we add an option or make a change, we need to do it in 2 places?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I'll deduplicate any common functions here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored the functions with deduplication.

exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', cmd],
{ ...execOptions, windowsVerbatimArguments: osPlatform === 'win32' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No cwd here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cwd is part of execOptions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so then it should also be part of execOptions in the "spawn" branch of the switch?

...(step.cwd ? { cwd: step.cwd } : {}),
});
break;
case 'fs':
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case 'fs':
case 'callback':

?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed.

- Use getTsconfigCompilerOptionsArray in Docker path instead of naive
  string splitting to properly handle compiler option values with spaces
- Wrap callback step operations with try-catch for contextual error
  messages during local bundling file operations
- Add regression tests for Docker preCompilation tsconfig handling
- Add test for callback failure error context
- Add getTsconfigCompilerOptionsArray parity tests in util.test.ts
- Remove unused getTsconfigCompilerOptions import from bundling.ts
@rix0rrr rix0rrr added the pr/do-not-merge This PR should not be merged at this time. label Mar 25, 2026
@aws-cdk-automation aws-cdk-automation removed the pr/needs-maintainer-review This PR needs a review from a Core Team Member label Mar 25, 2026
@Abogical Abogical removed the pr/do-not-merge This PR should not be merged at this time. label Mar 25, 2026
@mergify
Copy link
Copy Markdown
Contributor

mergify bot commented Mar 25, 2026

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify
Copy link
Copy Markdown
Contributor

mergify bot commented Mar 25, 2026

Merge Queue Status

  • Entered queue2026-03-25 09:34 UTC · Rule: default-squash
  • Checks passed · in-place
  • Merged2026-03-25 10:17 UTC · at 4217431b1175761cfaef30c1c54fa59d6a52db0c

This pull request spent 42 minutes 45 seconds in the queue, including 42 minutes 33 seconds running CI.

Required conditions to merge

@mergify
Copy link
Copy Markdown
Contributor

mergify bot commented Mar 25, 2026

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 9bf4263 into main Mar 25, 2026
18 of 19 checks passed
@mergify mergify bot deleted the fix-bundling-cmd branch March 25, 2026 10:17
@github-actions
Copy link
Copy Markdown
Contributor

Comments on closed issues and PRs are hard for our team to see.
If you need help, please open a new issue that references this one.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

contribution/core This is a PR that came from AWS. p2 pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes. pr-linter/exempt-integ-test The PR linter will not require integ test changes star-contributor [Pilot] contributed between 25-49 PRs to the CDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants