use std::{
borrow::Cow,
collections::{BTreeMap, HashMap, VecDeque},
path::Path,
};
use anyhow::{bail, Context};
use memofs::Vfs;
use rbx_dom_weak::{
types::{Attributes, Ref, Variant},
ustr, HashMapExt as _, Instance, Ustr, UstrMap,
};
use rbx_reflection::ClassTag;
use crate::{
project::{PathNode, Project, ProjectNode},
resolution::UnresolvedValue,
snapshot::{
InstanceContext, InstanceMetadata, InstanceSnapshot, InstanceWithMeta, InstigatingSource,
PathIgnoreRule, SyncRule,
},
snapshot_middleware::Middleware,
syncback::{filter_properties, inst_path, FsSnapshot, SyncbackReturn, SyncbackSnapshot},
variant_eq::variant_eq,
RojoRef,
};
use super::snapshot_from_vfs;
/// Checks if a class transition is recoverable in clean mode.
///
/// Recoverable transitions:
/// - Folder -> Script (creates init.luau)
/// - Script -> Folder (removes init.luau)
/// - Script -> Script (changes init.server/client.luau type)
fn can_transition_class(old_class: &str, new_class: &str) -> bool {
let is_script = |c: &str| matches!(c, "ModuleScript" | "Script" | "LocalScript");
let is_folder = |c: &str| c == "Folder";
// Folder can become a script (by creating init file)
if is_folder(old_class) && is_script(new_class) {
return true;
}
// Script can become Folder (by removing init file - handled by orphan removal)
if is_script(old_class) && is_folder(new_class) {
return true;
}
// Script can change type (e.g., ModuleScript -> LocalScript)
if is_script(old_class) && is_script(new_class) {
return true;
}
false
}
/// Determines the correct script-dir middleware for a Script based on its RunContext property.
///
/// Script class has a RunContext property that determines the correct middleware:
/// - Server → ServerScriptDir (init.server.luau)
/// - Client → ClientScriptDir (init.client.luau)
/// - Plugin → PluginScriptDir (init.plugin.luau)
/// - Legacy → LegacyScriptDir (init.legacy.luau)
///
/// If RunContext is not set or unrecognized, defaults to LegacyScriptDir.
fn middleware_for_script(inst: &Instance) -> Middleware {
let run_context_enums = rbx_reflection_database::get()
.ok()
.and_then(|db| db.enums.get("RunContext"))
.map(|e| &e.items);
let run_context_value = inst
.properties
.get(&ustr("RunContext"))
.and_then(|v| match v {
Variant::Enum(e) => Some(e.to_u32()),
_ => None,
});
if let (Some(enums), Some(value)) = (run_context_enums, run_context_value) {
for (name, &enum_value) in enums {
if enum_value == value {
return match *name {
"Server" => Middleware::ServerScriptDir,
"Client" => Middleware::ClientScriptDir,
"Plugin" => Middleware::PluginScriptDir,
"Legacy" => Middleware::LegacyScriptDir,
_ => Middleware::LegacyScriptDir,
};
}
}
}
// Default to LegacyScriptDir if no RunContext or unrecognized
Middleware::LegacyScriptDir
}
pub fn snapshot_project(
context: &InstanceContext,
vfs: &Vfs,
path: &Path,
name: &str,
) -> anyhow::Result