diff --git a/.cursor/plans/script_naming_refactor_424e3277.plan.md b/.cursor/plans/script_naming_refactor_424e3277.plan.md
new file mode 100644
index 00000000..4ac50ca8
--- /dev/null
+++ b/.cursor/plans/script_naming_refactor_424e3277.plan.md
@@ -0,0 +1,272 @@
+---
+name: Script Naming Refactor
+overview: Remove the `emitLegacyScripts` option entirely and implement a new, deterministic script naming convention where each script type has exactly one file extension. Add period encoding to the Windows encoding system and default that setting to true.
+todos:
+ - id: path-encoding
+ content: Add %DOT% to CHAR_ENCODINGS, add %% escape for literal %, default encoding to true
+ status: completed
+ - id: middleware-enum
+ content: Add LocalScript, LegacyScript, LocalScriptDir, LegacyScriptDir middleware variants
+ status: completed
+ - id: script-type
+ content: Simplify ScriptType enum and update snapshot_lua logic
+ status: completed
+ - id: sync-rules
+ content: Add .local.lua/.luau and .legacy.lua/.luau sync rules and init paths
+ status: completed
+ - id: remove-emit-legacy
+ content: Remove emitLegacyScripts from Project, InstanceContext, and all usages
+ status: completed
+ - id: syncback-runcontext
+ content: Update get_best_middleware to check RunContext for Script class
+ status: completed
+ - id: file-extensions
+ content: Update extension_for_middleware with new script types
+ status: completed
+ - id: meta-suffixes
+ content: Add .local and .legacy to meta file suffix stripping
+ status: completed
+ - id: web-api
+ content: Update web/api.rs for new script naming conventions
+ status: completed
+ - id: update-tests
+ content: Update snapshot files and test cases
+ status: completed
+isProject: false
+---
+
+# Script Naming Convention Refactor
+
+## New File Extension Mapping
+
+
+| Script Type | File Extension |
+| -------------------------- | -------------- |
+| Script (RunContext.Server) | `.server.luau` |
+| Script (RunContext.Client) | `.client.luau` |
+| Script (RunContext.Plugin) | `.plugin.luau` |
+| Script (RunContext.Legacy) | `.legacy.luau` |
+| LocalScript | `.local.luau` |
+| ModuleScript | `.luau` |
+
+
+## Key Changes
+
+### 1. Add Period Encoding, Percent Escaping, and Default to True
+
+Update `[src/path_encoding.rs](src/path_encoding.rs)`:
+
+1. Add `(".", "%DOT%")` to `CHAR_ENCODINGS`
+2. Add `%%` as escape sequence for literal `%` (encode first, decode last)
+
+```rust
+const CHAR_ENCODINGS: &[(&str, &str)] = &[
+ (".", "%DOT%"), // NEW
+ ("<", "%LT%"),
+ // ... rest unchanged
+];
+
+pub fn encode_path_name(name: &str) -> String {
+ // ... handle leading/trailing spaces ...
+
+ // FIRST: Escape existing % as %% (before adding new % signs)
+ let mut encoded_middle = middle.replace("%", "%%");
+
+ // THEN: Encode special characters (which add new % signs)
+ for (char, encoded) in CHAR_ENCODINGS {
+ encoded_middle = encoded_middle.replace(char, encoded);
+ }
+ // ...
+}
+
+pub fn decode_path_name(name: &str) -> String {
+ // ...
+ // FIRST: Decode %NAME% patterns
+ for (char, encoded) in CHAR_ENCODINGS {
+ result = result.replace(encoded, char);
+ }
+
+ // LAST: Unescape %% back to %
+ result = result.replace("%%", "%");
+ // ...
+}
+```
+
+This ensures round-tripping works correctly:
+
+- `My%DOT%Thing` encodes to `My%%DOT%%Thing`, decodes back to `My%DOT%Thing`
+- `My.Thing` encodes to `My%DOT%Thing`, decodes back to `My.Thing`
+
+Change the default for `encode_windows_invalid_chars` to `true` in `[src/syncback/mod.rs](src/syncback/mod.rs)` `SyncbackRules::encode_windows_invalid_chars()`:
+
+```rust
+pub fn encode_windows_invalid_chars(&self) -> bool {
+ self.encode_windows_invalid_chars.unwrap_or(true) // Changed from false to true
+}
+```
+
+Also update the `decode_windows_invalid_chars` logic in `[src/serve_session.rs](src/serve_session.rs)` to default to `true`.
+
+### 2. Add New Middleware Variants
+
+In `[src/snapshot_middleware/mod.rs](src/snapshot_middleware/mod.rs)`, add new variants:
+
+- `LocalScript` - for `.local.lua/.luau` files (LocalScript class)
+- `LegacyScript` - for `.legacy.lua/.luau` files (Script with RunContext.Legacy)
+- `LocalScriptDir` - for `init.local.lua/.luau` directories
+- `LegacyScriptDir` - for `init.legacy.lua/.luau` directories
+
+### 3. Update ScriptType Enum
+
+In `[src/snapshot_middleware/lua.rs](src/snapshot_middleware/lua.rs)`, simplify to:
+
+```rust
+pub enum ScriptType {
+ Server, // Script + RunContext.Server
+ Client, // Script + RunContext.Client
+ Plugin, // Script + RunContext.Plugin
+ Legacy, // Script + RunContext.Legacy
+ Local, // LocalScript
+ Module, // ModuleScript
+}
+```
+
+Remove `LegacyServer`, `LegacyClient`, `RunContextServer`, `RunContextClient` - these were for the dual-mode system.
+
+### 4. Update Default Sync Rules
+
+In `[src/snapshot_middleware/mod.rs](src/snapshot_middleware/mod.rs)` `default_sync_rules()`:
+
+```rust
+vec![
+ sync_rule!("*.server.lua", ServerScript, ".server.lua"),
+ sync_rule!("*.server.luau", ServerScript, ".server.luau"),
+ sync_rule!("*.client.lua", ClientScript, ".client.lua"),
+ sync_rule!("*.client.luau", ClientScript, ".client.luau"),
+ sync_rule!("*.plugin.lua", PluginScript, ".plugin.lua"),
+ sync_rule!("*.plugin.luau", PluginScript, ".plugin.luau"),
+ sync_rule!("*.legacy.lua", LegacyScript, ".legacy.lua"), // NEW
+ sync_rule!("*.legacy.luau", LegacyScript, ".legacy.luau"), // NEW
+ sync_rule!("*.local.lua", LocalScript, ".local.lua"), // NEW
+ sync_rule!("*.local.luau", LocalScript, ".local.luau"), // NEW
+ sync_rule!("*.{lua,luau}", ModuleScript), // Unchanged - catches remaining
+ // ... rest unchanged
+]
+```
+
+### 5. Update Init Path Handling
+
+In `[src/snapshot_middleware/mod.rs](src/snapshot_middleware/mod.rs)` `get_dir_middleware()`:
+
+```rust
+vec![
+ (Middleware::ModuleScriptDir, "init.luau"),
+ (Middleware::ModuleScriptDir, "init.lua"),
+ (Middleware::ServerScriptDir, "init.server.luau"),
+ (Middleware::ServerScriptDir, "init.server.lua"),
+ (Middleware::ClientScriptDir, "init.client.luau"),
+ (Middleware::ClientScriptDir, "init.client.lua"),
+ (Middleware::LocalScriptDir, "init.local.luau"), // NEW
+ (Middleware::LocalScriptDir, "init.local.lua"), // NEW
+ (Middleware::LegacyScriptDir, "init.legacy.luau"), // NEW
+ (Middleware::LegacyScriptDir, "init.legacy.lua"), // NEW
+ (Middleware::CsvDir, "init.csv"),
+]
+```
+
+### 6. Remove emitLegacyScripts Entirely
+
+Files to modify:
+
+- `[src/project.rs](src/project.rs)` - Remove `emit_legacy_scripts` field from `Project`
+- `[src/snapshot/metadata.rs](src/snapshot/metadata.rs)` - Remove from `InstanceContext`, remove `with_emit_legacy_scripts()`
+- `[src/snapshot_middleware/util.rs](src/snapshot_middleware/util.rs)` - Remove `emit_legacy_scripts_default()`
+- `[src/snapshot_middleware/project.rs](src/snapshot_middleware/project.rs)` - Remove `set_emit_legacy_scripts()` call
+- `[src/serve_session.rs](src/serve_session.rs)` - Remove emit_legacy_scripts usage
+
+### 7. Update Syncback Middleware Selection
+
+In `[src/syncback/mod.rs](src/syncback/mod.rs)` `get_best_middleware()`:
+
+```rust
+middleware = match inst.class.as_str() {
+ "Script" => {
+ // Check RunContext to determine which middleware
+ match inst.properties.get(&ustr("RunContext")) {
+ Some(Variant::Enum(e)) => match e.to_u32() {
+ // Legacy = 0, Server = 1, Client = 2, Plugin = 3
+ 0 => Middleware::LegacyScript,
+ 1 => Middleware::ServerScript,
+ 2 => Middleware::ClientScript,
+ 3 => Middleware::PluginScript,
+ _ => Middleware::ServerScript, // fallback
+ },
+ _ => Middleware::LegacyScript, // No RunContext = Legacy behavior
+ }
+ }
+ "LocalScript" => Middleware::LocalScript,
+ "ModuleScript" => Middleware::ModuleScript,
+ // ... rest unchanged
+}
+```
+
+### 8. Update File Name Extensions
+
+In `[src/syncback/file_names.rs](src/syncback/file_names.rs)` `extension_for_middleware()`:
+
+```rust
+Middleware::ServerScript => "server.luau",
+Middleware::ClientScript => "client.luau",
+Middleware::PluginScript => "plugin.luau",
+Middleware::LegacyScript => "legacy.luau", // NEW
+Middleware::LocalScript => "local.luau", // NEW
+Middleware::ModuleScript => "luau",
+```
+
+### 9. Update Meta File Suffix Stripping
+
+In `[src/snapshot_middleware/lua.rs](src/snapshot_middleware/lua.rs)` `syncback_lua()` and `[src/snapshot_middleware/meta_file.rs](src/snapshot_middleware/meta_file.rs)`:
+
+Add `.local` and `.legacy` to the suffix stripping logic.
+
+### 10. Update Web API (Two-Way Sync)
+
+In `[src/web/api.rs](src/web/api.rs)`:
+
+- Update script handling to use new naming conventions
+- Remove all `emit_legacy_scripts` references and conditional logic
+
+### 11. Update Test Files
+
+- Delete test projects in `rojo-test/build-tests/nested_runcontext/` that test emitLegacyScripts
+- Update all snapshot files (`.snap`) that contain `emit_legacy_scripts: true/false`
+- Update test cases in `src/snapshot_middleware/lua.rs` to use the new single behavior
+
+### 12. Clean Up Comments
+
+Remove all legacy references and comments about the old dual-mode system throughout the codebase.
+
+## Files to Modify (Summary)
+
+Core changes:
+
+- `src/path_encoding.rs` - Add period encoding (`%DOT%`) and `%%` escape for literal `%`
+- `src/syncback/mod.rs` - Default encode_windows_invalid_chars to true, update middleware selection
+- `src/serve_session.rs` - Default decode_windows_invalid_chars to true
+- `src/snapshot_middleware/mod.rs` - Add middleware variants, update sync rules
+- `src/snapshot_middleware/lua.rs` - Simplify ScriptType, update logic
+- `src/snapshot/metadata.rs` - Remove emit_legacy_scripts
+- `src/project.rs` - Remove emit_legacy_scripts field
+- `src/syncback/mod.rs` - Update middleware selection with RunContext check
+- `src/syncback/file_names.rs` - Add new extensions
+- `src/snapshot_middleware/util.rs` - Remove emit_legacy_scripts_default
+- `src/snapshot_middleware/project.rs` - Remove emit_legacy_scripts propagation
+- `src/serve_session.rs` - Remove emit_legacy_scripts usage
+- `src/web/api.rs` - Update for new conventions
+- `src/snapshot_middleware/meta_file.rs` - Add new suffix handling
+
+Test updates:
+
+- ~40 snapshot files need `emit_legacy_scripts` line removed and `decode_windows_invalid_chars` changed to `true`
+- Test functions need updating to not pass emit_legacy_scripts parameter
+
diff --git a/plugin/src/ChangeBatcher/encodeInstance.lua b/plugin/src/ChangeBatcher/encodeInstance.lua
index b238414c..54f7f8ce 100644
--- a/plugin/src/ChangeBatcher/encodeInstance.lua
+++ b/plugin/src/ChangeBatcher/encodeInstance.lua
@@ -8,6 +8,12 @@
- All other instances -> .model.json files or directories
Children are recursively encoded and included.
+
+ DUPLICATE HANDLING:
+ - Instances with duplicate-named siblings at ANY level of the path are skipped
+ - This matches the behavior in ref_properties.rs::is_path_unique
+ - A path is ambiguous if duplicates exist anywhere from the instance to the root
+ - A warning is logged when duplicates are encountered
]]
local Packages = script.Parent.Parent.Parent.Packages
@@ -41,9 +47,65 @@ local DIRECTORY_CLASSES = {
AdGui = true,
}
+-- Finds duplicate names among a list of instances
+-- Returns a set of names that appear more than once
+local function findDuplicateNames(children: { Instance }): { [string]: boolean }
+ local nameCounts: { [string]: number } = {}
+ local duplicates: { [string]: boolean } = {}
+
+ for _, child in children do
+ local name = child.Name
+ nameCounts[name] = (nameCounts[name] or 0) + 1
+ if nameCounts[name] > 1 then
+ duplicates[name] = true
+ end
+ end
+
+ return duplicates
+end
+
+-- Checks if an instance has duplicate-named siblings
+local function hasDuplicateSiblings(instance: Instance): boolean
+ local parent = instance.Parent
+ if not parent then
+ return false
+ end
+
+ local siblings = parent:GetChildren()
+ local duplicates = findDuplicateNames(siblings)
+ return duplicates[instance.Name] == true
+end
+
+-- Checks if the path to an instance is unique by walking up the tree.
+-- Returns false if ANY ancestor (including the instance itself) has duplicate siblings.
+-- This matches the behavior of is_path_unique in ref_properties.rs
+local function isPathUnique(instance: Instance): boolean
+ local current = instance
+
+ while current do
+ -- Check if current has duplicate-named siblings
+ if hasDuplicateSiblings(current) then
+ return false
+ end
+
+ -- Move up to parent
+ current = current.Parent
+
+ -- Stop at game (DataModel) - we've reached the root
+ if current and current == game then
+ break
+ end
+ end
+
+ return true
+end
+
-- Forward declaration for recursion
local encodeInstance
+-- Track skipped duplicates for logging
+local skippedDuplicateCount = 0
+
-- Encode Attributes if present on any instance
local function encodeAttributes(instance, properties)
-- Try to get Attributes - this works for all instance types
@@ -64,7 +126,21 @@ local function encodeAttributes(instance, properties)
end
end
-encodeInstance = function(instance, parentId)
+encodeInstance = function(instance, parentId, _skipPathCheck)
+ -- Check if the entire path to this instance is unique (unless we're in a recursive call)
+ -- For top-level instances, we check the entire ancestor chain for duplicates
+ -- For children, the parent already verified the path is unique up to that point,
+ -- so we only need to check siblings at each level (done in the recursion below)
+ if not _skipPathCheck and not isPathUnique(instance) then
+ skippedDuplicateCount += 1
+ Log.warn(
+ "Skipped instance '{}' ({}) - path contains duplicate-named siblings (cannot reliably sync)",
+ instance:GetFullName(),
+ instance.ClassName
+ )
+ return nil
+ end
+
local properties = {}
-- Always try to encode Attributes for any instance type
@@ -111,10 +187,25 @@ encodeInstance = function(instance, parentId)
end
-- For directory classes (Folder, Configuration, etc.), we only encode Attributes (done above)
- -- Recursively encode children
+ -- Recursively encode children, skipping those with duplicate names
local children = {}
- for _, child in ipairs(instance:GetChildren()) do
- local encodedChild = encodeInstance(child, nil) -- parentId will be resolved server-side
+ local childInstances = instance:GetChildren()
+ local childDuplicates = findDuplicateNames(childInstances)
+
+ for _, child in ipairs(childInstances) do
+ -- Skip children with duplicate names
+ if childDuplicates[child.Name] then
+ skippedDuplicateCount += 1
+ Log.warn(
+ "Skipped child instance '{}' ({}) - has duplicate-named siblings (cannot reliably sync)",
+ child:GetFullName(),
+ child.ClassName
+ )
+ continue
+ end
+
+ -- Pass true to skip duplicate check since we already checked above
+ local encodedChild = encodeInstance(child, nil, true)
if encodedChild then
table.insert(children, encodedChild)
end
@@ -129,4 +220,20 @@ encodeInstance = function(instance, parentId)
}
end
-return encodeInstance
+-- Wrapper function that resets the duplicate count and returns the final count
+local function encodeInstanceWithDuplicateTracking(instance, parentId)
+ skippedDuplicateCount = 0
+ local result = encodeInstance(instance, parentId, false)
+
+ -- Log summary if any duplicates were skipped
+ if skippedDuplicateCount > 0 then
+ Log.warn(
+ "Skipped {} location(s) with duplicate-named siblings during pull (cannot reliably sync)",
+ skippedDuplicateCount
+ )
+ end
+
+ return result, skippedDuplicateCount
+end
+
+return encodeInstanceWithDuplicateTracking
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__client_in_folder.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__client_in_folder.snap
index 3953fcb6..b0c562c2 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__client_in_folder.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__client_in_folder.snap
@@ -7,9 +7,10 @@ expression: contents
client_in_folder
- -
+
-
localScript
+ 2
-- This is a Lua client script
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__client_init.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__client_init.snap
index f3391ebe..d8bfe9ed 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__client_init.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__client_init.snap
@@ -3,9 +3,10 @@ source: tests/tests/build.rs
expression: contents
---
- -
+
-
client_init
+ 2
return "From folder/init.client.lua"
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__issue_546.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__issue_546.snap
index 3107344a..a4a38afc 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__issue_546.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__issue_546.snap
@@ -1,13 +1,13 @@
---
source: tests/tests/build.rs
-assertion_line: 98
expression: contents
---
- -
+
-
issue_546
true
+ 2
print("Hello, world!")
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__script_meta_disabled.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__script_meta_disabled.snap
index 28354bd6..153f81e2 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__script_meta_disabled.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__script_meta_disabled.snap
@@ -11,7 +11,7 @@ expression: contents
hello
true
- 0
+ 1
-- This script should be marked 'Disabled'
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__server_in_folder.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__server_in_folder.snap
index c9806d00..6edb857d 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__server_in_folder.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__server_in_folder.snap
@@ -10,7 +10,7 @@ expression: contents
-
serverScript
- 0
+ 1
-- This is a Lua server script
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__server_init.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__server_init.snap
index 2bbda073..2f40c2c9 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__server_init.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__server_init.snap
@@ -6,7 +6,7 @@ expression: contents
-
server_init
- 0
+ 1
return "From folder/init.server.luau"
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__sync_rule_complex.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__sync_rule_complex.snap
index e38050d8..c6988e66 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__sync_rule_complex.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__sync_rule_complex.snap
@@ -1,6 +1,5 @@
---
source: tests/tests/build.rs
-assertion_line: 104
expression: contents
---
@@ -11,13 +10,14 @@ expression: contents
-
bar
- 0
+ 1
-- Hello, from bar (a Script)!
- -
+
-
baz
+ 2
-- Hello, from baz (a LocalScript)!
diff --git a/rojo-test/build-tests/nested_runcontext/default.project.json b/rojo-test/build-tests/nested_runcontext/default.project.json
deleted file mode 100644
index 5327be9a..00000000
--- a/rojo-test/build-tests/nested_runcontext/default.project.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "nested_runcontext",
- "emitLegacyScripts": false,
- "tree": {
- "$className": "Folder",
- "folder1": {
- "$path": "folder1"
- },
- "folder2": {
- "$path": "folder2"
- }
- }
-}
diff --git a/rojo-test/build-tests/nested_runcontext/folder1/test.server.luau b/rojo-test/build-tests/nested_runcontext/folder1/test.server.luau
deleted file mode 100644
index e69de29b..00000000
diff --git a/rojo-test/build-tests/nested_runcontext/folder2/default.project.json b/rojo-test/build-tests/nested_runcontext/folder2/default.project.json
deleted file mode 100644
index 67feff44..00000000
--- a/rojo-test/build-tests/nested_runcontext/folder2/default.project.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "nested_runcontext",
- "emitLegacyScripts": true,
- "tree": {
- "$path": "folder3"
- }
-}
diff --git a/rojo-test/build-tests/nested_runcontext/folder2/folder3/test.server.luau b/rojo-test/build-tests/nested_runcontext/folder2/folder3/test.server.luau
deleted file mode 100644
index e69de29b..00000000
diff --git a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all-2.snap b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all-2.snap
index 49d8148b..d344efbd 100644
--- a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all-2.snap
+++ b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all-2.snap
@@ -1,7 +1,6 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
-
---
instances:
id-2:
@@ -25,7 +24,7 @@ instances:
Parent: id-2
Properties:
RunContext:
- Enum: 0
+ Enum: 1
Source:
String: "-- Hello, from bar!"
id-4:
@@ -41,4 +40,3 @@ instances:
String: Updated foo!
messageCursor: 1
sessionId: id-1
-
diff --git a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all.snap b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all.snap
index 62e5ceef..2671f94b 100644
--- a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all.snap
+++ b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__scripts_all.snap
@@ -1,7 +1,6 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
-
---
instances:
id-2:
@@ -25,7 +24,7 @@ instances:
Parent: id-2
Properties:
RunContext:
- Enum: 0
+ Enum: 1
Source:
String: "-- Hello, from bar!"
id-4:
@@ -41,4 +40,3 @@ instances:
String: "-- Hello, from foo!"
messageCursor: 0
sessionId: id-1
-
diff --git a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__sync_rule_complex_all.snap b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__sync_rule_complex_all.snap
index 80cd76f6..87cb8b5e 100644
--- a/rojo-test/serve-test-snapshots/end_to_end__tests__serve__sync_rule_complex_all.snap
+++ b/rojo-test/serve-test-snapshots/end_to_end__tests__serve__sync_rule_complex_all.snap
@@ -1,6 +1,5 @@
---
source: tests/tests/serve.rs
-assertion_line: 284
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
---
instances:
@@ -28,18 +27,20 @@ instances:
Parent: id-2
Properties:
RunContext:
- Enum: 0
+ Enum: 1
Source:
String: "-- Hello, from bar (a Script)!"
id-4:
Children: []
- ClassName: LocalScript
+ ClassName: Script
Id: id-4
Metadata:
ignoreUnknownInstances: false
Name: baz
Parent: id-2
Properties:
+ RunContext:
+ Enum: 2
Source:
String: "-- Hello, from baz (a LocalScript)!"
id-5:
@@ -77,4 +78,3 @@ instances:
String: "Hello, from qux (a .rojo file that's turned into a StringValue)!"
messageCursor: 0
sessionId: id-1
-
diff --git a/src/path_encoding.rs b/src/path_encoding.rs
index 34908d55..1c8d9871 100644
--- a/src/path_encoding.rs
+++ b/src/path_encoding.rs
@@ -1,15 +1,20 @@
-//! Encoding and decoding utilities for Windows-invalid filename characters.
+//! Encoding and decoding utilities for special filename characters.
//!
-//! Windows does not allow the following characters in file names:
-//! < > : " / \ | ? *
+//! This module encodes characters that are problematic in file names:
+//! - Windows-invalid characters: < > : " / \ | ? *
+//! - Periods (.) to prevent conflicts with file extension parsing
+//! - Leading/trailing spaces
+//! - Literal percent signs (%) to allow round-tripping
//!
-//! Windows also does not allow leading/trailing spaces in file names.
-//!
-//! This module provides functions to encode these characters using a %NAME%
-//! format (e.g., `<` becomes `%LT%`) and decode them back.
+//! Characters are encoded using a %NAME% format (e.g., `<` becomes `%LT%`,
+//! `.` becomes `%DOT%`). Literal `%` is escaped as `%%` (like printf).
-/// Mapping of Windows-invalid characters to their encoded representations.
+use std::collections::HashMap;
+use std::sync::OnceLock;
+
+/// Mapping of special characters to their encoded representations.
const CHAR_ENCODINGS: &[(&str, &str)] = &[
+ (".", "%DOT%"),
("<", "%LT%"),
(">", "%GT%"),
(":", "%COLON%"),
@@ -21,13 +26,27 @@ const CHAR_ENCODINGS: &[(&str, &str)] = &[
("*", "%STAR%"),
];
+/// Returns a map from encoded pattern (e.g., "DOT") to decoded char (e.g., ".").
+fn get_decode_map() -> &'static HashMap<&'static str, &'static str> {
+ static DECODE_MAP: OnceLock> = OnceLock::new();
+ DECODE_MAP.get_or_init(|| {
+ let mut map = HashMap::new();
+ for (char, encoded) in CHAR_ENCODINGS {
+ // Strip the leading and trailing % from the pattern
+ let pattern = &encoded[1..encoded.len() - 1];
+ map.insert(pattern, *char);
+ }
+ map
+ })
+}
+
const SPACE_ENCODING: &str = "%SPACE%";
-/// Encodes Windows-invalid characters in a file name.
+/// Encodes special characters in a file name.
///
-/// This replaces characters like `<`, `>`, `:`, `"`, `/`, `\`, `|`, `?`, `*`
-/// with their encoded representations like `%LT%`, `%GT%`, etc.
-/// Leading and trailing spaces are encoded as `%SPACE%`.
+/// This replaces characters like `.`, `<`, `>`, `:`, `"`, `/`, `\`, `|`, `?`, `*`
+/// with their encoded representations like `%DOT%`, `%LT%`, `%GT%`, etc.
+/// Literal `%` is escaped as `%%`. Leading and trailing spaces are encoded as `%SPACE%`.
pub fn encode_path_name(name: &str) -> String {
// Count leading and trailing spaces first (before any modifications)
let leading_spaces = name.len() - name.trim_start_matches(' ').len();
@@ -36,8 +55,10 @@ pub fn encode_path_name(name: &str) -> String {
// Get the middle part (without leading/trailing spaces)
let middle = &name[leading_spaces..name.len() - trailing_spaces];
- // Encode invalid characters in the middle part
- let mut encoded_middle = middle.to_string();
+ // First: escape % as %% (before adding new % signs from encodings)
+ let mut encoded_middle = middle.replace('%', "%%");
+
+ // Then: encode special characters
for (char, encoded) in CHAR_ENCODINGS {
encoded_middle = encoded_middle.replace(char, encoded);
}
@@ -49,11 +70,13 @@ pub fn encode_path_name(name: &str) -> String {
format!("{}{}{}", encoded_prefix, encoded_middle, encoded_suffix)
}
-/// Decodes encoded Windows-invalid characters back to their original form.
+/// Decodes encoded special characters back to their original form.
///
-/// This replaces encoded representations like `%LT%`, `%GT%`, etc. back
-/// to their original characters `<`, `>`, etc.
-/// `%SPACE%` at the start/end is decoded back to spaces.
+/// This replaces encoded representations like `%DOT%`, `%LT%`, `%GT%`, etc.
+/// back to their original characters `.`, `<`, `>`, etc.
+/// `%%` is decoded to literal `%`. `%SPACE%` at the start/end is decoded back to spaces.
+///
+/// Uses a proper left-to-right parser to correctly handle `%%` escaping.
pub fn decode_path_name(name: &str) -> String {
let mut result = name.to_string();
@@ -71,15 +94,83 @@ pub fn decode_path_name(name: &str) -> String {
result = result[..result.len() - SPACE_ENCODING.len()].to_string();
}
- // Decode invalid characters in the middle
- for (char, encoded) in CHAR_ENCODINGS {
- result = result.replace(encoded, char);
- }
+ // Parse and decode the middle part
+ let decoded_middle = decode_patterns(&result);
// Rebuild with actual spaces
let prefix = " ".repeat(leading_spaces);
let suffix = " ".repeat(trailing_spaces);
- format!("{}{}{}", prefix, result, suffix)
+ format!("{}{}{}", prefix, decoded_middle, suffix)
+}
+
+/// Parses an encoded string left-to-right, properly handling `%%` escapes and `%NAME%` patterns.
+fn decode_patterns(input: &str) -> String {
+ let decode_map = get_decode_map();
+ let chars: Vec = input.chars().collect();
+ let mut output = String::with_capacity(input.len());
+ let mut i = 0;
+
+ while i < chars.len() {
+ if chars[i] == '%' {
+ // Check for %% (escaped percent)
+ if i + 1 < chars.len() && chars[i + 1] == '%' {
+ output.push('%');
+ i += 2;
+ continue;
+ }
+
+ // Try to match a %NAME% pattern
+ if let Some((decoded, consumed)) = try_decode_pattern(&chars, i, decode_map) {
+ output.push_str(&decoded);
+ i += consumed;
+ continue;
+ }
+ }
+
+ // Regular character, just copy it
+ output.push(chars[i]);
+ i += 1;
+ }
+
+ output
+}
+
+/// Tries to decode a %NAME% pattern starting at position `start`.
+/// Returns Some((decoded_string, chars_consumed)) if successful, None otherwise.
+fn try_decode_pattern(
+ chars: &[char],
+ start: usize,
+ decode_map: &HashMap<&str, &str>,
+) -> Option<(String, usize)> {
+ // Must start with %
+ if chars.get(start) != Some(&'%') {
+ return None;
+ }
+
+ // Find the closing %
+ let mut end = start + 1;
+ while end < chars.len() && chars[end] != '%' {
+ // Pattern names are uppercase letters only
+ if !chars[end].is_ascii_uppercase() {
+ return None;
+ }
+ end += 1;
+ }
+
+ // Must have found a closing %
+ if end >= chars.len() || chars[end] != '%' {
+ return None;
+ }
+
+ // Extract the pattern name (without the % delimiters)
+ let pattern_name: String = chars[start + 1..end].iter().collect();
+
+ // Look up the pattern
+ if let Some(&decoded) = decode_map.get(pattern_name.as_str()) {
+ Some((decoded.to_string(), end - start + 1))
+ } else {
+ None
+ }
}
#[cfg(test)]
@@ -88,15 +179,15 @@ mod tests {
#[test]
fn test_encode_all_chars() {
- let input = r#"<>:"/\|?*"#;
- let expected = "%LT%%GT%%COLON%%QUOTE%%SLASH%%BACKSLASH%%PIPE%%QUESTION%%STAR%";
+ let input = r#".<>:"/\|?*"#;
+ let expected = "%DOT%%LT%%GT%%COLON%%QUOTE%%SLASH%%BACKSLASH%%PIPE%%QUESTION%%STAR%";
assert_eq!(encode_path_name(input), expected);
}
#[test]
fn test_decode_all_chars() {
- let input = "%LT%%GT%%COLON%%QUOTE%%SLASH%%BACKSLASH%%PIPE%%QUESTION%%STAR%";
- let expected = r#"<>:"/\|?*"#;
+ let input = "%DOT%%LT%%GT%%COLON%%QUOTE%%SLASH%%BACKSLASH%%PIPE%%QUESTION%%STAR%";
+ let expected = r#".<>:"/\|?*"#;
assert_eq!(decode_path_name(input), expected);
}
@@ -154,4 +245,67 @@ mod tests {
let decoded = decode_path_name(&encoded);
assert_eq!(decoded, original);
}
+
+ #[test]
+ fn test_encode_period() {
+ let input = "My.Script";
+ let expected = "My%DOT%Script";
+ assert_eq!(encode_path_name(input), expected);
+ }
+
+ #[test]
+ fn test_decode_period() {
+ let input = "My%DOT%Script";
+ let expected = "My.Script";
+ assert_eq!(decode_path_name(input), expected);
+ }
+
+ #[test]
+ fn test_roundtrip_with_period() {
+ let original = "My.Module.Name";
+ let encoded = encode_path_name(original);
+ let decoded = decode_path_name(&encoded);
+ assert_eq!(decoded, original);
+ }
+
+ #[test]
+ fn test_encode_percent() {
+ let input = "My%Thing";
+ let expected = "My%%Thing";
+ assert_eq!(encode_path_name(input), expected);
+ }
+
+ #[test]
+ fn test_decode_percent() {
+ let input = "My%%Thing";
+ let expected = "My%Thing";
+ assert_eq!(decode_path_name(input), expected);
+ }
+
+ #[test]
+ fn test_roundtrip_with_percent() {
+ let original = "My%Thing";
+ let encoded = encode_path_name(original);
+ let decoded = decode_path_name(&encoded);
+ assert_eq!(decoded, original);
+ }
+
+ #[test]
+ fn test_percent_encoding_pattern_roundtrip() {
+ // This tests that a name containing what looks like an encoding pattern
+ // round-trips correctly and doesn't get incorrectly decoded
+ let original = "My%DOT%Thing";
+ let encoded = encode_path_name(original);
+ assert_eq!(encoded, "My%%DOT%%Thing");
+ let decoded = decode_path_name(&encoded);
+ assert_eq!(decoded, original);
+ }
+
+ #[test]
+ fn test_combined_period_and_percent() {
+ let original = "My.%Thing.Other";
+ let encoded = encode_path_name(original);
+ let decoded = decode_path_name(&encoded);
+ assert_eq!(decoded, original);
+ }
}
diff --git a/src/project.rs b/src/project.rs
index b968e47c..0e432147 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -110,12 +110,6 @@ pub struct Project {
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_address: Option,
- /// Determines if Rojo should emit scripts with the appropriate `RunContext`
- /// for `*.client.lua` and `*.server.lua` files in the project instead of
- /// using `Script` and `LocalScript` Instances.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub emit_legacy_scripts: Option,
-
/// A list of globs, relative to the folder the project file is in, that
/// match files that should be excluded if Rojo encounters them.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -573,7 +567,6 @@ mod test {
"$path": "src", // Comment after value
},
"servePort": 34567,
- "emitLegacyScripts": false,
// Test glob parsing with comments
"globIgnorePaths": [
"**/*.spec.lua", // Ignore test files
@@ -601,7 +594,6 @@ mod test {
// Verify the parsed values
assert_eq!(project.name, Some("TestProject".to_string()));
assert_eq!(project.serve_port, Some(34567));
- assert_eq!(project.emit_legacy_scripts, Some(false));
// Verify glob_ignore_paths were parsed correctly
assert_eq!(project.glob_ignore_paths.len(), 2);
diff --git a/src/serve_session.rs b/src/serve_session.rs
index 8623ff10..143187ec 100644
--- a/src/serve_session.rs
+++ b/src/serve_session.rs
@@ -110,10 +110,9 @@ impl ServeSession {
.syncback_rules
.as_ref()
.map(|rules| rules.encode_windows_invalid_chars())
- .unwrap_or(false);
+ .unwrap_or(true);
- let mut instance_context =
- InstanceContext::with_emit_legacy_scripts(root_project.emit_legacy_scripts);
+ let mut instance_context = InstanceContext::new();
instance_context.set_decode_windows_invalid_chars(decode_windows_invalid_chars);
log::trace!("Generating snapshot of instances from VFS");
diff --git a/src/snapshot/metadata.rs b/src/snapshot/metadata.rs
index 755526ee..9214fdb7 100644
--- a/src/snapshot/metadata.rs
+++ b/src/snapshot/metadata.rs
@@ -8,11 +8,7 @@ use anyhow::Context;
use serde::{Deserialize, Serialize};
use crate::{
- glob::Glob,
- path_serializer,
- project::ProjectNode,
- snapshot_middleware::{emit_legacy_scripts_default, Middleware},
- RojoRef,
+ glob::Glob, path_serializer, project::ProjectNode, snapshot_middleware::Middleware, RojoRef,
};
/// Rojo-specific metadata that can be associated with an instance or a snapshot
@@ -142,32 +138,26 @@ impl Default for InstanceMetadata {
pub struct InstanceContext {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub path_ignore_rules: Arc>,
- pub emit_legacy_scripts: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub sync_rules: Vec,
- /// Whether to decode Windows-invalid characters in file names.
- /// Characters like `%LT%`, `%GT%`, `%COLON%`, etc. will be decoded
- /// back to `<`, `>`, `:`, etc. in instance names sent to the plugin.
- #[serde(default)]
+ /// Whether to decode special characters in file names.
+ /// Characters like `%DOT%`, `%LT%`, `%GT%`, `%COLON%`, etc. will be decoded
+ /// back to `.`, `<`, `>`, `:`, etc. in instance names sent to the plugin.
+ /// Defaults to true.
+ #[serde(default = "decode_windows_invalid_chars_default")]
pub decode_windows_invalid_chars: bool,
}
+fn decode_windows_invalid_chars_default() -> bool {
+ true
+}
+
impl InstanceContext {
pub fn new() -> Self {
Self {
path_ignore_rules: Arc::new(Vec::new()),
- emit_legacy_scripts: emit_legacy_scripts_default().unwrap(),
sync_rules: Vec::new(),
- decode_windows_invalid_chars: false,
- }
- }
-
- pub fn with_emit_legacy_scripts(emit_legacy_scripts: Option) -> Self {
- Self {
- emit_legacy_scripts: emit_legacy_scripts
- .or_else(emit_legacy_scripts_default)
- .unwrap(),
- ..Self::new()
+ decode_windows_invalid_chars: true,
}
}
@@ -206,10 +196,6 @@ impl InstanceContext {
self.sync_rules.clear();
}
- pub fn set_emit_legacy_scripts(&mut self, emit_legacy_scripts: bool) {
- self.emit_legacy_scripts = emit_legacy_scripts;
- }
-
/// Returns the middleware specified by the first sync rule that
/// matches the provided path. This does not handle default syncing rules.
pub fn get_user_sync_rule(&self, path: &Path) -> Option<&SyncRule> {
diff --git a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__add_property.snap b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__add_property.snap
index 128a9cea..ee1d0ab1 100644
--- a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__add_property.snap
+++ b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__add_property.snap
@@ -12,8 +12,7 @@ metadata:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_after_patch.snap b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_after_patch.snap
index d494d159..d53e0090 100644
--- a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_after_patch.snap
+++ b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_after_patch.snap
@@ -10,8 +10,7 @@ metadata:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_initial.snap b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_initial.snap
index 49e1c639..ba8afe2a 100644
--- a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_initial.snap
+++ b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__remove_property_initial.snap
@@ -12,8 +12,7 @@ metadata:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__set_name_and_class_name.snap b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__set_name_and_class_name.snap
index 6fd952b7..5af96846 100644
--- a/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__set_name_and_class_name.snap
+++ b/src/snapshot/tests/snapshots/librojo__snapshot__tests__apply__set_name_and_class_name.snap
@@ -10,8 +10,7 @@ metadata:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot/tests/snapshots/librojo__snapshot__tests__compute__add_child.snap b/src/snapshot/tests/snapshots/librojo__snapshot__tests__compute__add_child.snap
index c80e67ad..2f112685 100644
--- a/src/snapshot/tests/snapshots/librojo__snapshot__tests__compute__add_child.snap
+++ b/src/snapshot/tests/snapshots/librojo__snapshot__tests__compute__add_child.snap
@@ -11,8 +11,7 @@ added_instances:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/csv.rs b/src/snapshot_middleware/csv.rs
index 54e51179..c98a4a7c 100644
--- a/src/snapshot_middleware/csv.rs
+++ b/src/snapshot_middleware/csv.rs
@@ -383,7 +383,7 @@ Ack,Ack!,,An exclamation of despair,¡Ay!"#,
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_csv_init(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/root/init.csv"),
"root",
@@ -421,7 +421,7 @@ Ack,Ack!,,An exclamation of despair,¡Ay!"#,
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_csv_init(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/root/init.csv"),
"root",
diff --git a/src/snapshot_middleware/lua.rs b/src/snapshot_middleware/lua.rs
index 077a9b4b..849d0dc3 100644
--- a/src/snapshot_middleware/lua.rs
+++ b/src/snapshot_middleware/lua.rs
@@ -21,14 +21,12 @@ use super::{
#[derive(Debug)]
pub enum ScriptType {
- Server,
- Client,
- Module,
- Plugin,
- LegacyServer,
- LegacyClient,
- RunContextServer,
- RunContextClient,
+ Server, // Script + RunContext.Server
+ Client, // Script + RunContext.Client
+ Module, // ModuleScript
+ Plugin, // Script + RunContext.Plugin
+ Legacy, // Script + RunContext.Legacy
+ Local, // LocalScript
}
/// Core routine for turning Lua files into snapshots.
@@ -47,26 +45,12 @@ pub fn snapshot_lua(
.items;
let (class_name, run_context) = match script_type {
- ScriptType::Server => {
- if context.emit_legacy_scripts {
- ("Script", run_context_enums.get("Legacy"))
- } else {
- ("Script", run_context_enums.get("Server"))
- }
- }
- ScriptType::Client => {
- if context.emit_legacy_scripts {
- ("LocalScript", None)
- } else {
- ("Script", run_context_enums.get("Client"))
- }
- }
+ ScriptType::Server => ("Script", run_context_enums.get("Server")),
+ ScriptType::Client => ("Script", run_context_enums.get("Client")),
ScriptType::Module => ("ModuleScript", None),
ScriptType::Plugin => ("Script", run_context_enums.get("Plugin")),
- ScriptType::LegacyServer => ("Script", run_context_enums.get("Legacy")),
- ScriptType::LegacyClient => ("LocalScript", None),
- ScriptType::RunContextServer => ("Script", run_context_enums.get("Server")),
- ScriptType::RunContextClient => ("Script", run_context_enums.get("Client")),
+ ScriptType::Legacy => ("Script", run_context_enums.get("Legacy")),
+ ScriptType::Local => ("LocalScript", None),
};
let contents = vfs.read_to_string_lf_normalized(path)?;
@@ -101,8 +85,9 @@ pub fn snapshot_lua(
/// Attempts to snapshot an 'init' Lua script contained inside of a folder with
/// the given name.
///
-/// Scripts named `init.lua`, `init.server.lua`, or `init.client.lua` usurp
-/// their parents, which acts similarly to `__init__.py` from the Python world.
+/// Scripts named `init.lua`, `init.server.lua`, `init.client.lua`, `init.local.lua`,
+/// `init.legacy.lua`, or `init.plugin.lua` usurp their parents, which acts similarly
+/// to `__init__.py` from the Python world.
pub fn snapshot_lua_init(
context: &InstanceContext,
vfs: &Vfs,
@@ -115,9 +100,8 @@ pub fn snapshot_lua_init(
if dir_snapshot.class_name != "Folder" {
anyhow::bail!(
- "init.lua, init.server.lua, and init.client.lua can \
- only be used if the instance produced by the containing \
- directory would be a Folder.\n\
+ "init scripts can only be used if the instance produced by the \
+ containing directory would be a Folder.\n\
\n\
The directory {} turned into an instance of class {}.",
folder_path.display(),
@@ -177,6 +161,8 @@ pub fn syncback_lua<'sync>(
.strip_suffix(".server")
.or_else(|| file_stem.strip_suffix(".client"))
.or_else(|| file_stem.strip_suffix(".plugin"))
+ .or_else(|| file_stem.strip_suffix(".local"))
+ .or_else(|| file_stem.strip_suffix(".legacy"))
.unwrap_or(file_stem);
let meta_name = if meta_name.is_empty() {
if snapshot.encode_windows_invalid_chars() {
@@ -211,7 +197,9 @@ pub fn syncback_lua_init<'sync>(
ScriptType::Server => "init.server.luau",
ScriptType::Client => "init.client.luau",
ScriptType::Module => "init.luau",
- _ => anyhow::bail!("syncback is not yet implemented for {script_type:?}"),
+ ScriptType::Plugin => "init.plugin.luau",
+ ScriptType::Legacy => "init.legacy.luau",
+ ScriptType::Local => "init.local.luau",
});
let contents = if let Some(Variant::String(source)) = new_inst.properties.get(&ustr("Source")) {
@@ -248,7 +236,7 @@ mod test {
use memofs::{InMemoryFs, VfsSnapshot};
#[test]
- fn class_module_from_vfs() {
+ fn module_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -256,7 +244,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.lua"),
"foo",
@@ -271,30 +259,7 @@ mod test {
}
#[test]
- fn runcontext_module_from_vfs() {
- let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
- .unwrap();
-
- let vfs = Vfs::new(imfs);
-
- let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
- &vfs,
- Path::new("/foo.lua"),
- "foo",
- ScriptType::Module,
- )
- .unwrap()
- .unwrap();
-
- insta::with_settings!({ sort_maps => true }, {
- insta::assert_yaml_snapshot!(instance_snapshot);
- });
- }
-
- #[test]
- fn plugin_module_from_vfs() {
+ fn plugin_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.plugin.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -302,7 +267,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.plugin.lua"),
"foo",
@@ -317,7 +282,7 @@ mod test {
}
#[test]
- fn class_server_from_vfs() {
+ fn server_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -325,7 +290,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.server.lua"),
"foo",
@@ -340,30 +305,7 @@ mod test {
}
#[test]
- fn runcontext_server_from_vfs() {
- let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
- .unwrap();
-
- let vfs = Vfs::new(imfs);
-
- let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
- &vfs,
- Path::new("/foo.server.lua"),
- "foo",
- ScriptType::Server,
- )
- .unwrap()
- .unwrap();
-
- insta::with_settings!({ sort_maps => true }, {
- insta::assert_yaml_snapshot!(instance_snapshot);
- });
- }
-
- #[test]
- fn class_client_from_vfs() {
+ fn client_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.client.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -371,7 +313,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.client.lua"),
"foo",
@@ -386,19 +328,42 @@ mod test {
}
#[test]
- fn runcontext_client_from_vfs() {
+ fn local_from_vfs() {
let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/foo.client.lua", VfsSnapshot::file("Hello there!"))
+ imfs.load_snapshot("/foo.local.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
+ &InstanceContext::new(),
&vfs,
- Path::new("/foo.client.lua"),
+ Path::new("/foo.local.lua"),
"foo",
- ScriptType::Client,
+ ScriptType::Local,
+ )
+ .unwrap()
+ .unwrap();
+
+ insta::with_settings!({ sort_maps => true }, {
+ insta::assert_yaml_snapshot!(instance_snapshot);
+ });
+ }
+
+ #[test]
+ fn legacy_from_vfs() {
+ let mut imfs = InMemoryFs::new();
+ imfs.load_snapshot("/foo.legacy.lua", VfsSnapshot::file("Hello there!"))
+ .unwrap();
+
+ let vfs = Vfs::new(imfs);
+
+ let instance_snapshot = snapshot_lua(
+ &InstanceContext::new(),
+ &vfs,
+ Path::new("/foo.legacy.lua"),
+ "foo",
+ ScriptType::Legacy,
)
.unwrap()
.unwrap();
@@ -420,7 +385,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua_init(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/root/init.lua"),
"root",
@@ -452,7 +417,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua_init(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/root/init.lua"),
"root",
@@ -467,7 +432,7 @@ mod test {
}
#[test]
- fn class_module_with_meta() {
+ fn module_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -486,7 +451,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.lua"),
"foo",
@@ -501,41 +466,7 @@ mod test {
}
#[test]
- fn runcontext_module_with_meta() {
- let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
- .unwrap();
- imfs.load_snapshot(
- "/foo.meta.json",
- VfsSnapshot::file(
- r#"
- {
- "ignoreUnknownInstances": true
- }
- "#,
- ),
- )
- .unwrap();
-
- let vfs = Vfs::new(imfs);
-
- let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
- &vfs,
- Path::new("/foo.lua"),
- "foo",
- ScriptType::Module,
- )
- .unwrap()
- .unwrap();
-
- insta::with_settings!({ sort_maps => true }, {
- insta::assert_yaml_snapshot!(instance_snapshot);
- });
- }
-
- #[test]
- fn class_script_with_meta() {
+ fn server_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -554,7 +485,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
+ &InstanceContext::new(),
&vfs,
Path::new("/foo.server.lua"),
"foo",
@@ -569,41 +500,7 @@ mod test {
}
#[test]
- fn runcontext_script_with_meta() {
- let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
- .unwrap();
- imfs.load_snapshot(
- "/foo.meta.json",
- VfsSnapshot::file(
- r#"
- {
- "ignoreUnknownInstances": true
- }
- "#,
- ),
- )
- .unwrap();
-
- let vfs = Vfs::new(imfs);
-
- let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
- &vfs,
- Path::new("/foo.server.lua"),
- "foo",
- ScriptType::Server,
- )
- .unwrap()
- .unwrap();
-
- insta::with_settings!({ sort_maps => true }, {
- insta::assert_yaml_snapshot!(instance_snapshot);
- });
- }
-
- #[test]
- fn class_script_disabled() {
+ fn server_disabled() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/bar.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@@ -624,43 +521,7 @@ mod test {
let vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(true)),
- &vfs,
- Path::new("/bar.server.lua"),
- "bar",
- ScriptType::Server,
- )
- .unwrap()
- .unwrap();
-
- insta::with_settings!({ sort_maps => true }, {
- insta::assert_yaml_snapshot!(instance_snapshot);
- });
- }
-
- #[test]
- fn runcontext_script_disabled() {
- let mut imfs = InMemoryFs::new();
- imfs.load_snapshot("/bar.server.lua", VfsSnapshot::file("Hello there!"))
- .unwrap();
- imfs.load_snapshot(
- "/bar.meta.json",
- VfsSnapshot::file(
- r#"
- {
- "properties": {
- "Disabled": true
- }
- }
- "#,
- ),
- )
- .unwrap();
-
- let vfs = Vfs::new(imfs);
-
- let instance_snapshot = snapshot_lua(
- &InstanceContext::with_emit_legacy_scripts(Some(false)),
+ &InstanceContext::new(),
&vfs,
Path::new("/bar.server.lua"),
"bar",
diff --git a/src/snapshot_middleware/meta_file.rs b/src/snapshot_middleware/meta_file.rs
index 320c3656..e81212e0 100644
--- a/src/snapshot_middleware/meta_file.rs
+++ b/src/snapshot_middleware/meta_file.rs
@@ -55,13 +55,14 @@ impl AdjacentMetadata {
// Use the file stem from the actual path (which has encoded chars)
// rather than the decoded name, so meta files are found correctly
// when encodeWindowsInvalidChars is enabled.
- // Strip known script type suffixes (.server, .client, .plugin) to get
- // the base name for the meta file.
+ // Strip known script type suffixes to get the base name for the meta file.
let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
let base_name = file_stem
.strip_suffix(".server")
.or_else(|| file_stem.strip_suffix(".client"))
.or_else(|| file_stem.strip_suffix(".plugin"))
+ .or_else(|| file_stem.strip_suffix(".local"))
+ .or_else(|| file_stem.strip_suffix(".legacy"))
.unwrap_or(file_stem);
let meta_path_json = path.with_file_name(format!("{base_name}.meta.json"));
let meta_path_jsonc = path.with_file_name(format!("{base_name}.meta.jsonc"));
diff --git a/src/snapshot_middleware/mod.rs b/src/snapshot_middleware/mod.rs
index 7aa661ef..0dc7984c 100644
--- a/src/snapshot_middleware/mod.rs
+++ b/src/snapshot_middleware/mod.rs
@@ -53,10 +53,7 @@ use self::{
yaml::snapshot_yaml,
};
-pub use self::{
- lua::ScriptType, project::snapshot_project_node, util::emit_legacy_scripts_default,
- util::PathExt,
-};
+pub use self::{lua::ScriptType, project::snapshot_project_node, util::PathExt};
/// Returns an `InstanceSnapshot` for the provided path.
/// This will inspect the path and find the appropriate middleware for it,
@@ -98,6 +95,7 @@ pub fn snapshot_from_vfs(
// TODO: Is this even necessary anymore?
match file_name {
"init.server.luau" | "init.server.lua" | "init.client.luau" | "init.client.lua"
+ | "init.local.luau" | "init.local.lua" | "init.legacy.luau" | "init.legacy.lua"
| "init.luau" | "init.lua" | "init.csv" => return Ok(None),
_ => {}
}
@@ -131,6 +129,10 @@ fn get_dir_middleware<'path>(
(Middleware::ServerScriptDir, "init.server.lua"),
(Middleware::ClientScriptDir, "init.client.luau"),
(Middleware::ClientScriptDir, "init.client.lua"),
+ (Middleware::LocalScriptDir, "init.local.luau"),
+ (Middleware::LocalScriptDir, "init.local.lua"),
+ (Middleware::LegacyScriptDir, "init.legacy.luau"),
+ (Middleware::LegacyScriptDir, "init.legacy.lua"),
(Middleware::CsvDir, "init.csv"),
]
});
@@ -197,10 +199,8 @@ pub enum Middleware {
ClientScript,
ModuleScript,
PluginScript,
- LegacyClientScript,
- LegacyServerScript,
- RunContextServerScript,
- RunContextClientScript,
+ LocalScript,
+ LegacyScript,
Project,
Rbxm,
Rbxmx,
@@ -218,6 +218,10 @@ pub enum Middleware {
#[serde(skip_deserializing)]
ModuleScriptDir,
#[serde(skip_deserializing)]
+ LocalScriptDir,
+ #[serde(skip_deserializing)]
+ LegacyScriptDir,
+ #[serde(skip_deserializing)]
CsvDir,
}
@@ -239,18 +243,8 @@ impl Middleware {
Self::ClientScript => snapshot_lua(context, vfs, path, name, ScriptType::Client),
Self::ModuleScript => snapshot_lua(context, vfs, path, name, ScriptType::Module),
Self::PluginScript => snapshot_lua(context, vfs, path, name, ScriptType::Plugin),
- Self::LegacyClientScript => {
- snapshot_lua(context, vfs, path, name, ScriptType::LegacyClient)
- }
- Self::LegacyServerScript => {
- snapshot_lua(context, vfs, path, name, ScriptType::LegacyServer)
- }
- Self::RunContextClientScript => {
- snapshot_lua(context, vfs, path, name, ScriptType::RunContextClient)
- }
- Self::RunContextServerScript => {
- snapshot_lua(context, vfs, path, name, ScriptType::RunContextServer)
- }
+ Self::LocalScript => snapshot_lua(context, vfs, path, name, ScriptType::Local),
+ Self::LegacyScript => snapshot_lua(context, vfs, path, name, ScriptType::Legacy),
Self::Project => snapshot_project(context, vfs, path, name),
Self::Rbxm => snapshot_rbxm(context, vfs, path, name),
Self::Rbxmx => snapshot_rbxmx(context, vfs, path, name),
@@ -269,6 +263,10 @@ impl Middleware {
Self::ModuleScriptDir => {
snapshot_lua_init(context, vfs, path, name, ScriptType::Module)
}
+ Self::LocalScriptDir => snapshot_lua_init(context, vfs, path, name, ScriptType::Local),
+ Self::LegacyScriptDir => {
+ snapshot_lua_init(context, vfs, path, name, ScriptType::Legacy)
+ }
Self::CsvDir => snapshot_csv_init(context, vfs, path, name),
};
if let Ok(Some(ref mut snapshot)) = output {
@@ -299,6 +297,9 @@ impl Middleware {
Middleware::ServerScript => syncback_lua(snapshot),
Middleware::ClientScript => syncback_lua(snapshot),
Middleware::ModuleScript => syncback_lua(snapshot),
+ Middleware::PluginScript => syncback_lua(snapshot),
+ Middleware::LocalScript => syncback_lua(snapshot),
+ Middleware::LegacyScript => syncback_lua(snapshot),
Middleware::Rbxm => syncback_rbxm(snapshot),
Middleware::Rbxmx => syncback_rbxmx(snapshot),
Middleware::Toml => anyhow::bail!("cannot syncback Toml middleware"),
@@ -309,15 +310,9 @@ impl Middleware {
Middleware::ServerScriptDir => syncback_lua_init(ScriptType::Server, snapshot),
Middleware::ClientScriptDir => syncback_lua_init(ScriptType::Client, snapshot),
Middleware::ModuleScriptDir => syncback_lua_init(ScriptType::Module, snapshot),
+ Middleware::LocalScriptDir => syncback_lua_init(ScriptType::Local, snapshot),
+ Middleware::LegacyScriptDir => syncback_lua_init(ScriptType::Legacy, snapshot),
Middleware::CsvDir => syncback_csv_init(snapshot),
-
- Middleware::PluginScript
- | Middleware::LegacyServerScript
- | Middleware::LegacyClientScript
- | Middleware::RunContextServerScript
- | Middleware::RunContextClientScript => {
- anyhow::bail!("syncback is not implemented for {self:?} yet")
- }
}
}
@@ -330,6 +325,8 @@ impl Middleware {
| Middleware::ServerScriptDir
| Middleware::ClientScriptDir
| Middleware::ModuleScriptDir
+ | Middleware::LocalScriptDir
+ | Middleware::LegacyScriptDir
| Middleware::CsvDir
)
}
@@ -427,6 +424,10 @@ pub fn default_sync_rules() -> &'static [SyncRule] {
sync_rule!("*.client.luau", ClientScript, ".client.luau"),
sync_rule!("*.plugin.lua", PluginScript, ".plugin.lua"),
sync_rule!("*.plugin.luau", PluginScript, ".plugin.luau"),
+ sync_rule!("*.legacy.lua", LegacyScript, ".legacy.lua"),
+ sync_rule!("*.legacy.luau", LegacyScript, ".legacy.luau"),
+ sync_rule!("*.local.lua", LocalScript, ".local.lua"),
+ sync_rule!("*.local.luau", LocalScript, ".local.luau"),
sync_rule!("*.{lua,luau}", ModuleScript),
sync_rule!("*.project.json", Project, ".project.json"),
sync_rule!("*.project.jsonc", Project, ".project.jsonc"),
diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs
index 90d743a7..82d9185b 100644
--- a/src/snapshot_middleware/project.rs
+++ b/src/snapshot_middleware/project.rs
@@ -25,7 +25,7 @@ use crate::{
RojoRef,
};
-use super::{emit_legacy_scripts_default, snapshot_from_vfs};
+use super::snapshot_from_vfs;
pub fn snapshot_project(
context: &InstanceContext,
@@ -55,12 +55,6 @@ pub fn snapshot_project(
context.add_sync_rules(sync_rules);
context.add_path_ignore_rules(rules);
- context.set_emit_legacy_scripts(
- project
- .emit_legacy_scripts
- .or_else(emit_legacy_scripts_default)
- .unwrap(),
- );
match snapshot_project_node(&context, path, project_name, &project.tree, vfs, None)? {
Some(found_snapshot) => {
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
index 9364e46f..283d222e 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
index 1ed84fa2..12fcb651 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
@@ -20,8 +20,7 @@ metadata:
- /root/init.meta.jsonc
- /root/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
index 4f8d795b..c8c71fcc 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
@@ -20,8 +20,7 @@ metadata:
- /root/init.meta.jsonc
- /root/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
index 0d57b0c5..fccb39f4 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__empty_folder.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__empty_folder.snap
index 09b5583f..d0fae6f5 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__empty_folder.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__empty_folder.snap
@@ -20,8 +20,7 @@ metadata:
- /foo/init.meta.jsonc
- /foo/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__folder_in_folder.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__folder_in_folder.snap
index 5c2d4e49..7d43c462 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__folder_in_folder.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__dir__test__folder_in_folder.snap
@@ -20,8 +20,7 @@ metadata:
- /foo/init.meta.jsonc
- /foo/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
@@ -47,8 +46,7 @@ children:
- /foo/Child/init.meta.jsonc
- /foo/Child/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: dir
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__instance_from_vfs.snap
index 9cdfe3a3..ec8f79ec 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__instance_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__instance_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__with_metadata.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__with_metadata.snap
index ccc09f10..50aa332e 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__with_metadata.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json__test__with_metadata.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs.snap
index b72fc460..37da1388 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo.model.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
@@ -26,8 +25,7 @@ children:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs_legacy.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs_legacy.snap
index b72fc460..37da1388 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs_legacy.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__json_model__test__model_from_vfs_legacy.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo.model.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
@@ -26,8 +25,7 @@ children:
ignore_unknown_instances: false
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_disabled.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_disabled.snap
deleted file mode 100644
index 4166ea3a..00000000
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_disabled.snap
+++ /dev/null
@@ -1,30 +0,0 @@
----
-source: src/snapshot_middleware/lua.rs
-expression: instance_snapshot
----
-snapshot_id: "00000000000000000000000000000000"
-metadata:
- ignore_unknown_instances: false
- instigating_source:
- Path: /bar.server.lua
- relevant_paths:
- - /bar.server.lua
- - /bar.meta.json
- - /bar.meta.jsonc
- - /bar.meta.json5
- context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
- specified_id: ~
- middleware: ~
- schema: ~
-name: bar
-class_name: Script
-properties:
- Disabled:
- Bool: true
- RunContext:
- Enum: 0
- Source:
- String: Hello there!
-children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_with_meta.snap
deleted file mode 100644
index 260e7c41..00000000
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_script_with_meta.snap
+++ /dev/null
@@ -1,28 +0,0 @@
----
-source: src/snapshot_middleware/lua.rs
-expression: instance_snapshot
----
-snapshot_id: "00000000000000000000000000000000"
-metadata:
- ignore_unknown_instances: true
- instigating_source:
- Path: /foo.server.lua
- relevant_paths:
- - /foo.server.lua
- - /foo.meta.json
- - /foo.meta.jsonc
- - /foo.meta.json5
- context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
- specified_id: ~
- middleware: ~
- schema: ~
-name: foo
-class_name: Script
-properties:
- RunContext:
- Enum: 0
- Source:
- String: Hello there!
-children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_client_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__client_from_vfs.snap
similarity index 87%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_client_from_vfs.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__client_from_vfs.snap
index a462139c..f00ca340 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_client_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__client_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs.snap
index 45416fff..48b9c2b6 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs.snap
@@ -20,8 +20,7 @@ metadata:
- /root/init.meta.jsonc
- /root/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs_with_meta.snap
index 736985d4..a08bc42a 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__init_module_from_vfs_with_meta.snap
@@ -20,8 +20,7 @@ metadata:
- /root/init.meta.jsonc
- /root/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_server_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__legacy_from_vfs.snap
similarity index 79%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_server_from_vfs.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__legacy_from_vfs.snap
index 4743375f..f6cd0dcc 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_server_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__legacy_from_vfs.snap
@@ -6,15 +6,14 @@ snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
- Path: /foo.server.lua
+ Path: /foo.legacy.lua
relevant_paths:
- - /foo.server.lua
+ - /foo.legacy.lua
- /foo.meta.json
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_client_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__local_from_vfs.snap
similarity index 78%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_client_from_vfs.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__local_from_vfs.snap
index e7c4aaa4..1744921b 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_client_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__local_from_vfs.snap
@@ -6,15 +6,14 @@ snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
- Path: /foo.client.lua
+ Path: /foo.local.lua
relevant_paths:
- - /foo.client.lua
+ - /foo.local.lua
- /foo.meta.json
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_from_vfs.snap
similarity index 86%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_from_vfs.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_from_vfs.snap
index afcea174..9cce5a73 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_with_meta.snap
similarity index 86%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_with_meta.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_with_meta.snap
index 9c2bd685..081fdba5 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__class_module_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__module_with_meta.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__plugin_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__plugin_from_vfs.snap
new file mode 100644
index 00000000..4f54e477
--- /dev/null
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__plugin_from_vfs.snap
@@ -0,0 +1,27 @@
+---
+source: src/snapshot_middleware/lua.rs
+expression: instance_snapshot
+---
+snapshot_id: "00000000000000000000000000000000"
+metadata:
+ ignore_unknown_instances: false
+ instigating_source:
+ Path: /foo.plugin.lua
+ relevant_paths:
+ - /foo.plugin.lua
+ - /foo.meta.json
+ - /foo.meta.jsonc
+ - /foo.meta.json5
+ context:
+ decode_windows_invalid_chars: true
+ specified_id: ~
+ middleware: ~
+ schema: ~
+name: foo
+class_name: Script
+properties:
+ RunContext:
+ Enum: 3
+ Source:
+ String: Hello there!
+children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_from_vfs.snap
deleted file mode 100644
index a723e363..00000000
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_from_vfs.snap
+++ /dev/null
@@ -1,26 +0,0 @@
----
-source: src/snapshot_middleware/lua.rs
-expression: instance_snapshot
----
-snapshot_id: "00000000000000000000000000000000"
-metadata:
- ignore_unknown_instances: false
- instigating_source:
- Path: /foo.lua
- relevant_paths:
- - /foo.lua
- - /foo.meta.json
- - /foo.meta.jsonc
- - /foo.meta.json5
- context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
- specified_id: ~
- middleware: ~
- schema: ~
-name: foo
-class_name: ModuleScript
-properties:
- Source:
- String: Hello there!
-children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_with_meta.snap
deleted file mode 100644
index 50f5566f..00000000
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_module_with_meta.snap
+++ /dev/null
@@ -1,26 +0,0 @@
----
-source: src/snapshot_middleware/lua.rs
-expression: instance_snapshot
----
-snapshot_id: "00000000000000000000000000000000"
-metadata:
- ignore_unknown_instances: true
- instigating_source:
- Path: /foo.lua
- relevant_paths:
- - /foo.lua
- - /foo.meta.json
- - /foo.meta.jsonc
- - /foo.meta.json5
- context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
- specified_id: ~
- middleware: ~
- schema: ~
-name: foo
-class_name: ModuleScript
-properties:
- Source:
- String: Hello there!
-children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_disabled.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_disabled.snap
similarity index 88%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_disabled.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_disabled.snap
index 229434cb..075c0f92 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_disabled.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_disabled.snap
@@ -13,8 +13,7 @@ metadata:
- /bar.meta.jsonc
- /bar.meta.json5
context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_server_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_from_vfs.snap
similarity index 87%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_server_from_vfs.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_from_vfs.snap
index 6c1bbb54..20f64505 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_server_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_with_meta.snap
similarity index 87%
rename from src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_with_meta.snap
rename to src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_with_meta.snap
index 2cc32295..702e47da 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__runcontext_script_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__lua__test__server_with_meta.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: false
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_json.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_json.snap
index 1d6d7ed9..854eafeb 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_json.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_json.snap
@@ -10,8 +10,7 @@ metadata:
- /foo/bar.meta.jsonc
- /foo/bar.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_jsonc.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_jsonc.snap
index 1d6d7ed9..854eafeb 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_jsonc.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__adjacent_read_jsonc.snap
@@ -10,8 +10,7 @@ metadata:
- /foo/bar.meta.jsonc
- /foo/bar.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_json.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_json.snap
index d32f1ea7..09c35784 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_json.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_json.snap
@@ -10,8 +10,7 @@ metadata:
- /foo/init.meta.jsonc
- /foo/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_jsonc.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_jsonc.snap
index d32f1ea7..09c35784 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_jsonc.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__meta_file__test__directory_read_jsonc.snap
@@ -10,8 +10,7 @@ metadata:
- /foo/init.meta.jsonc
- /foo/init.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__no_name_project.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__no_name_project.snap
index 886e91d8..576ed53d 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__no_name_project.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__no_name_project.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo/default.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_from_direct_file.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_from_direct_file.snap
index df1a0e7a..0abd172a 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_from_direct_file.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_from_direct_file.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo/hello.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_path_property_overrides.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_path_property_overrides.snap
index 8b19153f..0cefd978 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_path_property_overrides.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_path_property_overrides.snap
@@ -11,8 +11,7 @@ metadata:
- /foo/other.project.json
- /foo/default.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: project
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_children.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_children.snap
index 30baacd2..a999b54c 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_children.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_children.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
@@ -31,8 +30,7 @@ children:
parent_class: Folder
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project.snap
index b693973b..ed1b8b73 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project.snap
@@ -11,8 +11,7 @@ metadata:
- /foo/other.project.json
- /foo/default.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: project
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project_with_children.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project_with_children.snap
index 24e08067..9d4a6a30 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project_with_children.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_project_with_children.snap
@@ -11,8 +11,7 @@ metadata:
- /foo/other.project.json
- /foo/default.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: project
schema: ~
@@ -32,8 +31,7 @@ children:
parent_class: Folder
relevant_paths: []
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_txt.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_txt.snap
index 6a0fe78e..0c4403af 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_txt.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_path_to_txt.snap
@@ -14,8 +14,7 @@ metadata:
- /foo/other.meta.json5
- /foo/default.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: text
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_resolved_properties.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_resolved_properties.snap
index a712765d..588c5560 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_resolved_properties.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_resolved_properties.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_unresolved_properties.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_unresolved_properties.snap
index ea0b4016..c90a2906 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_unresolved_properties.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__project__test__project_with_unresolved_properties.snap
@@ -10,8 +10,7 @@ metadata:
relevant_paths:
- /foo.project.json
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap
index 892e2ab7..6cd888d4 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__with_metadata.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__with_metadata.snap
index 74ad11a4..ffd1ebea 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__with_metadata.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__with_metadata.snap
@@ -13,8 +13,7 @@ metadata:
- foo.meta.jsonc
- foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__instance_from_vfs.snap
index be12e2ba..1a96f8c8 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__instance_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__instance_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__with_metadata.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__with_metadata.snap
index 3daf721a..a462fa94 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__with_metadata.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__txt__test__with_metadata.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap
index 75ac67e0..53f642f6 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap
@@ -13,8 +13,7 @@ metadata:
- /foo.meta.jsonc
- /foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: ~
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__with_metadata.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__with_metadata.snap
index 0eeb3da0..5d6f6bd0 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__with_metadata.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__with_metadata.snap
@@ -13,8 +13,7 @@ metadata:
- foo.meta.jsonc
- foo.meta.json5
context:
- emit_legacy_scripts: true
- decode_windows_invalid_chars: false
+ decode_windows_invalid_chars: true
specified_id: manually specified
middleware: ~
schema: ~
diff --git a/src/snapshot_middleware/util.rs b/src/snapshot_middleware/util.rs
index c16edcce..145a5628 100644
--- a/src/snapshot_middleware/util.rs
+++ b/src/snapshot_middleware/util.rs
@@ -48,8 +48,3 @@ where
.with_context(|| format!("Path does not have a parent: {}", path.display()))
}
}
-
-// TEMP function until rojo 8.0, when it can be replaced with bool::default (aka false)
-pub fn emit_legacy_scripts_default() -> Option {
- Some(true)
-}
diff --git a/src/syncback/file_names.rs b/src/syncback/file_names.rs
index 2fcf7984..4d1e7351 100644
--- a/src/syncback/file_names.rs
+++ b/src/syncback/file_names.rs
@@ -91,6 +91,8 @@ pub fn extension_for_middleware(middleware: Middleware) -> &'static str {
Middleware::ClientScript => "client.luau",
Middleware::ModuleScript => "luau",
Middleware::PluginScript => "plugin.luau",
+ Middleware::LocalScript => "local.luau",
+ Middleware::LegacyScript => "legacy.luau",
Middleware::Project => "project.json5",
Middleware::Rbxm => "rbxm",
Middleware::Rbxmx => "rbxmx",
@@ -98,12 +100,6 @@ pub fn extension_for_middleware(middleware: Middleware) -> &'static str {
Middleware::Text => "txt",
Middleware::Yaml => "yml",
- Middleware::LegacyServerScript
- | Middleware::LegacyClientScript
- | Middleware::RunContextServerScript
- | Middleware::RunContextClientScript => {
- todo!("syncback does not work on the middleware {middleware:?} yet")
- }
// These are manually specified and not `_` to guard against future
// middleware additions missing this function.
Middleware::Ignore => unimplemented!("syncback does not work on Ignore middleware"),
@@ -111,7 +107,9 @@ pub fn extension_for_middleware(middleware: Middleware) -> &'static str {
| Middleware::CsvDir
| Middleware::ServerScriptDir
| Middleware::ClientScriptDir
- | Middleware::ModuleScriptDir => {
+ | Middleware::ModuleScriptDir
+ | Middleware::LocalScriptDir
+ | Middleware::LegacyScriptDir => {
unimplemented!("directory middleware requires special treatment")
}
}
diff --git a/src/syncback/mod.rs b/src/syncback/mod.rs
index 4cb1d68b..7e305fa2 100644
--- a/src/syncback/mod.rs
+++ b/src/syncback/mod.rs
@@ -325,8 +325,21 @@ pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
"Folder" | "Configuration" | "Tool" | "ScreenGui" | "SurfaceGui" | "BillboardGui"
| "AdGui" => Middleware::Dir,
"StringValue" => Middleware::Text,
- "Script" => Middleware::ServerScript,
- "LocalScript" => Middleware::ClientScript,
+ "Script" => {
+ // Check RunContext to determine which middleware to use
+ // RunContext enum values: Legacy = 0, Server = 1, Client = 2, Plugin = 3
+ match inst.properties.get(&ustr("RunContext")) {
+ Some(Variant::Enum(e)) => match e.to_u32() {
+ 0 => Middleware::LegacyScript,
+ 1 => Middleware::ServerScript,
+ 2 => Middleware::ClientScript,
+ 3 => Middleware::PluginScript,
+ _ => Middleware::LegacyScript, // Unknown RunContext, default to Legacy
+ },
+ _ => Middleware::LegacyScript, // No RunContext property, default to Legacy
+ }
+ }
+ "LocalScript" => Middleware::LocalScript,
"ModuleScript" => Middleware::ModuleScript,
"LocalizationTable" => Middleware::Csv,
// Default: use JsonModel for everything else (becomes Dir if has children)
@@ -339,6 +352,9 @@ pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
Middleware::ServerScript => Middleware::ServerScriptDir,
Middleware::ClientScript => Middleware::ClientScriptDir,
Middleware::ModuleScript => Middleware::ModuleScriptDir,
+ Middleware::PluginScript => Middleware::ServerScriptDir, // Plugin scripts with children use server script dir for now
+ Middleware::LegacyScript => Middleware::LegacyScriptDir,
+ Middleware::LocalScript => Middleware::LocalScriptDir,
Middleware::Csv => Middleware::CsvDir,
Middleware::JsonModel | Middleware::Text => Middleware::Dir,
_ => middleware,
@@ -431,11 +447,11 @@ impl SyncbackRules {
Ok(globs)
}
- /// Returns whether Windows-invalid characters should be encoded in file
- /// names during syncback. Defaults to `false`.
+ /// Returns whether special characters (including periods) should be encoded
+ /// in file names during syncback. Defaults to `true`.
#[inline]
pub fn encode_windows_invalid_chars(&self) -> bool {
- self.encode_windows_invalid_chars.unwrap_or(false)
+ self.encode_windows_invalid_chars.unwrap_or(true)
}
}
diff --git a/src/web/api.rs b/src/web/api.rs
index bec86a0c..80b8416f 100644
--- a/src/web/api.rs
+++ b/src/web/api.rs
@@ -257,6 +257,54 @@ impl ApiService {
/// Syncback an added instance by creating a file in the filesystem.
/// The file watcher will pick up the change and update Rojo's tree.
///
+ /// Checks if a path in the Rojo tree is unique by verifying no duplicates exist
+ /// at ANY level of the path (similar to ref_properties::is_path_unique).
+ /// Returns true if the path is unique, false if duplicates exist at any level.
+ fn is_tree_path_unique(&self, tree: &crate::snapshot::RojoTree, target_ref: Ref) -> bool {
+ let mut current_ref = target_ref;
+
+ loop {
+ let current = match tree.get_instance(current_ref) {
+ Some(inst) => inst,
+ None => return false,
+ };
+
+ let parent_ref = current.parent();
+ if parent_ref.is_none() {
+ // Reached root - path is unique at all levels
+ return true;
+ }
+
+ let parent = match tree.get_instance(parent_ref) {
+ Some(inst) => inst,
+ None => return true,
+ };
+
+ // Check if any sibling has the same name as current
+ let current_name = current.name();
+ let mut count = 0;
+ for child_ref in parent.children() {
+ if let Some(child) = tree.get_instance(*child_ref) {
+ if child.name() == current_name {
+ count += 1;
+ if count > 1 {
+ // Duplicate found at this level - path is not unique
+ log::warn!(
+ "Syncback: Path to '{}' is not unique - duplicate-named siblings at '{}'",
+ current_name,
+ parent.name()
+ );
+ return false;
+ }
+ }
+ }
+ }
+
+ // Move up to parent and check the next level
+ current_ref = parent_ref;
+ }
+ }
+
/// This follows the same middleware selection logic as the dedicated syncback
/// system in `src/syncback/mod.rs` to ensure consistent behavior.
fn syncback_added_instance(
@@ -280,6 +328,14 @@ impl ApiService {
.get_instance(added.parent)
.context("Parent instance not found in Rojo tree")?;
+ // Check if the parent path is unique (no duplicate-named siblings at any level)
+ if !self.is_tree_path_unique(&tree, added.parent) {
+ anyhow::bail!(
+ "Cannot sync instance '{}' - parent path contains duplicate-named siblings",
+ added.name
+ );
+ }
+
// Get the parent's filesystem path from its metadata
let parent_path = parent_instance
.metadata()
@@ -302,6 +358,54 @@ impl ApiService {
self.syncback_instance_to_path(added, &parent_dir)
}
+ /// Check for duplicate names among children and return the set of unique children
+ /// that should be synced. Logs warnings for skipped duplicates.
+ fn filter_duplicate_children<'a>(
+ &self,
+ children: &'a [crate::web::interface::AddedInstance],
+ ) -> Vec<&'a crate::web::interface::AddedInstance> {
+ use std::collections::HashMap;
+
+ // Count occurrences of each name
+ let mut name_counts: HashMap<&str, usize> = HashMap::new();
+ for child in children {
+ *name_counts.entry(&child.name).or_insert(0) += 1;
+ }
+
+ // Find duplicates
+ let duplicates: std::collections::HashSet<&str> = name_counts
+ .iter()
+ .filter(|(_, &count)| count > 1)
+ .map(|(&name, _)| name)
+ .collect();
+
+ // Log skipped duplicates
+ let mut skipped_count = 0;
+ for child in children {
+ if duplicates.contains(child.name.as_str()) {
+ log::warn!(
+ "Syncback: Skipped instance '{}' ({}) - has duplicate-named siblings (cannot reliably sync)",
+ child.name,
+ child.class_name
+ );
+ skipped_count += 1;
+ }
+ }
+
+ if skipped_count > 0 {
+ log::warn!(
+ "Syncback: Skipped {} instance(s) with duplicate-named siblings",
+ skipped_count
+ );
+ }
+
+ // Return only non-duplicate children
+ children
+ .iter()
+ .filter(|child| !duplicates.contains(child.name.as_str()))
+ .collect()
+ }
+
/// Recursively syncback an instance and its children to the filesystem.
/// This is the internal implementation that handles the actual file creation.
fn syncback_instance_to_path(
@@ -320,7 +424,9 @@ impl ApiService {
)
})?;
- let has_children = !added.children.is_empty();
+ // Filter out children with duplicate names (cannot reliably sync)
+ let unique_children = self.filter_duplicate_children(&added.children);
+ let has_children = !unique_children.is_empty();
// Determine the appropriate middleware/file format based on class name
// This matches the logic in src/syncback/mod.rs::get_best_middleware
@@ -345,7 +451,7 @@ impl ApiService {
"Syncback: Created ModuleScript directory at {}",
dir_path.display()
);
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
@@ -378,7 +484,7 @@ impl ApiService {
"Syncback: Created Script directory at {}",
dir_path.display()
);
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
@@ -399,7 +505,7 @@ impl ApiService {
fs::create_dir_all(&dir_path).with_context(|| {
format!("Failed to create directory: {}", dir_path.display())
})?;
- let init_path = dir_path.join("init.client.luau");
+ let init_path = dir_path.join("init.local.luau");
fs::write(&init_path, source.as_bytes()).with_context(|| {
format!("Failed to write file: {}", init_path.display())
})?;
@@ -408,11 +514,11 @@ impl ApiService {
"Syncback: Created LocalScript directory at {}",
dir_path.display()
);
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
- let file_path = parent_dir.join(format!("{}.client.luau", added.name));
+ let file_path = parent_dir.join(format!("{}.local.luau", added.name));
fs::write(&file_path, source.as_bytes()).with_context(|| {
format!("Failed to write file: {}", file_path.display())
})?;
@@ -439,7 +545,8 @@ impl ApiService {
};
// Add .gitkeep for empty directories with no metadata (matches dedicated syncback)
- if added.children.is_empty() && !has_metadata {
+ // Uses !has_children which accounts for filtered duplicate children
+ if !has_children && !has_metadata {
fs::write(dir_path.join(".gitkeep"), b"")
.with_context(|| "Failed to write .gitkeep")?;
}
@@ -451,7 +558,7 @@ impl ApiService {
);
// Recursively process children
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
}
@@ -469,7 +576,7 @@ impl ApiService {
"Syncback: Created StringValue directory at {}",
dir_path.display()
);
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
@@ -508,7 +615,7 @@ impl ApiService {
"Syncback: Created LocalizationTable directory at {}",
dir_path.display()
);
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
@@ -542,7 +649,7 @@ impl ApiService {
);
// Recursively process children
- for child in &added.children {
+ for child in &unique_children {
self.syncback_instance_to_path(child, &dir_path)?;
}
} else {
@@ -752,11 +859,9 @@ impl ApiService {
/// For `Script` class:
/// - RunContext: Client → "client"
/// - RunContext: Server → "server"
- /// - RunContext: Legacy → "server" (default for legacy mode)
+ /// - RunContext: Legacy → "legacy"
/// - RunContext: Plugin → "plugin"
- /// - No RunContext → "server" (default)
- ///
- /// This ensures proper round-tripping regardless of emitLegacyScripts mode.
+ /// - No RunContext → "legacy" (default)
fn get_script_suffix_for_run_context(
&self,
added: &crate::web::interface::AddedInstance,
@@ -779,16 +884,16 @@ impl ApiService {
return match name.as_ref() {
"Client" => "client",
"Server" => "server",
- "Legacy" => "server",
+ "Legacy" => "legacy",
"Plugin" => "plugin",
- _ => "server",
+ _ => "legacy",
};
}
}
}
- // Default to server if no RunContext or unrecognized
- "server"
+ // Default to legacy if no RunContext or unrecognized
+ "legacy"
}
/// Write an adjacent meta file for scripts without children.
@@ -1596,15 +1701,13 @@ mod tests {
// Test that script class names result in correct file extensions
let test_cases = vec![
("ModuleScript", "TestModule", "TestModule.luau"),
- ("Script", "TestScript", "TestScript.server.luau"),
- ("LocalScript", "TestClient", "TestClient.client.luau"),
+ ("LocalScript", "TestClient", "TestClient.local.luau"),
];
for (class_name, name, expected) in test_cases {
let file_name = match class_name {
"ModuleScript" => format!("{}.luau", name),
- "Script" => format!("{}.server.luau", name),
- "LocalScript" => format!("{}.client.luau", name),
+ "LocalScript" => format!("{}.local.luau", name),
_ => format!("{}.model.json", name),
};
assert_eq!(
@@ -1758,8 +1861,8 @@ mod tests {
}
#[test]
- fn test_run_context_legacy_gives_server_suffix() {
- // Script with RunContext: Legacy should produce .server.luau (for legacy mode)
+ fn test_run_context_legacy_gives_legacy_suffix() {
+ // Script with RunContext: Legacy should produce .legacy.luau
let run_context_enums = rbx_reflection_database::get()
.ok()
.and_then(|db| db.enums.get("RunContext"))
@@ -1792,14 +1895,13 @@ mod tests {
// For Script class, the file suffix is determined by RunContext:
// - Client → .client.luau
// - Server → .server.luau
- // - Legacy → .server.luau
+ // - Legacy → .legacy.luau
// - Plugin → .plugin.luau
- // This allows proper round-tripping regardless of emitLegacyScripts mode
let expected_mappings = vec![
("Client", "client"),
("Server", "server"),
- ("Legacy", "server"),
+ ("Legacy", "legacy"),
("Plugin", "plugin"),
];
@@ -1807,9 +1909,9 @@ mod tests {
let suffix = match run_context {
"Client" => "client",
"Server" => "server",
- "Legacy" => "server",
+ "Legacy" => "legacy",
"Plugin" => "plugin",
- _ => "server",
+ _ => "legacy",
};
assert_eq!(
suffix, expected_suffix,
@@ -1820,14 +1922,14 @@ mod tests {
}
#[test]
- fn test_local_script_always_client() {
- // LocalScript class always produces .client.luau regardless of any properties
+ fn test_local_script_always_local() {
+ // LocalScript class always produces .local.luau regardless of any properties
let class_name = "LocalScript";
- let expected_suffix = "client";
+ let expected_suffix = "local";
- // LocalScript doesn't check RunContext - it's always client
+ // LocalScript doesn't check RunContext - it's always local
let suffix = if class_name == "LocalScript" {
- "client"
+ "local"
} else {
"unknown"
};
@@ -2136,8 +2238,6 @@ mod tests {
// =========================================================================
mod run_context_comprehensive_tests {
use super::*;
- use rbx_dom_weak::types::Enum;
- use std::collections::HashMap;
/// Helper to get RunContext enum value by name
fn get_run_context_value(name: &str) -> Option {
@@ -2817,7 +2917,7 @@ mod tests {
// In scripts-only mode, all instances get ignoreUnknownInstances: true
// This prevents accidental deletion of non-script instances
let folder = "Folder";
- let is_script = is_script_class(folder);
+ let _is_script = is_script_class(folder);
// Non-scripts should have ignoreUnknownInstances set
let should_set_ignore = true; // All instances in scripts-only mode
@@ -2868,18 +2968,18 @@ mod tests {
expected_extension: ".luau",
description: "ModuleScript without RunContext",
},
- // LocalScript - always .client.luau
+ // LocalScript - always .local.luau
TestCase {
class_name: "LocalScript",
run_context: None,
- expected_extension: ".client.luau",
+ expected_extension: ".local.luau",
description: "LocalScript",
},
// Script with various RunContext values
TestCase {
class_name: "Script",
run_context: None,
- expected_extension: ".server.luau",
+ expected_extension: ".legacy.luau",
description: "Script without RunContext (default)",
},
TestCase {
@@ -2897,7 +2997,7 @@ mod tests {
TestCase {
class_name: "Script",
run_context: Some("Legacy"),
- expected_extension: ".server.luau",
+ expected_extension: ".legacy.luau",
description: "Script with RunContext::Legacy",
},
TestCase {
@@ -2911,16 +3011,16 @@ mod tests {
for case in test_cases {
let suffix = match case.class_name {
"ModuleScript" => String::new(),
- "LocalScript" => "client".to_string(),
+ "LocalScript" => "local".to_string(),
"Script" => {
// Match on RunContext name to determine suffix
match case.run_context {
Some("Client") => "client",
Some("Server") => "server",
- Some("Legacy") => "server",
+ Some("Legacy") => "legacy",
Some("Plugin") => "plugin",
- None => "server",
- _ => "server",
+ None => "legacy",
+ _ => "legacy",
}
.to_string()
}
@@ -3003,4 +3103,472 @@ mod tests {
}
}
}
+
+ // =========================================================================
+ // Duplicate Path Detection Tests
+ // =========================================================================
+ mod duplicate_path_tests {
+ // Helper to simulate sibling name checking
+ fn has_duplicate_sibling_names(names: &[&str], target_name: &str) -> bool {
+ let count = names.iter().filter(|&&n| n == target_name).count();
+ count > 1
+ }
+
+ // Simulates the duplicate children filter logic
+ fn filter_duplicate_names<'a>(names: &[&'a str]) -> Vec<&'a str> {
+ let mut counts: std::collections::HashMap<&str, usize> =
+ std::collections::HashMap::new();
+ for name in names {
+ *counts.entry(name).or_insert(0) += 1;
+ }
+
+ names
+ .iter()
+ .filter(|&&name| counts.get(name).copied().unwrap_or(0) <= 1)
+ .copied()
+ .collect()
+ }
+
+ #[test]
+ fn test_no_duplicates_all_pass() {
+ let names = vec!["Script1", "Script2", "Script3", "Folder"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(filtered.len(), 4, "All unique names should pass");
+ }
+
+ #[test]
+ fn test_duplicate_siblings_filtered() {
+ let names = vec!["Script", "Script", "Other"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate 'Script' entries should be filtered"
+ );
+ assert_eq!(filtered[0], "Other", "Only 'Other' should remain");
+ }
+
+ #[test]
+ fn test_multiple_duplicates_filtered() {
+ let names = vec!["A", "A", "B", "B", "C"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(filtered.len(), 1, "Only unique 'C' should remain");
+ assert_eq!(filtered[0], "C");
+ }
+
+ #[test]
+ fn test_all_duplicates_empty_result() {
+ let names = vec!["Same", "Same", "Same"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 0,
+ "All duplicates should result in empty list"
+ );
+ }
+
+ #[test]
+ fn test_sibling_detection_positive() {
+ let siblings = vec!["Script", "Script", "Other"];
+ assert!(
+ has_duplicate_sibling_names(&siblings, "Script"),
+ "'Script' should be detected as having duplicate siblings"
+ );
+ }
+
+ #[test]
+ fn test_sibling_detection_negative() {
+ let siblings = vec!["Script", "Folder", "Model"];
+ assert!(
+ !has_duplicate_sibling_names(&siblings, "Script"),
+ "'Script' should NOT be detected as duplicate with unique siblings"
+ );
+ }
+
+ #[test]
+ fn test_sibling_detection_not_in_list() {
+ let siblings = vec!["A", "B", "C"];
+ assert!(
+ !has_duplicate_sibling_names(&siblings, "D"),
+ "'D' not in list should not have duplicates"
+ );
+ }
+
+ #[test]
+ fn test_path_uniqueness_concept() {
+ // Test the concept: path is unique only if NO level has duplicates
+ //
+ // Tree:
+ // Root
+ // ├─ FolderA
+ // │ └─ Script
+ // └─ FolderA (duplicate!)
+ // └─ Script
+ //
+ // Path "Root/FolderA/Script" is NOT unique because FolderA is duplicated
+
+ let root_children = vec!["FolderA", "FolderA"];
+ let folder_children = vec!["Script"];
+
+ // Check at root level - duplicates exist
+ let has_dup_at_root = has_duplicate_sibling_names(&root_children, "FolderA");
+ assert!(
+ has_dup_at_root,
+ "FolderA should be detected as duplicate at root level"
+ );
+
+ // Check at folder level - no duplicates
+ let has_dup_at_folder = has_duplicate_sibling_names(&folder_children, "Script");
+ assert!(
+ !has_dup_at_folder,
+ "Script has no duplicate siblings within its parent"
+ );
+
+ // But the PATH is still not unique because of the ancestor duplicate
+ let path_is_unique = !has_dup_at_root;
+ assert!(
+ !path_is_unique,
+ "Path should NOT be unique due to ancestor duplicate"
+ );
+ }
+
+ #[test]
+ fn test_path_uniqueness_deep_hierarchy() {
+ // Tree:
+ // Root (unique)
+ // └─ Level1 (unique)
+ // └─ Level2 (has duplicate sibling)
+ // └─ Target
+ //
+ // Path to Target is NOT unique
+
+ let level1_children = vec!["Level2", "Level2", "Other"];
+ let level2_children = vec!["Target"];
+
+ let has_dup_at_level1 = has_duplicate_sibling_names(&level1_children, "Level2");
+ assert!(has_dup_at_level1, "Level2 has duplicate at Level1");
+
+ // Even though Target itself has no duplicates, path is not unique
+ let has_dup_at_level2 = has_duplicate_sibling_names(&level2_children, "Target");
+ assert!(!has_dup_at_level2, "Target has no duplicate at Level2");
+
+ // Path uniqueness requires ALL levels to be unique
+ let path_unique = !has_dup_at_level1 && !has_dup_at_level2;
+ assert!(
+ !path_unique,
+ "Path should NOT be unique due to Level2 duplicate"
+ );
+ }
+
+ #[test]
+ fn test_path_uniqueness_fully_unique() {
+ // Tree:
+ // Root
+ // └─ Folder (unique)
+ // └─ SubFolder (unique)
+ // └─ Script (unique)
+
+ let root_children = vec!["Folder"];
+ let folder_children = vec!["SubFolder"];
+ let sub_children = vec!["Script"];
+
+ let root_unique = !has_duplicate_sibling_names(&root_children, "Folder");
+ let folder_unique = !has_duplicate_sibling_names(&folder_children, "SubFolder");
+ let sub_unique = !has_duplicate_sibling_names(&sub_children, "Script");
+
+ let path_unique = root_unique && folder_unique && sub_unique;
+ assert!(
+ path_unique,
+ "Path should be unique when all levels are unique"
+ );
+ }
+
+ #[test]
+ fn test_case_sensitive_duplicates() {
+ // Roblox names are case-sensitive, so "Script" and "script" are different
+ let names = vec!["Script", "script", "SCRIPT"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 3,
+ "Case-different names should all be kept (case-sensitive)"
+ );
+ }
+
+ #[test]
+ fn test_empty_names_handled() {
+ let names = vec!["", "", "Valid"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Empty string duplicates should be filtered"
+ );
+ assert_eq!(filtered[0], "Valid");
+ }
+
+ #[test]
+ fn test_special_characters_in_names() {
+ // Names with special characters that might cause filesystem issues
+ // are handled separately by validate_file_name, but duplicates should still work
+ let names = vec!["Script (1)", "Script (2)", "Script (1)"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Only 'Script (2)' should remain after filtering duplicates"
+ );
+ assert_eq!(filtered[0], "Script (2)");
+ }
+
+ #[test]
+ fn test_whitespace_names() {
+ // Names with leading/trailing whitespace are technically different
+ let names = vec!["Script", " Script", "Script "];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 3,
+ "Whitespace-different names should all be kept"
+ );
+ }
+
+ // =====================================================================
+ // Non-Script Duplicate Tests - Duplicates apply to ALL instance types
+ // =====================================================================
+
+ #[test]
+ fn test_duplicate_folders_filtered() {
+ // Duplicate Folders should be filtered just like scripts
+ let names = vec!["MyFolder", "MyFolder", "OtherFolder"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(filtered.len(), 1, "Duplicate 'MyFolder' should be filtered");
+ assert_eq!(filtered[0], "OtherFolder");
+ }
+
+ #[test]
+ fn test_duplicate_parts_filtered() {
+ // Duplicate Parts should be filtered
+ let names = vec!["Part", "Part", "Baseplate"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(filtered.len(), 1, "Duplicate 'Part' should be filtered");
+ assert_eq!(filtered[0], "Baseplate");
+ }
+
+ #[test]
+ fn test_duplicate_models_filtered() {
+ // Duplicate Models should be filtered
+ let names = vec!["Car", "Car", "House"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate 'Car' models should be filtered"
+ );
+ }
+
+ #[test]
+ fn test_duplicate_values_filtered() {
+ // Duplicate IntValues/StringValues should be filtered
+ let names = vec!["Score", "Score", "Health"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate 'Score' values should be filtered"
+ );
+ }
+
+ #[test]
+ fn test_duplicate_gui_elements_filtered() {
+ // Duplicate GUI elements should be filtered
+ let names = vec!["MainFrame", "MainFrame", "Sidebar"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate 'MainFrame' GUI should be filtered"
+ );
+ }
+
+ #[test]
+ fn test_mixed_types_same_name_filtered() {
+ // Even if types differ, same names are duplicates (filesystem perspective)
+ // E.g., a Folder named "Data" and a Script named "Data" would conflict
+ let names = vec!["Data", "Data"]; // Could be Folder + Script
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 0,
+ "Same-named instances of different types still conflict"
+ );
+ }
+
+ #[test]
+ fn test_folder_hierarchy_duplicates() {
+ // Duplicates in folder hierarchies
+ // Root/
+ // ├─ Enemies/
+ // │ └─ Enemy (unique)
+ // └─ Enemies/ (duplicate!)
+ // └─ Enemy
+
+ let root_children = vec!["Enemies", "Enemies", "Players"];
+ let enemies_unique = !has_duplicate_sibling_names(&root_children, "Enemies");
+ assert!(
+ !enemies_unique,
+ "Duplicate 'Enemies' folders should be detected"
+ );
+ }
+
+ #[test]
+ fn test_part_ancestor_duplicates() {
+ // A Part deep in hierarchy with duplicate ancestor
+ // Workspace/
+ // ├─ Zone/
+ // │ └─ SpawnPoint (Part)
+ // └─ Zone/ (duplicate!)
+ // └─ SpawnPoint (Part)
+
+ let workspace_children = vec!["Zone", "Zone"];
+ let zone_unique = !has_duplicate_sibling_names(&workspace_children, "Zone");
+ assert!(
+ !zone_unique,
+ "Duplicate 'Zone' ancestors make Part paths ambiguous"
+ );
+ }
+
+ #[test]
+ fn test_remote_event_duplicates() {
+ // RemoteEvents with duplicate names
+ let names = vec!["FireBullet", "FireBullet", "TakeDamage"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate RemoteEvents should be filtered"
+ );
+ }
+
+ #[test]
+ fn test_configuration_duplicates() {
+ // Configuration instances with duplicates
+ let names = vec!["Settings", "Settings", "Config"];
+ let filtered = filter_duplicate_names(&names);
+ assert_eq!(
+ filtered.len(),
+ 1,
+ "Duplicate Configurations should be filtered"
+ );
+ }
+ }
+
+ // =========================================================================
+ // Duplicate Path Detection - Integration Scenarios
+ // =========================================================================
+ mod duplicate_path_integration_tests {
+
+ #[test]
+ fn test_syncback_skips_duplicate_children() {
+ // When syncing back children, duplicates should be skipped
+ // This test verifies the filter_duplicate_children logic
+
+ let children_names = vec!["ChildA", "ChildB", "ChildA", "ChildC"];
+ let unique: Vec<_> = children_names
+ .iter()
+ .filter(|&&name| children_names.iter().filter(|&&n| n == name).count() == 1)
+ .collect();
+
+ assert_eq!(unique.len(), 2, "ChildB and ChildC should be kept");
+ assert!(unique.contains(&&"ChildB"));
+ assert!(unique.contains(&&"ChildC"));
+ }
+
+ #[test]
+ fn test_recursive_duplicate_handling() {
+ // Test that duplicates at any level are handled
+ //
+ // Parent/
+ // ├─ Child1/
+ // │ ├─ GrandChild (duplicate)
+ // │ └─ GrandChild (duplicate)
+ // └─ Child2/
+ // └─ GrandChild (unique within its parent)
+
+ let child1_grandchildren = vec!["GrandChild", "GrandChild"];
+ let child2_grandchildren = vec!["GrandChild"];
+
+ // Child1's grandchildren have duplicates
+ let child1_filtered: Vec<_> = child1_grandchildren
+ .iter()
+ .filter(|&&name| child1_grandchildren.iter().filter(|&&n| n == name).count() == 1)
+ .collect();
+ assert_eq!(
+ child1_filtered.len(),
+ 0,
+ "Child1's duplicate grandchildren filtered"
+ );
+
+ // Child2's grandchild is unique
+ let child2_filtered: Vec<_> = child2_grandchildren
+ .iter()
+ .filter(|&&name| child2_grandchildren.iter().filter(|&&n| n == name).count() == 1)
+ .collect();
+ assert_eq!(child2_filtered.len(), 1, "Child2's unique grandchild kept");
+ }
+
+ #[test]
+ fn test_duplicate_log_message_format() {
+ // Verify the expected log message format for skipped duplicates
+ let class_name = "ModuleScript";
+ let full_name = "game.ReplicatedStorage.DuplicateScript";
+
+ let expected_msg = format!(
+ "Skipped instance '{}' ({}) - path contains duplicate-named siblings (cannot reliably sync)",
+ full_name, class_name
+ );
+
+ assert!(expected_msg.contains(full_name));
+ assert!(expected_msg.contains(class_name));
+ assert!(expected_msg.contains("duplicate-named siblings"));
+ }
+
+ #[test]
+ fn test_parent_path_validation() {
+ // When pulling an instance, we must verify the parent path is unique
+ // This simulates the is_tree_path_unique check
+
+ // Scenario: Trying to add a script under a folder that has duplicate siblings
+ let parent_path_levels = vec![
+ ("Root", vec!["FolderA", "FolderA"]), // Duplicate at this level!
+ ];
+
+ let path_unique = parent_path_levels
+ .iter()
+ .all(|(target, siblings)| siblings.iter().filter(|&&s| s == *target).count() == 1);
+
+ assert!(
+ !path_unique,
+ "Parent path with duplicates should fail validation"
+ );
+ }
+
+ #[test]
+ fn test_valid_parent_path() {
+ // Valid parent path with no duplicates at any level
+ let parent_path_levels = vec![
+ (
+ "ReplicatedStorage",
+ vec!["Workspace", "ReplicatedStorage", "ServerStorage"],
+ ),
+ ("Shared", vec!["Shared", "Client", "Server"]),
+ ];
+
+ let path_unique = parent_path_levels
+ .iter()
+ .all(|(target, siblings)| siblings.iter().filter(|&&s| s == *target).count() == 1);
+
+ assert!(path_unique, "Valid parent path should pass validation");
+ }
+ }
}
diff --git a/tests/tests/build.rs b/tests/tests/build.rs
index 8c533115..d44196ea 100644
--- a/tests/tests/build.rs
+++ b/tests/tests/build.rs
@@ -44,7 +44,6 @@ gen_build_tests! {
json_model_legacy_name,
module_in_folder,
module_init,
- nested_runcontext,
optional,
project_composed_default,
project_composed_file,