diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ignore_trees_removing-stdout.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ignore_trees_removing-stdout.snap index b6b9aaa0..68a2a314 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ignore_trees_removing-stdout.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ignore_trees_removing-stdout.snap @@ -2,4 +2,4 @@ source: tests/rojo_test/syncback_util.rs expression: "String::from_utf8_lossy(&output.stdout)" --- - +Removing C:/Users/Joe/AppData/Local/Temp/.tmpGY4Eb8/ignore_trees_removing/src diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird-stdout.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird-stdout.snap index 1f97ad7e..bbdf4f6b 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird-stdout.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird-stdout.snap @@ -4,3 +4,5 @@ expression: "String::from_utf8_lossy(&output.stdout)" --- Writing src/modules/ClientModule.luau Writing src/modules/ServerModule.luau +Removing C:/Users/Joe/AppData/Local/Temp/.tmpLKd1Ar/nested_projects_weird/client-only.project.json5 +Removing C:/Users/Joe/AppData/Local/Temp/.tmpLKd1Ar/nested_projects_weird/server-only.project.json5 diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback-stdout.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback-stdout.snap index 3927e9a3..a787ab3e 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback-stdout.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback-stdout.snap @@ -3,5 +3,6 @@ source: tests/rojo_test/syncback_util.rs expression: "String::from_utf8_lossy(&output.stdout)" --- Writing src/ChildWithoutDuplicates/Child/.gitkeep +Writing src/ChildWithDuplicates Writing src/ChildWithoutDuplicates Writing src/ChildWithoutDuplicates/Child diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_duplicate-stdout.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_duplicate-stdout.snap index 20ef2edd..05d449d0 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_duplicate-stdout.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_duplicate-stdout.snap @@ -4,4 +4,6 @@ expression: "String::from_utf8_lossy(&output.stdout)" --- Writing src/Pointer_1.model.json5 Writing src/Pointer_2.model.json5 -Writing src/Target.model.json5 +Writing src/Target/init.meta.json5 +Writing src/Target +Removing C:/Users/Joe/AppData/Local/Temp/.tmpA4y8vf/ref_properties_duplicate/src/Target.model.json5 diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__duplicate_rojo_id-container.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__duplicate_rojo_id-container.model.json5.snap index ccba2a61..1aef5772 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__duplicate_rojo_id-container.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__duplicate_rojo_id-container.model.json5.snap @@ -7,19 +7,19 @@ expression: container.model.json5 { attributes: { Rojo_Id: "value_1", - Rojo_Ref_Value: "ReplicatedStorage/container/value_1", + Rojo_Ref_Value: "ReplicatedStorage/container/value_1" }, className: "ObjectValue", - name: "value_1", + name: "value_1" }, { attributes: { Rojo_Id: "value_1", - Rojo_Ref_Value: "ReplicatedStorage/container/value_2", + Rojo_Ref_Value: "ReplicatedStorage/container/value_2" }, className: "ObjectValue", - name: "value_2", - }, + name: "value_2" + } ], - className: "Folder", + className: "Folder" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_adding-src__int_value.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_adding-src__int_value.model.json5.snap index cb975e53..aeaa1991 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_adding-src__int_value.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_adding-src__int_value.model.json5.snap @@ -5,6 +5,6 @@ expression: src/int_value.model.json5 { className: "IntValue", properties: { - Value: 1337, - }, + Value: 1337 + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_removing-src__Message.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_removing-src__Message.model.json5.snap index 91452672..5c5bdc6f 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_removing-src__Message.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ignore_paths_removing-src__Message.model.json5.snap @@ -5,6 +5,6 @@ expression: src/Message.model.json5 { className: "Message", properties: { - Text: "This message should be written to the disk.", - }, + Text: "This message should be written to the disk." + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__dir_with_meta__init.meta.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__dir_with_meta__init.meta.json5.snap index b35cba30..55faa78c 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__dir_with_meta__init.meta.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__dir_with_meta__init.meta.json5.snap @@ -6,7 +6,7 @@ expression: src/dir_with_meta/init.meta.json5 className: "Configuration", properties: { Tags: [ - "This tag on dir_with_meta", - ], - }, + "This tag on dir_with_meta" + ] + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__model_json.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__model_json.model.json5.snap index a7a22849..213f61f9 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__model_json.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__model_json.model.json5.snap @@ -5,6 +5,6 @@ expression: src/model_json.model.json5 { className: "StringValue", properties: { - Value: "This text is model_json", - }, + Value: "This text is model_json" + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__project_json.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__project_json.project.json5.snap index 703b6d94..9280fd77 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__project_json.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__json_middlewares-src__project_json.project.json5.snap @@ -10,8 +10,8 @@ expression: src/project_json.project.json5 Value: [ 1337, -1337, - 1337, - ], - }, - }, + 1337 + ] + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__nested_projects-nested.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__nested_projects-nested.project.json5.snap index e83b07c8..ef7ed920 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__nested_projects-nested.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__nested_projects-nested.project.json5.snap @@ -9,11 +9,11 @@ expression: nested.project.json5 BoolValue: { $className: "BoolValue", $properties: { - Value: true, - }, + Value: true + } }, StringValue: { - $path: "string_value.txt", - }, - }, + $path: "string_value.txt" + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__project_reserialize-property_mismatch.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__project_reserialize-property_mismatch.project.json5.snap index 8f3cfa7a..06d62aeb 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__project_reserialize-property_mismatch.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__project_reserialize-property_mismatch.project.json5.snap @@ -8,8 +8,8 @@ expression: property_mismatch.project.json5 $className: "BrickColorValue", $properties: { Value: { - BrickColor: 345, - }, - }, - }, + BrickColor: 345 + } + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__pointer.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__pointer.model.json5.snap index 14a5988e..c2bd235f 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__pointer.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__pointer.model.json5.snap @@ -4,7 +4,7 @@ expression: src/pointer.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/target", + Rojo_Ref_Value: "ReplicatedStorage/target" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__target.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__target.model.json5.snap index ed24249b..b7b907c7 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__target.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties-src__target.model.json5.snap @@ -4,7 +4,7 @@ expression: src/target.model.json5 --- { attributes: { - Rojo_Id: "test referent id", + Rojo_Id: "test referent id" }, - className: "Folder", + className: "Folder" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_blank-src__pointer.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_blank-src__pointer.model.json5.snap index 14a5988e..c2bd235f 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_blank-src__pointer.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_blank-src__pointer.model.json5.snap @@ -4,7 +4,7 @@ expression: src/pointer.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/target", + Rojo_Ref_Value: "ReplicatedStorage/target" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Pointer_2.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Pointer_2.model.json5.snap index 7715f6d8..54b72c1c 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Pointer_2.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Pointer_2.model.json5.snap @@ -4,7 +4,7 @@ expression: src/Pointer_2.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/Target_2", + Rojo_Ref_Value: "ReplicatedStorage/Target_2" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Target_2.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Target_2.model.json5.snap index bc0a9ea8..87f30dd6 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Target_2.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_conflict-src__Target_2.model.json5.snap @@ -4,7 +4,7 @@ expression: src/Target_2.model.json5 --- { attributes: { - Rojo_Id: "identical ID", + Rojo_Id: "identical ID" }, - className: "Folder", + className: "Folder" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer1.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer1.model.json5.snap index bb3fecb1..79c8250c 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer1.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer1.model.json5.snap @@ -4,7 +4,7 @@ expression: src/Pointer1.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/Target1", + Rojo_Ref_Value: "ReplicatedStorage/Target1" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer2.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer2.model.json5.snap index 323cfc8b..77fe2f05 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer2.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer2.model.json5.snap @@ -4,7 +4,7 @@ expression: src/Pointer2.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/Target2", + Rojo_Ref_Value: "ReplicatedStorage/Target2" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer3.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer3.model.json5.snap index a9d67563..874c8b54 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer3.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__ref_properties_pruned-src__Pointer3.model.json5.snap @@ -4,7 +4,7 @@ expression: src/Pointer3.model.json5 --- { attributes: { - Rojo_Ref_Value: "ReplicatedStorage/Target3", + Rojo_Ref_Value: "ReplicatedStorage/Target3" }, - className: "ObjectValue", + className: "ObjectValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-default.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-default.project.json5.snap index ac1cf877..8bafeb8f 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-default.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-default.project.json5.snap @@ -9,8 +9,8 @@ expression: default.project.json5 project_node: { $className: "BoolValue", $properties: { - Value: true, - }, - }, - }, + Value: true + } + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-src__model_json.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-src__model_json.model.json5.snap index 9fde2d1c..69370b42 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-src__model_json.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__respect_old_middleware-src__model_json.model.json5.snap @@ -5,6 +5,6 @@ expression: src/model_json.model.json5 { className: "StringValue", properties: { - Value: "This should be a .model.json file", - }, + Value: "This should be a .model.json file" + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-default.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-default.project.json5.snap index 7ac993d9..089739e4 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-default.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-default.project.json5.snap @@ -8,13 +8,13 @@ expression: default.project.json5 tree: { $className: "Folder", src: { - $path: "src", + $path: "src" }, trigger_reserialization: { $className: "BoolValue", $properties: { - Value: true, - }, - }, - }, + Value: true + } + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__adjacent.meta.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__adjacent.meta.json5.snap index 96631d86..e07966b2 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__adjacent.meta.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__adjacent.meta.json5.snap @@ -3,8 +3,7 @@ source: tests/tests/syncback.rs expression: src/adjacent.meta.json5 --- { - $schema: "rojo/adjacent-meta", attributes: { - trigger_reserialization: true, - }, + trigger_reserialization: true + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__init__init.meta.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__init__init.meta.json5.snap index 93f64b68..c212fd02 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__init__init.meta.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__init__init.meta.json5.snap @@ -3,8 +3,7 @@ source: tests/tests/syncback.rs expression: src/init/init.meta.json5 --- { - $schema: "rojo/init-meta", attributes: { - trigger_reserialization: true, - }, + trigger_reserialization: true + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__model.model.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__model.model.json5.snap index 573f91dd..ea03ce5c 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__model.model.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__schema_roundtrip-src__model.model.json5.snap @@ -3,6 +3,5 @@ source: tests/tests/syncback.rs expression: src/model.model.json5 --- { - $schema: "rojo/model.json", - className: "BoolValue", + className: "BoolValue" } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__string_value_project-default.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__string_value_project-default.project.json5.snap index 950f0e5e..97328ea1 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__string_value_project-default.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__string_value_project-default.project.json5.snap @@ -9,14 +9,14 @@ expression: default.project.json5 inside_project_file: { $className: "StringValue", $properties: { - Value: "imgettingverytiredofwritingthesetests2", - }, + Value: "imgettingverytiredofwritingthesetests2" + } }, on_file_system: { $attributes: { - imgettingverytiredofwritingthesetests: "person299 was ahead of his time", + imgettingverytiredofwritingthesetests: "person299 was ahead of his time" }, - $path: "string_value.txt", - }, - }, + $path: "string_value.txt" + } + } } diff --git a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__unscriptable_properties-default.project.json5.snap b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__unscriptable_properties-default.project.json5.snap index 0d62dded..219bee85 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__unscriptable_properties-default.project.json5.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__tests__syncback__unscriptable_properties-default.project.json5.snap @@ -5,14 +5,14 @@ expression: default.project.json5 { name: "unscriptable_properties", syncbackRules: { - syncUnscriptable: true, + syncUnscriptable: true }, tree: { $className: "BinaryStringValue", $properties: { Value: { - BinaryString: "Rojo/is/cool", - }, - }, - }, + BinaryString: "Rojo/is/cool" + } + } + } } diff --git a/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/default.project.json5 b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/default.project.json5 new file mode 100644 index 00000000..dd6f2dba --- /dev/null +++ b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/default.project.json5 @@ -0,0 +1,10 @@ +{ + "name": "clean_mode_orphan_removal", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "$path": "src" + } + } +} diff --git a/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/KeptScript.luau b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/KeptScript.luau new file mode 100644 index 00000000..c7010c56 --- /dev/null +++ b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/KeptScript.luau @@ -0,0 +1,3 @@ +-- This script should be KEPT because the input model +-- has a ModuleScript with the same name "KeptScript" +print("I should be kept") diff --git a/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedFolder/NestedOrphan.luau b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedFolder/NestedOrphan.luau new file mode 100644 index 00000000..cadb9738 --- /dev/null +++ b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedFolder/NestedOrphan.luau @@ -0,0 +1,3 @@ +-- This nested script should also be REMOVED +-- when its parent folder is removed +print("I am also orphaned") diff --git a/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedScript.luau b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedScript.luau new file mode 100644 index 00000000..339ab7c1 --- /dev/null +++ b/rojo-test/syncback-tests/clean_mode_orphan_removal/input-project/src/OrphanedScript.luau @@ -0,0 +1,3 @@ +-- This script should be REMOVED in clean mode +-- because the input model doesn't have this script +print("I am orphaned") diff --git a/src/cli/syncback.rs b/src/cli/syncback.rs index 7a2cae18..134a5428 100644 --- a/src/cli/syncback.rs +++ b/src/cli/syncback.rs @@ -54,6 +54,12 @@ pub struct SyncbackCommand { /// If provided, the prompt for writing to the file system is skipped. #[clap(long, short = 'y')] pub non_interactive: bool, + + /// If provided, syncback will preserve existing file structure and middleware + /// formats when possible. Without this flag (default), syncback creates a fresh + /// project layout that exactly matches the input file, removing any orphaned files. + #[clap(long, short = 'n')] + pub incremental: bool, } impl SyncbackCommand { @@ -99,12 +105,17 @@ impl SyncbackCommand { } let syncback_timer = Instant::now(); - eprintln!("Beginning syncback..."); + if self.incremental { + eprintln!("Beginning incremental syncback..."); + } else { + eprintln!("Beginning syncback (clean mode)..."); + } let snapshot = syncback_loop( session_old.vfs(), &mut dom_old, dom_new, session_old.root_project(), + self.incremental, )?; log::debug!( "Syncback finished in {:.02}s!", diff --git a/src/syncback/mod.rs b/src/syncback/mod.rs index 6b853147..19f96413 100644 --- a/src/syncback/mod.rs +++ b/src/syncback/mod.rs @@ -14,9 +14,9 @@ use rbx_dom_weak::{ }; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, env, - path::Path, + path::{Path, PathBuf}, sync::OnceLock, }; @@ -74,6 +74,7 @@ pub fn syncback_loop( old_tree: &mut RojoTree, mut new_tree: WeakDom, project: &Project, + incremental: bool, ) -> anyhow::Result { let ignore_patterns = project .syncback_rules @@ -192,13 +193,40 @@ pub fn syncback_loop( let project_path = project.folder_location(); + // In clean mode, collect all existing paths from old tree for cleanup. + // These will be compared against new paths to remove orphaned files. + let existing_paths: HashSet = if !incremental { + log::debug!("Clean mode: collecting existing paths for cleanup"); + let mut paths = HashSet::new(); + for ref_id in descendants(old_tree.inner(), old_tree.get_root_id()) { + if let Some(inst) = old_tree.get_instance(ref_id) { + for path in inst.metadata().relevant_paths.iter() { + // Skip paths matching ignore patterns + if is_valid_path(&ignore_patterns, project_path, path) { + // Only include paths that actually exist on disk + if path.exists() { + paths.insert(path.clone()); + } + } + } + } + } + log::debug!("Collected {} existing paths", paths.len()); + paths + } else { + HashSet::new() + }; + let syncback_data = SyncbackData { vfs, old_tree, new_tree: &new_tree, project, + incremental, }; + // Always start with old reference for the Project middleware. + // In clean mode, child snapshots will have old=None (handled by SyncbackSnapshot methods). let mut snapshots = vec![SyncbackSnapshot { data: syncback_data, old: Some(old_tree.get_root_id()), @@ -211,19 +239,22 @@ pub fn syncback_loop( 'syncback: while let Some(snapshot) = snapshots.pop() { let inst_path = snapshot.get_new_inst_path(snapshot.new); - // We can quickly check that two subtrees are identical and if they are, - // skip reconciling them. - if let Some(old_ref) = snapshot.old { - match (old_hashes.get(&old_ref), new_hashes.get(&snapshot.new)) { - (Some(old), Some(new)) => { - if old == new { - log::trace!( - "Skipping {inst_path} due to it being identically hashed as {old:?}" - ); - continue; + // In incremental mode, we can quickly check that two subtrees are identical + // and if they are, skip reconciling them. In clean mode, we always process + // all instances to ensure fresh structure. + if incremental { + if let Some(old_ref) = snapshot.old { + match (old_hashes.get(&old_ref), new_hashes.get(&snapshot.new)) { + (Some(old), Some(new)) => { + if old == new { + log::trace!( + "Skipping {inst_path} due to it being identically hashed as {old:?}" + ); + continue; + } } + _ => unreachable!("All Instances in both DOMs should have hashes"), } - _ => unreachable!("All Instances in both DOMs should have hashes"), } } @@ -331,6 +362,91 @@ pub fn syncback_loop( snapshots.extend(syncback.children); } + // In clean mode, remove any existing paths that weren't overwritten by new structure. + // This ensures no orphaned files remain from the old project layout. + if !incremental && !existing_paths.is_empty() { + log::debug!("Clean mode: checking for orphaned files to remove"); + + // Helper to normalize a path by stripping UNC prefix on Windows + // This is needed because paths may have different prefixes (\\?\ vs regular) + fn normalize_path(p: &Path) -> PathBuf { + // Strip Windows UNC prefix if present + let path_str = p.to_string_lossy(); + if path_str.starts_with(r"\\?\") || path_str.starts_with("//?/") { + PathBuf::from(&path_str[4..]) + } else { + p.to_path_buf() + } + } + + // Convert added_paths to absolute paths by joining with project base path. + // fs_snapshot paths are relative to the project folder. + let added_paths: HashSet = fs_snapshot + .added_paths() + .into_iter() + .map(|p| normalize_path(&project_path.join(p))) + .collect(); + + // Get the project file path to exclude it from removal + let project_file = normalize_path(&project.file_location); + + // First pass: collect normalized paths to remove (deduplicated) + let mut paths_to_remove: HashSet = HashSet::new(); + for old_path in &existing_paths { + let old_path_norm = normalize_path(old_path); + + // Never remove the project file itself + if old_path_norm == project_file { + log::debug!("Skipping project file: {}", old_path.display()); + continue; + } + // Skip if this exact path is being written to + if added_paths.contains(&old_path_norm) { + continue; + } + // Skip if a parent of this path is being added + // (e.g., the new structure will replace it) + if added_paths + .iter() + .any(|added| old_path_norm.starts_with(added)) + { + continue; + } + // Skip if this path is an ancestor of something being written to + // (e.g., a directory that will contain new files) + if added_paths + .iter() + .any(|added| added.starts_with(&old_path_norm)) + { + continue; + } + // Use normalized path to avoid duplicates from UNC vs regular paths + paths_to_remove.insert(old_path_norm); + } + + // Second pass: only remove top-level orphaned paths. + // Skip any path that is inside another path we're removing, + // since remove_dir_all will handle the contents. + let paths_vec: Vec<_> = paths_to_remove.iter().collect(); + for old_path in &paths_vec { + // Check if this path is inside another path we're removing + let is_nested = paths_vec + .iter() + .any(|other| *other != *old_path && old_path.starts_with(*other)); + + if is_nested { + continue; + } + + log::debug!("Removing orphaned path: {}", old_path.display()); + if old_path.is_dir() { + fs_snapshot.remove_dir(old_path); + } else { + fs_snapshot.remove_file(old_path); + } + } + } + Ok(fs_snapshot) } diff --git a/src/syncback/snapshot.rs b/src/syncback/snapshot.rs index 95f3f547..67cc4d45 100644 --- a/src/syncback/snapshot.rs +++ b/src/syncback/snapshot.rs @@ -21,6 +21,9 @@ pub struct SyncbackData<'sync> { pub(super) old_tree: &'sync RojoTree, pub(super) new_tree: &'sync WeakDom, pub(super) project: &'sync Project, + /// When true, preserves existing file structure and middleware formats. + /// When false (clean mode), all child instances are treated as new. + pub(super) incremental: bool, } pub struct SyncbackSnapshot<'sync> { @@ -35,11 +38,16 @@ impl<'sync> SyncbackSnapshot<'sync> { /// Constructs a SyncbackSnapshot from the provided refs /// while inheriting this snapshot's path and data. This should be used for /// directories. + /// + /// In clean mode (incremental=false), old_ref is ignored and set to None, + /// ensuring fresh filenames and middleware choices for all children. #[inline] pub fn with_joined_path(&self, new_ref: Ref, old_ref: Option) -> anyhow::Result { + // In clean mode, ignore old_ref to ensure fresh structure + let effective_old_ref = if self.data.incremental { old_ref } else { None }; let mut snapshot = Self { data: self.data, - old: old_ref, + old: effective_old_ref, new: new_ref, path: PathBuf::new(), middleware: None, @@ -62,6 +70,9 @@ impl<'sync> SyncbackSnapshot<'sync> { /// /// The actual path of the snapshot is made by getting a file name for the /// snapshot and then appending it to the provided base path. + /// + /// In clean mode (incremental=false), old_ref is ignored and set to None, + /// ensuring fresh filenames and middleware choices for all children. #[inline] pub fn with_base_path( &self, @@ -69,9 +80,11 @@ impl<'sync> SyncbackSnapshot<'sync> { new_ref: Ref, old_ref: Option, ) -> anyhow::Result { + // In clean mode, ignore old_ref to ensure fresh structure + let effective_old_ref = if self.data.incremental { old_ref } else { None }; let mut snapshot = Self { data: self.data, - old: old_ref, + old: effective_old_ref, new: new_ref, path: PathBuf::new(), middleware: None, diff --git a/tests/rojo_test/syncback_util.rs b/tests/rojo_test/syncback_util.rs index b4b54953..07e0089b 100644 --- a/tests/rojo_test/syncback_util.rs +++ b/tests/rojo_test/syncback_util.rs @@ -11,7 +11,7 @@ const INPUT_FILE_PROJECT: &str = "input-project"; const INPUT_FILE_PLACE: &str = "input.rbxl"; const INPUT_FILE_MODEL: &str = "input.rbxm"; -/// Convenience method to run a `rojo syncback` test. +/// Convenience method to run a `rojo syncback` test in clean mode (default). /// /// Test projects should be defined in the `syncback-tests` folder; their filename /// should be given as the first parameter. @@ -19,6 +19,18 @@ const INPUT_FILE_MODEL: &str = "input.rbxm"; /// The passed in callback is where the actual test body should go. Setup and /// cleanup happens automatically. pub fn run_syncback_test(name: &str, callback: impl FnOnce(&Path)) { + run_syncback_test_impl(name, false, callback) +} + +/// Convenience method to run a `rojo syncback` test in incremental mode. +/// +/// Use this for tests that depend on preserving existing file structure and +/// middleware formats. +pub fn run_syncback_test_incremental(name: &str, callback: impl FnOnce(&Path)) { + run_syncback_test_impl(name, true, callback) +} + +fn run_syncback_test_impl(name: &str, incremental: bool, callback: impl FnOnce(&Path)) { let _ = env_logger::try_init(); // let working_dir = get_working_dir_path(); @@ -55,21 +67,27 @@ pub fn run_syncback_test(name: &str, callback: impl FnOnce(&Path)) { .expect("Couldn't copy project to temporary directory"); }; + let mut args = vec![ + "--color", + "never", + "syncback", + project_path.to_str().unwrap(), + "--input", + input_file.to_str().unwrap(), + "--non-interactive", + "--list", + ]; + + if incremental { + args.push("--incremental"); + } + let output = Command::new(ROJO_PATH) // I don't really understand why setting the working directory breaks this, but it does. // It's a bit concerning but I'm more interested in writing tests than debugging it right now. // TODO: Figure out why and fix it. // .current_dir(working_dir) - .args([ - "--color", - "never", - "syncback", - project_path.to_str().unwrap(), - "--input", - input_file.to_str().unwrap(), - "--non-interactive", - "--list", - ]) + .args(args) .output() .expect("Couldn't spawn syncback process"); diff --git a/tests/tests/clean_mode.rs b/tests/tests/clean_mode.rs new file mode 100644 index 00000000..eb16b4d1 --- /dev/null +++ b/tests/tests/clean_mode.rs @@ -0,0 +1,322 @@ +//! Tests for clean mode (default) vs incremental mode behavior. +//! +//! These tests verify that: +//! 1. Clean mode removes orphaned files +//! 2. Clean mode doesn't remove files that are being written to +//! 3. Clean mode doesn't remove parent directories of files being written +//! 4. Incremental mode preserves existing file structure + +use std::path::Path; +use std::process::Command; + +use tempfile::tempdir; + +use crate::rojo_test::io_util::{copy_recursive, ROJO_PATH}; + +const SYNCBACK_TESTS_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/rojo-test/syncback-tests"); + +/// Helper to run syncback and return success status +fn run_syncback(project_path: &Path, input_path: &Path, incremental: bool) -> bool { + let mut args = vec![ + "--color", + "never", + "syncback", + project_path.to_str().unwrap(), + "--input", + input_path.to_str().unwrap(), + "--non-interactive", + ]; + + if incremental { + args.push("--incremental"); + } + + let output = Command::new(ROJO_PATH) + .args(args) + .output() + .expect("Couldn't spawn syncback process"); + + if !output.status.success() { + eprintln!("Syncback failed!"); + eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + } + + output.status.success() +} + +/// Test that clean mode removes orphaned files that don't exist in the input +#[test] +fn clean_mode_removes_orphaned_files() { + let _ = env_logger::try_init(); + + // Use the sync_rules test case and add an orphaned file + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input.rbxm"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + // Create an orphaned file that doesn't match anything in the input + let orphaned_file = project_path.join("src").join("OrphanedFile.luau"); + fs_err::write(&orphaned_file, "-- This is orphaned").expect("Couldn't create orphaned file"); + + assert!( + orphaned_file.exists(), + "Orphaned file should exist before syncback" + ); + + // Run syncback in clean mode (default) + assert!( + run_syncback(&project_path, &input_file, false), + "Syncback should succeed" + ); + + // The orphaned file should be removed in clean mode + // (the input doesn't have "OrphanedFile" script) + assert!( + !orphaned_file.exists(), + "Orphaned file should be removed in clean mode" + ); + + // The expected files should exist + let module_file = project_path.join("src").join("module.luau"); + assert!( + module_file.exists(), + "module.luau should be created by syncback" + ); +} + +/// Test that clean mode doesn't remove files that are being written to +#[test] +fn clean_mode_preserves_written_files() { + let _ = env_logger::try_init(); + + // Use the child_but_not test case + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("child_but_not") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("child_but_not") + .join("input.rbxl"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + // Run syncback in clean mode + assert!( + run_syncback(&project_path, &input_file, false), + "Syncback should succeed" + ); + + // The directories being written to should exist + let only_one_copy = project_path.join("OnlyOneCopy"); + let replicated_storage = project_path.join("ReplicatedStorage"); + + assert!( + only_one_copy.exists(), + "OnlyOneCopy directory should exist (it's being written to)" + ); + assert!( + replicated_storage.exists(), + "ReplicatedStorage directory should exist (it's being written to)" + ); + + // The files inside should exist + assert!( + only_one_copy.join("child_of_one.luau").exists(), + "child_of_one.luau should exist" + ); + assert!( + replicated_storage + .join("child_replicated_storage.luau") + .exists(), + "child_replicated_storage.luau should exist" + ); +} + +/// Test that incremental mode preserves existing file structure +#[test] +fn incremental_mode_preserves_structure() { + let _ = env_logger::try_init(); + + // Use the sync_rules test case - it has custom extensions + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input.rbxm"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + // Verify the custom-extension files exist before syncback + let modulescript = project_path.join("src").join("module.modulescript"); + let text = project_path.join("src").join("text.text"); + assert!( + modulescript.exists(), + "module.modulescript should exist before syncback" + ); + assert!(text.exists(), "text.text should exist before syncback"); + + // Run syncback in incremental mode + assert!( + run_syncback(&project_path, &input_file, true), + "Syncback should succeed" + ); + + // In incremental mode, the custom extensions should be preserved + assert!( + modulescript.exists(), + "module.modulescript should be preserved in incremental mode" + ); + assert!( + text.exists(), + "text.text should be preserved in incremental mode" + ); +} + +/// Test that clean mode uses fresh file extensions (not preserving old ones) +#[test] +fn clean_mode_uses_fresh_extensions() { + let _ = env_logger::try_init(); + + // Use the sync_rules test case - it has custom extensions + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("sync_rules") + .join("input.rbxm"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + // Run syncback in clean mode (default) + assert!( + run_syncback(&project_path, &input_file, false), + "Syncback should succeed" + ); + + // In clean mode, the files should use standard extensions + let modulescript_old = project_path.join("src").join("module.modulescript"); + let text_old = project_path.join("src").join("text.text"); + let module_new = project_path.join("src").join("module.luau"); + let text_new = project_path.join("src").join("text.txt"); + + // Old extensions should NOT exist + assert!( + !modulescript_old.exists(), + "module.modulescript should NOT exist in clean mode" + ); + assert!( + !text_old.exists(), + "text.text should NOT exist in clean mode" + ); + + // New standard extensions SHOULD exist + assert!( + module_new.exists(), + "module.luau should exist in clean mode" + ); + assert!(text_new.exists(), "text.txt should exist in clean mode"); +} + +/// Test that clean mode doesn't remove the project file +#[test] +fn clean_mode_preserves_project_file() { + let _ = env_logger::try_init(); + + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("ignore_trees_adding") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("ignore_trees_adding") + .join("input.rbxl"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + let project_file = project_path.join("default.project.json5"); + assert!( + project_file.exists(), + "Project file should exist before syncback" + ); + + // Run syncback in clean mode + assert!( + run_syncback(&project_path, &input_file, false), + "Syncback should succeed" + ); + + // Project file should still exist + assert!( + project_file.exists(), + "Project file should be preserved in clean mode" + ); +} + +/// Test that running clean mode twice produces identical results +#[test] +fn clean_mode_is_idempotent() { + let _ = env_logger::try_init(); + + let source_path = Path::new(SYNCBACK_TESTS_PATH) + .join("child_but_not") + .join("input-project"); + let input_file = Path::new(SYNCBACK_TESTS_PATH) + .join("child_but_not") + .join("input.rbxl"); + + let test_dir = tempdir().expect("Couldn't create temporary directory"); + let project_path = test_dir.path().join("test_project"); + + fs_err::create_dir(&project_path).expect("Couldn't create project directory"); + copy_recursive(&source_path, &project_path).expect("Couldn't copy project"); + + // Run syncback twice in clean mode + assert!( + run_syncback(&project_path, &input_file, false), + "First syncback should succeed" + ); + + // Capture state after first run + let file1 = project_path.join("OnlyOneCopy").join("child_of_one.luau"); + let content1 = fs_err::read_to_string(&file1).unwrap(); + + assert!( + run_syncback(&project_path, &input_file, false), + "Second syncback should succeed" + ); + + // File should still exist with same content + assert!( + file1.exists(), + "File should still exist after second syncback" + ); + let content2 = fs_err::read_to_string(&file1).unwrap(); + assert_eq!( + content1, content2, + "File content should be identical after second syncback" + ); +} diff --git a/tests/tests/mod.rs b/tests/tests/mod.rs index bc710016..b9734569 100644 --- a/tests/tests/mod.rs +++ b/tests/tests/mod.rs @@ -1,3 +1,4 @@ mod build; +mod clean_mode; mod serve; mod syncback; diff --git a/tests/tests/syncback.rs b/tests/tests/syncback.rs index e86aec1e..437b2f0b 100644 --- a/tests/tests/syncback.rs +++ b/tests/tests/syncback.rs @@ -2,7 +2,9 @@ use std::ffi::OsStr; use insta::assert_snapshot; -use crate::rojo_test::syncback_util::{run_syncback_test, snapshot_rbxm}; +use crate::rojo_test::syncback_util::{ + run_syncback_test, run_syncback_test_incremental, snapshot_rbxm, +}; macro_rules! syncback_tests { ($($test_name:ident => $list:expr$(,)?),*) => {$( @@ -28,27 +30,47 @@ macro_rules! syncback_tests { )*}; } +/// Tests that need to run in incremental mode (preserving existing structure) +macro_rules! syncback_tests_incremental { + ($($test_name:ident => $list:expr$(,)?),*) => {$( + #[test] + fn $test_name() { + run_syncback_test_incremental(stringify!($test_name), |path| { + for name in $list { + let snapshot_name = format!(concat!(stringify!($test_name), "-{}"), name); + let new = path.join::<&str>(name); + if !new.exists() { + panic!("the path stub '{}' does not exist after syncback runs. consider double checking for typos.", name); + } + if let Some("rbxm") = new.extension().and_then(OsStr::to_str) { + let content = fs_err::read(new).unwrap(); + snapshot_rbxm(&snapshot_name, content, name); + } else { + let content = fs_err::read_to_string(new).unwrap(); + assert_snapshot!(snapshot_name, content, name); + } + } + }); + } + )*}; +} + +// Tests that run in clean mode (default) syncback_tests! { // Ensures that there's only one copy written to disk if navigating a // project file might yield two copies child_but_not => ["OnlyOneCopy/child_of_one.luau", "ReplicatedStorage/child_replicated_storage.luau"], - // Ensures that syncback works with CSVs - csv => ["src/csv_init/init.csv", "src/csv.csv"], // Ensures that if a RojoId is duplicated somewhere in the project, it's // rewritten rather than synced back as a conflict duplicate_rojo_id => ["container.model.json5"], // Ensures that the `ignorePaths` setting works for additions (new files get .json5) ignore_paths_adding => ["src/int_value.model.json5", "src/subfolder/string_value.txt"], - // Ensures that the `ignorePaths` setting works for `init` files - ignore_paths_init => ["src/non-init.luau", "src/init-file/init.luau"], // Ensures that the `ignorePaths` setting works for removals (new file gets .json5) ignore_paths_removing => ["src/Message.model.json5"], // Ensures that `ignoreTrees` works for additions ignore_trees_adding => [], // Ensures that `ignoreTrees` works for removals ignore_trees_removing => [], - // Ensures that all of the JSON middlewares are handled as expected - json_middlewares => ["src/dir_with_meta/init.meta.json5", "src/model_json.model.json5", "src/project_json.project.json5"], // Ensures projects that refer to other projects work as expected nested_projects => ["nested.project.json5", "string_value.txt"], // Ensures files that are ignored by nested projects are picked up if @@ -62,28 +84,37 @@ syncback_tests! { project_reserialize => ["attribute_mismatch.luau", "property_mismatch.project.json5"], // Confirms that Instances that cannot serialize as directories serialize as rbxms rbxm_fallback => [], - // Ensures that ref properties are linked properly on the file system - ref_properties => ["src/pointer.model.json5", "src/target.model.json5"], // Ensures that ref properties are linked when no attributes are manually // set in the DataModel (new files get .json5) ref_properties_blank => ["src/pointer.model.json5", "src/target.txt"], - // Ensures that if there is a conflict in RojoRefs, one of them is rewritten - ref_properties_conflict => ["src/Pointer_2.model.json5", "src/Target_2.model.json5"], // Ensures that having multiple pointers that are aimed at the same target doesn't trigger ref rewrites. ref_properties_duplicate => [], // Ensures that ref properties that point to nothing after the prune both // do not leave any trace of themselves (new files get .json5) ref_properties_pruned => ["src/Pointer1.model.json5", "src/Pointer2.model.json5", "src/Pointer3.model.json5"], - // Ensures that the old middleware is respected during syncback - respect_old_middleware => ["default.project.json5", "src/model_json.model.json5", "src/rbxm.rbxm", "src/rbxmx.rbxmx"], // Ensures that the `$schema` field roundtrips with syncback schema_roundtrip => ["default.project.json5", "src/model.model.json5", "src/init/init.meta.json5", "src/adjacent.meta.json5"], // Ensures that StringValues inside project files are written to the // project file, but only if they don't have `$path` set string_value_project => ["default.project.json5"], - // Ensures that sync rules are respected. This is really just a test to - // ensure it uses the old path when possible, but we want the coverage. - sync_rules => ["src/module.modulescript", "src/text.text"], // Ensures that the `syncUnscriptable` setting works unscriptable_properties => ["default.project.json5"], } + +// Tests that run in incremental mode (preserving existing structure) +syncback_tests_incremental! { + // Ensures that syncback works with CSVs (preserves existing CSV structure) + csv => ["src/csv_init/init.csv", "src/csv.csv"], + // Ensures that the `ignorePaths` setting works for `init` files + ignore_paths_init => ["src/non-init.luau", "src/init-file/init.luau"], + // Ensures that all of the JSON middlewares are handled as expected + json_middlewares => ["src/dir_with_meta/init.meta.json5", "src/model_json.model.json5", "src/project_json.project.json5"], + // Ensures that ref properties are linked properly on the file system + ref_properties => ["src/pointer.model.json5", "src/target.model.json5"], + // Ensures that if there is a conflict in RojoRefs, one of them is rewritten + ref_properties_conflict => ["src/Pointer_2.model.json5", "src/Target_2.model.json5"], + // Ensures that the old middleware is respected during syncback (incremental mode only) + respect_old_middleware => ["default.project.json5", "src/model_json.model.json5", "src/rbxm.rbxm", "src/rbxmx.rbxmx"], + // Ensures that sync rules are respected (incremental mode only - uses old paths when possible) + sync_rules => ["src/module.modulescript", "src/text.text"], +}