DefaultStackSynthesizer: App level synthesizer leaks assets between Stacks when specified
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the bug
I am working on a multi-stack App with multiple specified account targets (via the env property on each Stack). When specifying the property defaultStackSynthesizer on the App I started facing deployment issues I had never faced before. The output implied that the specific Stack I was trying to deploy wanted to publish assets belonging to other stacks.
By looking at the manifest for each Stack I was able to verify this. This issue does not present itself if you have all Stacks configured to deploy to the same account. I assume the same goes if they are all account agnostic. While the deployment failures are not irrelevant I think the underlying issue of the wrong assets being added to manifests is the key here.
### Expected Behavior
The Stack manifests only contain relevant files.
### Current Behavior
The Stack manifests contains assets belonging to other stacks. Screenshot of manifest for Stack attached to App after another Stack:

Note how it not only contains its own template but also the template for StackA.
### Reproduction Steps
1. Create a new project using "npx cdk init app --language=typescript"
2. Modify the app to instantiate a DefaultStackSynthesizer, no props needed
3. Add two Stacks that use this App as scope
4. Synth using "npx cdk synth"
5. Check Stack manifests under cdk.out
I have also taken the liberty of creating a repository which highlights the issue: [CDK Assets leak](https://github.com/castodius/cdk-assets-leak). The cdk.out directory is commited and this [file](https://github.com/castodius/cdk-assets-leak/blob/main/cdk.out/StackB.assets.json) highlights the issue with more than one template being present.
### Possible Solution
I have located the source of the issue in DefaultStackSynthesizer. It stems from reusableBind

It copies the DefaultStackSynthesizer, however this only partially works since the class instance contains another class instance and the reference is kept. The reference to the assetManifest is kept and all Stacks end up sharing the same AssetManifestBuilder.

This snippet of code highlights why this is an issue:
```javscript
const helper = {
value: 1
}
const wrapper = {
foo: helper
}
// prints 1
console.log(wrapper.foo.value)
const copy1 = Object.create(wrapper)
copy1.foo.value = 2
// prints 2 since the reference to helper was kept
console.log(wrapper.foo.value)
```
### Additional Information/Context
I will create a PR soon that should fix this.
### CDK CLI Version
2.127.0 (build 6c90efc)
### Framework Version
_No response_
### Node.js Version
v20.10.0
### OS
Sonoma 14.2.1
### Language
TypeScript
### Language Version
_No response_
### Other information
_No response_
Contributor guide
Research direction
Start with DefaultStackSynthesizer and its reusableBind path, then inspect how the nested AssetManifestBuilder reference is retained. Reproduce the issue with two stacks using npx cdk synth and compare their cdk.out manifests; done means each Stack manifest contains only its relevant assets.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- infrastructure
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100