Add init file discovery in JobThreadContext and enhance script renaming logic

- Introduced a new method `find_init_file` to locate the first `init.*.luau` or `init.*.lua` file within a directory-format script folder.
- Updated the script renaming logic to handle directory-format scripts correctly by renaming the init file instead of the directory itself.
- Enhanced logging to provide warnings when no init file is found, improving debugging capabilities during class name changes.
- Adjusted related file handling to ensure proper file path management during script transitions.
This commit is contained in:
Joe 2026-02-05 18:46:43 -06:00
parent f4f07744c7
commit f38c96e12b
2 changed files with 111 additions and 45 deletions

View file

@ -2,7 +2,7 @@ use crossbeam_channel::{select, Receiver, RecvError, Sender};
use jod_thread::JoinHandle;
use memofs::{IoResultExt, Vfs, VfsEvent};
use rbx_dom_weak::types::{Ref, Variant};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::{
fs,
@ -134,6 +134,31 @@ struct JobThreadContext {
}
impl JobThreadContext {
/// Find the init file inside a directory-format script folder.
/// Returns the path to the first `init.*.luau` or `init.*.lua` found.
fn find_init_file(dir: &Path) -> Option<PathBuf> {
// Check known init file names in priority order
let candidates = [
"init.luau",
"init.server.luau",
"init.client.luau",
"init.local.luau",
"init.plugin.luau",
"init.legacy.luau",
"init.lua",
"init.server.lua",
"init.client.lua",
"init.local.lua",
];
for name in &candidates {
let candidate = dir.join(name);
if candidate.exists() {
return Some(candidate);
}
}
None
}
/// Computes and applies patches to the DOM for a given file path.
///
/// This function finds the nearest ancestor to the given path that has associated instances
@ -538,8 +563,33 @@ impl JobThreadContext {
);
if old_is_script && new_is_script && path.exists() {
// Script-to-script transition: rename the file extension
if let Some(parent) = path.parent() {
// Script-to-script transition: rename the file extension.
// For directory-format scripts, the path is the directory
// (e.g., src/MyModule/), not the init file inside. We must
// find and rename the init file, not the directory.
let (actual_file, file_parent) = if path.is_dir() {
// Find the init file inside the directory
let init_file = Self::find_init_file(path);
match init_file {
Some(f) => (f.clone(), path.to_path_buf()),
None => {
log::warn!(
"Cannot change ClassName for directory {} \
— no init file found inside",
path.display()
);
continue;
}
}
} else {
(
path.clone(),
path.parent()
.unwrap_or(path.as_path())
.to_path_buf(),
)
};
let name = instance.name();
let new_suffix = match new_class.as_str() {
"ModuleScript" => "",
@ -547,7 +597,7 @@ impl JobThreadContext {
"LocalScript" => ".local",
_ => "",
};
let is_init = path
let is_init = actual_file
.file_name()
.and_then(|f| f.to_str())
.map(|f| f.starts_with("init."))
@ -565,26 +615,27 @@ impl JobThreadContext {
format!("{}{}.luau", name, new_suffix)
};
let new_path = parent.join(&new_file_name);
if new_path != *path {
let new_path = file_parent.join(&new_file_name);
if new_path != actual_file {
log::info!(
"Two-way sync: Changing class {} -> {}, \
renaming {} -> {}",
old_class,
new_class,
path.display(),
actual_file.display(),
new_path.display()
);
if let Err(err) = fs::rename(path, &new_path) {
if let Err(err) =
fs::rename(&actual_file, &new_path)
{
log::error!(
"Failed to rename {:?} to {:?}: {}",
path,
actual_file,
new_path,
err
);
}
}
}
} else if old_is_script != new_is_script {
log::warn!(
"Cannot change ClassName from {} to {} — \

View file

@ -2057,7 +2057,22 @@ impl ApiService {
.as_ref()
.context("Instance has no filesystem path")?;
let inst_path = instigating_source.path();
// ProjectNode instances are defined in the project file. Writing meta
// JSON to the project file path would corrupt it. Only filesystem-backed
// instances (InstigatingSource::Path) can have properties persisted.
let inst_path = match instigating_source {
InstigatingSource::Path(p) => p.as_path(),
InstigatingSource::ProjectNode { name, .. } => {
log::warn!(
"Cannot persist non-Source properties for instance '{}' (id: {:?}) — \
it is defined in a project file. Edit the project file directly.",
name,
update.id
);
return Ok(());
}
};
let class_name = instance.class_name();
let is_script = matches!(
class_name.as_str(),