diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts b/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts index 9a008576d..89c710e2d 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts @@ -143,7 +143,8 @@ function stripConstructPath(resource: any): any { return resource; } - const copy = JSON.parse(JSON.stringify(resource)); - delete copy.Metadata['aws:cdk:path']; - return copy; + // A shallow copy is enough: the only thing being removed is one key of + // `Metadata`, and the caller only reads the result. + const { 'aws:cdk:path': _, ...metadata } = resource.Metadata; + return { ...resource, Metadata: metadata }; } diff --git a/packages/@aws-cdk/toolkit-lib/test/api/refactoring/refactoring.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/refactoring/refactoring.test.ts index 25c1cf70f..07c537395 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/refactoring/refactoring.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/refactoring/refactoring.test.ts @@ -392,6 +392,38 @@ describe(computeResourceDigests, () => { expect(result['Stack1.Q1']).toBe(result['Stack1.Q2']); }); + test('other metadata still contributes to the digest, and the input is not modified', () => { + const makeTemplate = (assetPath: string) => ({ + Resources: { + Q1: { + Type: 'AWS::SQS::Queue', + Properties: { Foo: 'Bar' }, + Metadata: { + 'aws:cdk:path': 'Stack/Q1/Resource', + 'aws:asset:path': assetPath, + }, + }, + }, + }); + + const template = makeTemplate('asset.1234'); + const stacks = makeStacks([template]); + const digest = computeResourceDigests(stacks)['Stack1.Q1']; + + // Metadata other than the construct path is part of the digest + const other = computeResourceDigests(makeStacks([makeTemplate('asset.5678')]))['Stack1.Q1']; + expect(digest).not.toBe(other); + + // Computing the digest leaves the caller's template alone + expect(template.Resources.Q1.Metadata).toEqual({ + 'aws:cdk:path': 'Stack/Q1/Resource', + 'aws:asset:path': 'asset.1234', + }); + + // ...and is stable across repeated calls + expect(computeResourceDigests(stacks)['Stack1.Q1']).toBe(digest); + }); + test('different physical IDs lead to different digests', () => { mockLoadResourceModel.mockReturnValue({ primaryIdentifier: ['FooName'],