Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Making a new release? Simply add the new header with the version and date undern

## Unreleased

* Added support for `.jsonc` files for all JSON-related files (e.g. `.project.jsonc` and `.meta.jsonc`) to accompany JSONC support ([#1159])

[#1159]: https://github.com/rojo-rbx/rojo/pull/1159

## [7.6.1] (November 6th, 2025)

* Fixed a bug where the last sync timestamp was not updating correctly in the plugin ([#1132])
Expand Down
72 changes: 50 additions & 22 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use thiserror::Error;

use crate::{glob::Glob, json, resolution::UnresolvedValue, snapshot::SyncRule};

static PROJECT_FILENAME: &str = "default.project.json";
/// Represents 'default' project names that act as `init` files
pub static DEFAULT_PROJECT_NAMES: [&str; 2] = ["default.project.json", "default.project.jsonc"];

/// Error type returned by any function that handles projects.
#[derive(Debug, Error)]
Expand Down Expand Up @@ -131,7 +132,7 @@ impl Project {
pub fn is_project_file(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| name.ends_with(".project.json"))
.map(|name| name.ends_with(".project.json") || name.ends_with(".project.jsonc"))
.unwrap_or(false)
}

Expand All @@ -149,18 +150,19 @@ impl Project {
None
}
} else {
let child_path = path.join(PROJECT_FILENAME);
let child_meta = fs::metadata(&child_path).ok()?;
for filename in DEFAULT_PROJECT_NAMES {
let child_path = path.join(filename);
let child_meta = fs::metadata(&child_path).ok()?;

if child_meta.is_file() {
Some(child_path)
} else {
// This is a folder with the same name as a Rojo default project
// file.
//
// That's pretty weird, but we can roll with it.
None
if child_meta.is_file() {
return Some(child_path);
}
}
// This is a folder with the same name as a Rojo default project
// file.
//
// That's pretty weird, but we can roll with it.
None
}
}

Expand All @@ -181,16 +183,20 @@ impl Project {

// If you're editing this to be generic, make sure you also alter the
// snapshot middleware to support generic init paths.
if file_name == PROJECT_FILENAME {
let folder_name = self.folder_location().file_name().and_then(OsStr::to_str);
if let Some(folder_name) = folder_name {
self.name = Some(folder_name.to_string());
} else {
return Err(Error::FolderNameInvalid {
path: self.file_location.clone(),
});
for default_file_name in DEFAULT_PROJECT_NAMES {
if file_name == default_file_name {
let folder_name = self.folder_location().file_name().and_then(OsStr::to_str);
if let Some(folder_name) = folder_name {
self.name = Some(folder_name.to_string());
return Ok(());
} else {
return Err(Error::FolderNameInvalid {
path: self.file_location.clone(),
});
}
}
} else if let Some(fallback) = fallback {
}
if let Some(fallback) = fallback {
self.name = Some(fallback.to_string());
} else {
// As of the time of writing (July 10, 2024) there is no way for
Expand Down Expand Up @@ -257,6 +263,10 @@ impl Project {
project_file_location: &Path,
fallback_name: Option<&str>,
) -> Result<Self, ProjectError> {
log::debug!(
"Loading project file from {}",
project_file_location.display()
);
let project_path = project_file_location.to_path_buf();
let contents = vfs.read(&project_path).map_err(|e| match e.kind() {
io::ErrorKind::NotFound => Error::NoProjectFound {
Expand All @@ -272,6 +282,24 @@ impl Project {
)?)
}

pub(crate) fn load_initial_project(vfs: &Vfs, path: &Path) -> Result<Self, ProjectError> {
if Self::is_project_file(path) {
Self::load_exact(vfs, path, None)
} else {
// Check for default projects.
for default_project_name in DEFAULT_PROJECT_NAMES {
let project_path = path.join(default_project_name);
if project_path.exists() {
return Self::load_exact(vfs, &project_path, None);
}
}
Err(Error::NoProjectFound {
path: path.to_path_buf(),
}
.into())
}
}

/// Checks if there are any compatibility issues with this project file and
/// warns the user if there are any.
fn check_compatibility(&self) {
Expand Down Expand Up @@ -530,7 +558,7 @@ mod test {

let project = Project::load_from_slice(
project_json.as_bytes(),
PathBuf::from("/test/default.project.json"),
PathBuf::from("/test/default.project.jsonc"),
None,
)
.expect("Failed to parse project with JSONC features");
Expand Down
11 changes: 1 addition & 10 deletions src/serve_session.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
borrow::Cow,
collections::HashSet,
io,
net::IpAddr,
Expand Down Expand Up @@ -101,15 +100,7 @@ impl ServeSession {

log::trace!("Starting new ServeSession at path {}", start_path.display());

let project_path = if Project::is_project_file(start_path) {
Cow::Borrowed(start_path)
} else {
Cow::Owned(start_path.join("default.project.json"))
};

log::debug!("Loading project file from {}", project_path.display());

let root_project = Project::load_exact(&vfs, &project_path, None)?;
let root_project = Project::load_initial_project(&vfs, start_path)?;

let mut tree = RojoTree::new(InstanceSnapshot::new());

Expand Down
92 changes: 77 additions & 15 deletions src/snapshot_middleware/csv.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
use std::{collections::BTreeMap, path::Path};

use anyhow::Context;
use memofs::{IoResultExt, Vfs};
use memofs::Vfs;
use rbx_dom_weak::ustr;
use serde::Serialize;

use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};

use super::{
dir::{dir_meta, snapshot_dir_no_meta},
meta_file::AdjacentMetadata,
use crate::{
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot},
snapshot_middleware::meta_file::DirectoryMetadata,
};

use super::{dir::snapshot_dir_no_meta, meta_file::AdjacentMetadata};

pub fn snapshot_csv(
_context: &InstanceContext,
vfs: &Vfs,
path: &Path,
name: &str,
) -> anyhow::Result<Option<InstanceSnapshot>> {
let meta_path = path.with_file_name(format!("{}.meta.json", name));
let contents = vfs.read(path)?;

let table_contents = convert_localization_csv(&contents).with_context(|| {
Expand All @@ -35,13 +34,10 @@ pub fn snapshot_csv(
.metadata(
InstanceMetadata::new()
.instigating_source(path)
.relevant_paths(vec![path.to_path_buf(), meta_path.clone()]),
.relevant_paths(vec![path.to_path_buf()]),
);

if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
metadata.apply_all(&mut snapshot)?;
}
AdjacentMetadata::read_and_apply_all(vfs, path, name, &mut snapshot)?;

Ok(Some(snapshot))
}
Expand Down Expand Up @@ -75,9 +71,7 @@ pub fn snapshot_csv_init(
init_snapshot.children = dir_snapshot.children;
init_snapshot.metadata = dir_snapshot.metadata;

if let Some(mut meta) = dir_meta(vfs, folder_path)? {
meta.apply_all(&mut init_snapshot)?;
}
DirectoryMetadata::read_and_apply_all(vfs, folder_path, &mut init_snapshot)?;

Ok(Some(init_snapshot))
}
Expand Down Expand Up @@ -223,4 +217,72 @@ Ack,Ack!,,An exclamation of despair,¡Ay!"#,

insta::assert_yaml_snapshot!(instance_snapshot);
}

#[test]
fn csv_init() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/root",
VfsSnapshot::dir([(
"init.csv",
VfsSnapshot::file(
r#"
Key,Source,Context,Example,es
Ack,Ack!,,An exclamation of despair,¡Ay!"#,
),
)]),
)
.unwrap();

let vfs = Vfs::new(imfs);

let instance_snapshot = snapshot_csv_init(
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&vfs,
Path::new("/root/init.csv"),
)
.unwrap()
.unwrap();

insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}

#[test]
fn csv_init_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/root",
VfsSnapshot::dir([
(
"init.csv",
VfsSnapshot::file(
r#"
Key,Source,Context,Example,es
Ack,Ack!,,An exclamation of despair,¡Ay!"#,
),
),
(
"init.meta.json",
VfsSnapshot::file(r#"{"id": "manually specified"}"#),
),
]),
)
.unwrap();

let vfs = Vfs::new(imfs);

let instance_snapshot = snapshot_csv_init(
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&vfs,
Path::new("/root/init.csv"),
)
.unwrap()
.unwrap();

insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
}
22 changes: 2 additions & 20 deletions src/snapshot_middleware/dir.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::Path;

use memofs::{DirEntry, IoResultExt, Vfs};
use memofs::{DirEntry, Vfs};

use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};

Expand All @@ -16,26 +16,11 @@ pub fn snapshot_dir(
None => return Ok(None),
};

if let Some(mut meta) = dir_meta(vfs, path)? {
meta.apply_all(&mut snapshot)?;
}
DirectoryMetadata::read_and_apply_all(vfs, path, &mut snapshot)?;

Ok(Some(snapshot))
}

/// Retrieves the meta file that should be applied for this directory, if it
/// exists.
pub fn dir_meta(vfs: &Vfs, path: &Path) -> anyhow::Result<Option<DirectoryMetadata>> {
let meta_path = path.join("init.meta.json");

if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
let metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
Ok(Some(metadata))
} else {
Ok(None)
}
}

/// Snapshot a directory without applying meta files; useful for if the
/// directory's ClassName will change before metadata should be applied. For
/// example, this can happen if the directory contains an `init.client.lua`
Expand Down Expand Up @@ -73,11 +58,8 @@ pub fn snapshot_dir_no_meta(
.ok_or_else(|| anyhow::anyhow!("File name was not valid UTF-8: {}", path.display()))?
.to_string();

let meta_path = path.join("init.meta.json");

let relevant_paths = vec![
path.to_path_buf(),
meta_path,
// TODO: We shouldn't need to know about Lua existing in this
// middleware. Should we figure out a way for that function to add
// relevant paths to this middleware?
Expand Down
Loading
Loading