mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Implement new oneshot Vfs for CLI commands and enhance JSON5 serialization
- Added `new_oneshot` method to the Vfs struct, providing a backend suitable for one-time operations without file watching. - Updated CLI commands (`fmt_project` and `syncback`) to utilize the new oneshot Vfs, improving error handling during file operations. - Enhanced JSON5 serialization by introducing a custom serializer that directly builds a tree structure, improving performance and maintaining sorted keys. - Improved error logging in JSON serialization to provide more context on failures.
This commit is contained in:
parent
fca802a4e5
commit
5f91a0777a
7 changed files with 3753 additions and 44 deletions
|
|
@ -259,6 +259,28 @@ impl Vfs {
|
|||
Self::new(StdBackend::new())
|
||||
}
|
||||
|
||||
/// Creates a new `Vfs` suitable for one-shot operations like syncback.
|
||||
///
|
||||
/// Unlike `new_default()`, this creates a backend that:
|
||||
/// - Has file watching disabled by default
|
||||
/// - Uses a non-fatal error handler for watcher issues (logs instead of exiting)
|
||||
///
|
||||
/// This is ideal for CLI commands that don't need real-time file watching
|
||||
/// and shouldn't be terminated if the watcher thread encounters issues.
|
||||
pub fn new_oneshot() -> Self {
|
||||
let backend = StdBackend::new_with_error_handler(Box::new(|err| {
|
||||
// Log the error but don't exit - one-shot operations don't need file watching
|
||||
log::debug!(
|
||||
"File watcher issue (non-fatal for one-shot operation): {}",
|
||||
err
|
||||
);
|
||||
true // Stop the watcher thread, but don't exit the process
|
||||
}));
|
||||
let vfs = Self::new(backend);
|
||||
vfs.set_watch_enabled(false);
|
||||
vfs
|
||||
}
|
||||
|
||||
/// Creates a new `Vfs` with the given backend.
|
||||
pub fn new<B: VfsBackend>(backend: B) -> Self {
|
||||
let lock = VfsInner {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ pub struct FmtProjectCommand {
|
|||
|
||||
impl FmtProjectCommand {
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
let vfs = Vfs::new_default();
|
||||
vfs.set_watch_enabled(false);
|
||||
// Use oneshot Vfs - file watching isn't needed for formatting
|
||||
let vfs = Vfs::new_oneshot();
|
||||
|
||||
let base_path = resolve_path(&self.project);
|
||||
let project = Project::load_fuzzy(&vfs, &base_path)?
|
||||
|
|
|
|||
|
|
@ -69,8 +69,9 @@ impl SyncbackCommand {
|
|||
dom_start_timer.elapsed().as_secs_f32()
|
||||
);
|
||||
|
||||
let vfs = Vfs::new_default();
|
||||
vfs.set_watch_enabled(false);
|
||||
// Use oneshot Vfs for syncback - file watching isn't needed and
|
||||
// watcher errors shouldn't terminate the process
|
||||
let vfs = Vfs::new_oneshot();
|
||||
|
||||
let project_start_timer = Instant::now();
|
||||
let session_old = ServeSession::new(vfs, path_old.clone())?;
|
||||
|
|
|
|||
3698
src/json.rs
3698
src/json.rs
File diff suppressed because it is too large
Load diff
|
|
@ -83,12 +83,32 @@ pub fn syncback_json_model<'sync>(
|
|||
model.schema = old_inst.metadata().schema.clone();
|
||||
}
|
||||
|
||||
let serialized = match crate::json::to_vec_pretty_sorted(&model) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
// Provide more detail about what's in the model
|
||||
let prop_details: Vec<_> = model
|
||||
.properties
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={:?}", k.as_str(), v))
|
||||
.collect();
|
||||
let attr_names: Vec<_> = model.attributes.keys().map(|k| k.as_str()).collect();
|
||||
log::error!(
|
||||
"Failed to serialize JSON model for class '{}': properties=[{}], attributes={:?}, error: {}",
|
||||
model.class_name,
|
||||
prop_details.join(", "),
|
||||
attr_names,
|
||||
e
|
||||
);
|
||||
return Err(e.context(format!(
|
||||
"failed to serialize new JSON Model for class '{}'",
|
||||
model.class_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SyncbackReturn {
|
||||
fs_snapshot: FsSnapshot::new().with_added_file(
|
||||
&snapshot.path,
|
||||
crate::json::to_vec_pretty_sorted(&model)
|
||||
.context("failed to serialize new JSON Model")?,
|
||||
),
|
||||
fs_snapshot: FsSnapshot::new().with_added_file(&snapshot.path, serialized),
|
||||
children: Vec::new(),
|
||||
removed_children: Vec::new(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1823,14 +1823,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_float32() {
|
||||
let variant = Variant::Float32(3.14);
|
||||
let variant = Variant::Float32(3.125); // Use a non-constant value
|
||||
let result = variant_to_json(&variant);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float64() {
|
||||
let variant = Variant::Float64(2.71828);
|
||||
let variant = Variant::Float64(2.5); // Use a non-constant value
|
||||
let result = variant_to_json(&variant);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
|
|
@ -1959,7 +1959,7 @@ mod tests {
|
|||
// Configuration creates a directory instead
|
||||
let json5_types = vec!["Part", "Frame", "TextLabel", "Model"];
|
||||
for class_name in json5_types {
|
||||
let file_name = format!("Test.model.json5");
|
||||
let file_name = "Test.model.json5";
|
||||
assert!(
|
||||
file_name.ends_with(".model.json5"),
|
||||
"{} should create .model.json5 file",
|
||||
|
|
@ -2062,10 +2062,8 @@ mod tests {
|
|||
.map(|e| &e.items);
|
||||
|
||||
if let Some(enums) = run_context_enums {
|
||||
if let Some(&client_value) = enums.get("Client") {
|
||||
// Verify the enum value exists
|
||||
assert!(client_value > 0 || client_value == 0);
|
||||
}
|
||||
// Verify the enum value exists - just getting it is the test
|
||||
assert!(enums.get("Client").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2078,9 +2076,7 @@ mod tests {
|
|||
.map(|e| &e.items);
|
||||
|
||||
if let Some(enums) = run_context_enums {
|
||||
if let Some(&server_value) = enums.get("Server") {
|
||||
assert!(server_value > 0 || server_value == 0);
|
||||
}
|
||||
assert!(enums.get("Server").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2093,9 +2089,7 @@ mod tests {
|
|||
.map(|e| &e.items);
|
||||
|
||||
if let Some(enums) = run_context_enums {
|
||||
if let Some(&legacy_value) = enums.get("Legacy") {
|
||||
assert!(legacy_value > 0 || legacy_value == 0);
|
||||
}
|
||||
assert!(enums.get("Legacy").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2108,9 +2102,7 @@ mod tests {
|
|||
.map(|e| &e.items);
|
||||
|
||||
if let Some(enums) = run_context_enums {
|
||||
if let Some(&plugin_value) = enums.get("Plugin") {
|
||||
assert!(plugin_value > 0 || plugin_value == 0);
|
||||
}
|
||||
assert!(enums.get("Plugin").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3675,7 +3667,7 @@ mod tests {
|
|||
// 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 children_names = ["ChildA", "ChildB", "ChildA", "ChildC"];
|
||||
let unique: Vec<_> = children_names
|
||||
.iter()
|
||||
.filter(|&&name| children_names.iter().filter(|&&n| n == name).count() == 1)
|
||||
|
|
@ -3697,8 +3689,8 @@ mod tests {
|
|||
// └─ Child2/
|
||||
// └─ GrandChild (unique within its parent)
|
||||
|
||||
let child1_grandchildren = vec!["GrandChild", "GrandChild"];
|
||||
let child2_grandchildren = vec!["GrandChild"];
|
||||
let child1_grandchildren = ["GrandChild", "GrandChild"];
|
||||
let child2_grandchildren = ["GrandChild"];
|
||||
|
||||
// Child1's grandchildren have duplicates
|
||||
let child1_filtered: Vec<_> = child1_grandchildren
|
||||
|
|
@ -3741,7 +3733,7 @@ mod tests {
|
|||
// 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![
|
||||
let parent_path_levels = [
|
||||
("Root", vec!["FolderA", "FolderA"]), // Duplicate at this level!
|
||||
];
|
||||
|
||||
|
|
@ -3758,7 +3750,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_valid_parent_path() {
|
||||
// Valid parent path with no duplicates at any level
|
||||
let parent_path_levels = vec![
|
||||
let parent_path_levels = [
|
||||
(
|
||||
"ReplicatedStorage",
|
||||
vec!["Workspace", "ReplicatedStorage", "ServerStorage"],
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ impl<'a> Internable<&'a HashMap<Ref, Instance<'_>>> for Instance<'a> {
|
|||
|
||||
impl Internable<()> for SocketPacket<'_> {
|
||||
fn intern(&self, redactions: &mut RedactionMap, extra: ()) {
|
||||
redactions.intern(&self.session_id);
|
||||
redactions.intern(self.session_id);
|
||||
match &self.body {
|
||||
SocketPacketBody::Messages(packet) => packet.intern(redactions, extra),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue