diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/PackageJsonGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/PackageJsonGenerator.java index 9190b8a0184..bb35c9d4298 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/PackageJsonGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/PackageJsonGenerator.java @@ -21,6 +21,7 @@ import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.typescript.codegen.util.MergeJsonNodes; import software.amazon.smithy.utils.IoUtils; import software.amazon.smithy.utils.SmithyInternalApi; @@ -43,9 +44,11 @@ static void writePackageJson( ) { // Write the package.json file. InputStream resource = PackageJsonGenerator.class.getResourceAsStream("base-package.json"); - ObjectNode node = Node.parse(IoUtils.toUtf8String(resource)) - .expectObjectNode() - .merge(settings.getPackageJson()); + ObjectNode node = MergeJsonNodes.mergeWithScripts( + Node.parse(IoUtils.toUtf8String(resource)) + .expectObjectNode(), + settings.getPackageJson() + ); // Merge TypeScript dependencies into the package.json file. for (Map.Entry> depEntry : dependencies.entrySet()) { diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodes.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodes.java new file mode 100644 index 00000000000..80a7ff4a40c --- /dev/null +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodes.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.typescript.codegen.util; + +import java.util.Objects; +import software.amazon.smithy.model.node.ObjectNode; + + +public final class MergeJsonNodes { + + private MergeJsonNodes() {} + + /** + * @param left - original node. + * @param right - overwriting node. + * @return new node with shallow merged fields except the recursively merged "scripts" field. + */ + public static ObjectNode mergeWithScripts(ObjectNode left, ObjectNode right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + + ObjectNode.Builder merged = left.toBuilder(); + right.getMembers().forEach((k, v) -> { + String key = k.getValue(); + if (left.containsMember(key)) { + if (left.getMember(key).get().isObjectNode() && v.isObjectNode() && key.equals("scripts")) { + merged.withMember(key, + MergeJsonNodes.mergeWithScripts(left.expectObjectMember(key), v.expectObjectNode()) + ); + } else { + merged.withMember(key, v); + } + } else { + merged.withMember(key, v); + } + }); + return merged.build(); + } +} diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodesTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodesTest.java new file mode 100644 index 00000000000..9be1f46a3f5 --- /dev/null +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/util/MergeJsonNodesTest.java @@ -0,0 +1,104 @@ +package software.amazon.smithy.typescript.codegen.util; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MergeJsonNodesTest { + + @Test + void apply() { + ObjectNode left = ObjectNode.parse(""" + { + "name": "hello, world", + "version": 5, + "scripts": { + "target": "exec", + "target2": "exec2", + "args": ["a", "b", "c"], + "nested": { + "deep": "." + } + }, + "metadata": { + "A": "A", + "B": "B", + "C": "C" + } + } + """).expectObjectNode(); + + ObjectNode right = ObjectNode.parse(""" + { + "version": 6, + "scripts": { + "target": "no-op", + "args": ["a", "b", "c", "d"], + "nested": { + "option": "b" + } + }, + "metadata": { + "A": "A" + } + } + """).expectObjectNode(); + + ObjectNode expected = ObjectNode.parse(""" + { + "name": "hello, world", + "version": 6, + "scripts": { + "target": "no-op", + "target2": "exec2", + "args": ["a", "b", "c", "d"], + "nested": { + "deep": ".", + "option": "b" + } + }, + "metadata": { + "A": "A" + } + } + """).expectObjectNode(); + + ObjectNode l = expected; + ObjectNode r = MergeJsonNodes.mergeWithScripts(left, right); + + assertEquals( + l.expectStringMember("name"), + r.expectStringMember("name") + ); + assertEquals( + l.expectNumberMember("version"), + r.expectNumberMember("version") + ); + assertEquals( + l.expectObjectMember("scripts").expectStringMember("target"), + r.expectObjectMember("scripts").expectStringMember("target") + ); + assertEquals( + l.expectObjectMember("scripts").expectStringMember("target2"), + r.expectObjectMember("scripts").expectStringMember("target2") + ); + assertEquals( + l.expectObjectMember("scripts").expectArrayMember("args").getElementsAs(Node::toString), + r.expectObjectMember("scripts").expectArrayMember("args").getElementsAs(Node::toString) + ); + assertEquals( + l.expectObjectMember("scripts").expectObjectMember("nested").expectStringMember("option"), + r.expectObjectMember("scripts").expectObjectMember("nested").expectStringMember("option") + ); + assertEquals( + 1, + r.expectObjectMember("scripts").expectObjectMember("nested").getStringMap().size() + ); + assertEquals( + 1, + r.expectObjectMember("metadata").getStringMap().size() + ); + } +} \ No newline at end of file