Implement Syncback to support converting Roblox files to a Rojo project (#937)

This is a very large commit.
Consider checking the linked PR for more information.
This commit is contained in:
Micah
2025-11-19 09:21:33 -08:00
committed by GitHub
parent 071b6e7e23
commit 9b5a07191b
239 changed files with 5325 additions and 225 deletions

View File

@@ -1,11 +1,22 @@
use std::{path::Path, str};
use anyhow::Context as _;
use memofs::Vfs;
use rbx_dom_weak::{types::Enum, ustr, HashMapExt as _, UstrMap};
use rbx_dom_weak::{
types::{Enum, Variant},
ustr, HashMapExt as _, UstrMap,
};
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use crate::{
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot},
syncback::{FsSnapshot, SyncbackReturn, SyncbackSnapshot},
};
use super::{dir::snapshot_dir_no_meta, meta_file::AdjacentMetadata, meta_file::DirectoryMetadata};
use super::{
dir::{snapshot_dir_no_meta, syncback_dir_no_meta},
meta_file::{AdjacentMetadata, DirectoryMetadata},
PathExt as _,
};
#[derive(Debug)]
pub enum ScriptType {
@@ -95,10 +106,11 @@ pub fn snapshot_lua_init(
context: &InstanceContext,
vfs: &Vfs,
init_path: &Path,
name: &str,
script_type: ScriptType,
) -> anyhow::Result<Option<InstanceSnapshot>> {
let folder_path = init_path.parent().unwrap();
let dir_snapshot = snapshot_dir_no_meta(context, vfs, folder_path)?.unwrap();
let dir_snapshot = snapshot_dir_no_meta(context, vfs, folder_path, name)?.unwrap();
if dir_snapshot.class_name != "Folder" {
anyhow::bail!(
@@ -117,12 +129,89 @@ pub fn snapshot_lua_init(
init_snapshot.children = dir_snapshot.children;
init_snapshot.metadata = dir_snapshot.metadata;
// The directory snapshot middleware includes all possible init paths
// so we don't need to add it here.
DirectoryMetadata::read_and_apply_all(vfs, folder_path, &mut init_snapshot)?;
Ok(Some(init_snapshot))
}
pub fn syncback_lua<'sync>(
snapshot: &SyncbackSnapshot<'sync>,
) -> anyhow::Result<SyncbackReturn<'sync>> {
let new_inst = snapshot.new_inst();
let contents = if let Some(Variant::String(source)) = new_inst.properties.get(&ustr("Source")) {
source.as_bytes().to_vec()
} else {
anyhow::bail!("Scripts must have a `Source` property that is a String")
};
let mut fs_snapshot = FsSnapshot::new();
fs_snapshot.add_file(&snapshot.path, contents);
let meta = AdjacentMetadata::from_syncback_snapshot(snapshot, snapshot.path.clone())?;
if let Some(mut meta) = meta {
// Scripts have relatively few properties that we care about, so shifting
// is fine.
meta.properties.shift_remove(&ustr("Source"));
if !meta.is_empty() {
let parent_location = snapshot.path.parent_err()?;
fs_snapshot.add_file(
parent_location.join(format!("{}.meta.json", new_inst.name)),
serde_json::to_vec_pretty(&meta).context("cannot serialize metadata")?,
);
}
}
Ok(SyncbackReturn {
fs_snapshot,
// Scripts don't have a child!
children: Vec::new(),
removed_children: Vec::new(),
})
}
pub fn syncback_lua_init<'sync>(
script_type: ScriptType,
snapshot: &SyncbackSnapshot<'sync>,
) -> anyhow::Result<SyncbackReturn<'sync>> {
let new_inst = snapshot.new_inst();
let path = snapshot.path.join(match script_type {
ScriptType::Server => "init.server.luau",
ScriptType::Client => "init.client.luau",
ScriptType::Module => "init.luau",
_ => anyhow::bail!("syncback is not yet implemented for {script_type:?}"),
});
let contents = if let Some(Variant::String(source)) = new_inst.properties.get(&ustr("Source")) {
source.as_bytes().to_vec()
} else {
anyhow::bail!("Scripts must have a `Source` property that is a String")
};
let mut dir_syncback = syncback_dir_no_meta(snapshot)?;
dir_syncback.fs_snapshot.add_file(&path, contents);
let meta = DirectoryMetadata::from_syncback_snapshot(snapshot, path.clone())?;
if let Some(mut meta) = meta {
// Scripts have relatively few properties that we care about, so shifting
// is fine.
meta.properties.shift_remove(&ustr("Source"));
if !meta.is_empty() {
dir_syncback.fs_snapshot.add_file(
snapshot.path.join("init.meta.json"),
serde_json::to_vec_pretty(&meta)
.context("could not serialize new init.meta.json")?,
);
}
}
Ok(dir_syncback)
}
#[cfg(test)]
mod test {
use super::*;
@@ -305,6 +394,7 @@ mod test {
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&vfs,
Path::new("/root/init.lua"),
"root",
ScriptType::Module,
)
.unwrap()
@@ -336,6 +426,7 @@ mod test {
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&vfs,
Path::new("/root/init.lua"),
"root",
ScriptType::Module,
)
.unwrap()