Compare commits

...

4 Commits

Author SHA1 Message Date
Micah
441c469966 Release Rojo v7.6.0 (#1125) 2025-10-10 19:17:55 -07:00
Micah
f3c423d77d Fix the various lints (#1124) 2025-10-10 13:00:56 -07:00
Micah
beb497878b Add flag for skipping git initialization to init command (#1122) 2025-10-07 17:12:22 -07:00
Micah
6ea95d487c Refactor init command (#1117) 2025-09-30 14:38:38 -07:00
35 changed files with 7559 additions and 941 deletions

View File

@@ -60,7 +60,7 @@ jobs:
submodules: true submodules: true
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@1.79.0 uses: dtolnay/rust-toolchain@1.83.0
- name: Restore Rust Cache - name: Restore Rust Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4

View File

@@ -1,7 +1,7 @@
# Rojo Changelog # Rojo Changelog
## Unreleased ## 7.6.0 - October 10th, 2025
* Added flag to `rojo init` to skip initializing a git repository ([#1122])
* Added fallback method for when an Instance can't be synced through normal means ([#1030]) * Added fallback method for when an Instance can't be synced through normal means ([#1030])
This should make it possible to sync `MeshParts` and `Unions`! This should make it possible to sync `MeshParts` and `Unions`!
@@ -19,6 +19,7 @@
* Added `--absolute` flag to the sourcemap subcommand, which will emit absolute paths instead of relative paths. ([#1092]) * Added `--absolute` flag to the sourcemap subcommand, which will emit absolute paths instead of relative paths. ([#1092])
* Fixed applying `gameId` and `placeId` before initial sync was accepted ([#1104]) * Fixed applying `gameId` and `placeId` before initial sync was accepted ([#1104])
[#1122]: https://github.com/rojo-rbx/rojo/pull/1122
[#1030]: https://github.com/rojo-rbx/rojo/pull/1030 [#1030]: https://github.com/rojo-rbx/rojo/pull/1030
[#1096]: https://github.com/rojo-rbx/rojo/pull/1096 [#1096]: https://github.com/rojo-rbx/rojo/pull/1096
[#1093]: https://github.com/rojo-rbx/rojo/pull/1093 [#1093]: https://github.com/rojo-rbx/rojo/pull/1093

87
Cargo.lock generated
View File

@@ -430,7 +430,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
"dirs-sys", "dirs-sys 0.3.7",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys 0.4.1",
] ]
[[package]] [[package]]
@@ -444,6 +453,18 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.10.0" version = "1.10.0"
@@ -1093,23 +1114,12 @@ dependencies = [
] ]
[[package]] [[package]]
name = "lz4" name = "lz4_flex"
version = "1.24.0" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
dependencies = [ dependencies = [
"libc", "twox-hash",
"lz4-sys",
]
[[package]]
name = "lz4-sys"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
dependencies = [
"cc",
"libc",
] ]
[[package]] [[package]]
@@ -1301,6 +1311,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.6.1" version = "6.6.1"
@@ -1599,13 +1615,13 @@ dependencies = [
[[package]] [[package]]
name = "rbx_binary" name = "rbx_binary"
version = "1.0.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9573fee5e073d7b303f475c285197fdc8179468de66ca60ee115a58fbac99296" checksum = "0d419f67c8012bf83569086e1208c541478b3b8e4f523deaa0b80d723fb5ef22"
dependencies = [ dependencies = [
"ahash", "ahash",
"log", "log",
"lz4", "lz4_flex",
"profiling", "profiling",
"rbx_dom_weak", "rbx_dom_weak",
"rbx_reflection", "rbx_reflection",
@@ -1616,9 +1632,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_dom_weak" name = "rbx_dom_weak"
version = "3.0.0" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04425cf6e9376e5486f4fb35906c120d1b1b45618a490318cf563fab1fa230a9" checksum = "bc74878a4a801afc8014b14ede4b38015a13de5d29ab0095d5ed284a744253f6"
dependencies = [ dependencies = [
"ahash", "ahash",
"rbx_types", "rbx_types",
@@ -1628,9 +1644,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_reflection" name = "rbx_reflection"
version = "5.0.0" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6d0d62baa613556b058a5f94a53b01cf0ccde0ea327ce03056e335b982e77e" checksum = "565dd3430991f35443fa6d23cc239fade2110c5089deb6bae5de77c400df4fd2"
dependencies = [ dependencies = [
"rbx_types", "rbx_types",
"serde", "serde",
@@ -1639,11 +1655,12 @@ dependencies = [
[[package]] [[package]]
name = "rbx_reflection_database" name = "rbx_reflection_database"
version = "1.0.3+roblox-670" version = "2.0.0+roblox-694"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22c05ef92528c0fb0cc580592a65ca178d3ea9beb07a1d9ca0a2503c4f3721c" checksum = "844ceb61f23bad59b06d7299b69ff276579316eafa9857981da3012a6223f663"
dependencies = [ dependencies = [
"lazy_static", "dirs 5.0.1",
"log",
"rbx_reflection", "rbx_reflection",
"rmp-serde", "rmp-serde",
"serde", "serde",
@@ -1651,9 +1668,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_types" name = "rbx_types"
version = "2.0.0" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78e4fdde46493def107e5f923d82e813dec9b3eef52c2f75fbad3a716023eda2" checksum = "03220ffce2bd06ad04f77a003cb807f2e5b2a18e97623066a5ac735a978398af"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"bitflags 1.3.2", "bitflags 1.3.2",
@@ -1666,9 +1683,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_xml" name = "rbx_xml"
version = "1.0.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb623833c31cc43bbdaeb32f5e91db8ecd63fc46e438d0d268baf9e61539cf1c" checksum = "be6c302cefe9c92ed09bcbb075cd24379271de135b0af331409a64c2ea3646ee"
dependencies = [ dependencies = [
"ahash", "ahash",
"base64 0.13.1", "base64 0.13.1",
@@ -1860,14 +1877,14 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bb8c693a387f1ae8d2026d82d8b0c175cc4777b97c1f7b12fdb3be595bb13" checksum = "743bb8c693a387f1ae8d2026d82d8b0c175cc4777b97c1f7b12fdb3be595bb13"
dependencies = [ dependencies = [
"dirs", "dirs 2.0.2",
"thiserror", "thiserror",
"winreg 0.6.2", "winreg 0.6.2",
] ]
[[package]] [[package]]
name = "rojo" name = "rojo"
version = "7.5.1" version = "7.6.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"backtrace", "backtrace",
@@ -2454,6 +2471,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "twox-hash"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "rojo" name = "rojo"
version = "7.5.1" version = "7.6.0"
rust-version = "1.79.0" rust-version = "1.83"
authors = [ authors = [
"Lucien Greathouse <me@lpghatguy.com>", "Lucien Greathouse <me@lpghatguy.com>",
"Micah Reid <git@dekkonot.com>", "Micah Reid <git@dekkonot.com>",
@@ -55,11 +55,11 @@ memofs = { version = "0.3.0", path = "crates/memofs" }
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" } # rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
# rbx_xml = { path = "../rbx-dom/rbx_xml" } # rbx_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "1.0.0" rbx_binary = "2.0.0"
rbx_dom_weak = "3.0.0" rbx_dom_weak = "4.0.0"
rbx_reflection = "5.0.0" rbx_reflection = "6.0.0"
rbx_reflection_database = "1.0.3" rbx_reflection_database = "2.0.0"
rbx_xml = "1.0.0" rbx_xml = "2.0.0"
anyhow = "1.0.80" anyhow = "1.0.80"
backtrace = "0.3.69" backtrace = "0.3.69"

View File

@@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
Pull requests are welcome! Pull requests are welcome!
Rojo supports Rust 1.70.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has. Rojo supports Rust 1.83 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
## License ## License
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details. Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.

View File

@@ -2,4 +2,4 @@ return {
hello = function() hello = function()
print("Hello world, from {project_name}!") print("Hello world, from {project_name}!")
end, end,
} }

View File

@@ -0,0 +1 @@
print("Hello world, from client!")

View File

@@ -0,0 +1 @@
print("Hello world, from server!")

View File

@@ -0,0 +1,3 @@
return function()
print("Hello, world!")
end

View File

@@ -0,0 +1 @@
print("Hello world, from plugin!")

View File

@@ -47,6 +47,7 @@ fn main() -> Result<(), anyhow::Error> {
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let plugin_dir = root_dir.join("plugin"); let plugin_dir = root_dir.join("plugin");
let templates_dir = root_dir.join("assets").join("project-templates");
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?; let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
let plugin_version = let plugin_version =
@@ -57,7 +58,9 @@ fn main() -> Result<(), anyhow::Error> {
"plugin version does not match Cargo version" "plugin version does not match Cargo version"
); );
let snapshot = VfsSnapshot::dir(hashmap! { let template_snapshot = snapshot_from_fs_path(&templates_dir)?;
let plugin_snapshot = VfsSnapshot::dir(hashmap! {
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?, "default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
"plugin" => VfsSnapshot::dir(hashmap! { "plugin" => VfsSnapshot::dir(hashmap! {
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?, "fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
@@ -70,10 +73,11 @@ fn main() -> Result<(), anyhow::Error> {
}), }),
}); });
let out_path = Path::new(&out_dir).join("plugin.bincode"); let template_file = File::create(Path::new(&out_dir).join("templates.bincode"))?;
let out_file = File::create(out_path)?; let plugin_file = File::create(Path::new(&out_dir).join("plugin.bincode"))?;
bincode::serialize_into(out_file, &snapshot)?; bincode::serialize_into(plugin_file, &plugin_snapshot)?;
bincode::serialize_into(template_file, &template_snapshot)?;
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc"); println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
println!("cargo:rerun-if-changed=build/windows/rojo.manifest"); println!("cargo:rerun-if-changed=build/windows/rojo.manifest");

View File

@@ -228,23 +228,17 @@ impl VfsBackend for InMemoryFs {
} }
fn must_be_file<T>(path: &Path) -> io::Result<T> { fn must_be_file<T>(path: &Path) -> io::Result<T> {
Err(io::Error::new( Err(io::Error::other(format!(
io::ErrorKind::Other, "path {} was a directory, but must be a file",
format!( path.display()
"path {} was a directory, but must be a file", )))
path.display()
),
))
} }
fn must_be_dir<T>(path: &Path) -> io::Result<T> { fn must_be_dir<T>(path: &Path) -> io::Result<T> {
Err(io::Error::new( Err(io::Error::other(format!(
io::ErrorKind::Other, "path {} was a file, but must be a directory",
format!( path.display()
"path {} was a file, but must be a directory", )))
path.display()
),
))
} }
fn not_found<T>(path: &Path) -> io::Result<T> { fn not_found<T>(path: &Path) -> io::Result<T> {

View File

@@ -15,45 +15,27 @@ impl NoopBackend {
impl VfsBackend for NoopBackend { impl VfsBackend for NoopBackend {
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> { fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> { fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> { fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn remove_file(&mut self, _path: &Path) -> io::Result<()> { fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> { fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> { fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> { fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
@@ -61,17 +43,11 @@ impl VfsBackend for NoopBackend {
} }
fn watch(&mut self, _path: &Path) -> io::Result<()> { fn watch(&mut self, _path: &Path) -> io::Result<()> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
fn unwatch(&mut self, _path: &Path) -> io::Result<()> { fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
Err(io::Error::new( Err(io::Error::other("NoopBackend doesn't do anything"))
io::ErrorKind::Other,
"NoopBackend doesn't do anything",
))
} }
} }

View File

@@ -109,15 +109,13 @@ impl VfsBackend for StdBackend {
self.watches.insert(path.to_path_buf()); self.watches.insert(path.to_path_buf());
self.watcher self.watcher
.watch(path, RecursiveMode::Recursive) .watch(path, RecursiveMode::Recursive)
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner)) .map_err(io::Error::other)
} }
} }
fn unwatch(&mut self, path: &Path) -> io::Result<()> { fn unwatch(&mut self, path: &Path) -> io::Result<()> {
self.watches.remove(path); self.watches.remove(path);
self.watcher self.watcher.unwatch(path).map_err(io::Error::other)
.unwatch(path)
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
} }
} }

View File

@@ -1 +1 @@
7.5.1 7.6.0

View File

@@ -378,13 +378,26 @@ types = {
if pod == "Default" then if pod == "Default" then
return nil return nil
else else
return PhysicalProperties.new( -- Passing `nil` instead of not passing anything gives
pod.density, -- different results, so we have to branch here.
pod.friction, if pod.acousticAbsorption then
pod.elasticity, return (PhysicalProperties.new :: any)(
pod.frictionWeight, pod.density,
pod.elasticityWeight pod.friction,
) pod.elasticity,
pod.frictionWeight,
pod.elasticityWeight,
pod.acousticAbsorption
)
else
return PhysicalProperties.new(
pod.density,
pod.friction,
pod.elasticity,
pod.frictionWeight,
pod.elasticityWeight
)
end
end end
end, end,
@@ -398,6 +411,7 @@ types = {
elasticity = roblox.Elasticity, elasticity = roblox.Elasticity,
frictionWeight = roblox.FrictionWeight, frictionWeight = roblox.FrictionWeight,
elasticityWeight = roblox.ElasticityWeight, elasticityWeight = roblox.ElasticityWeight,
acousticAbsorption = roblox.AcousticAbsorption,
} }
end end
end, end,

View File

@@ -441,7 +441,8 @@
"friction": 1.0, "friction": 1.0,
"elasticity": 0.0, "elasticity": 0.0,
"frictionWeight": 50.0, "frictionWeight": 50.0,
"elasticityWeight": 25.0 "elasticityWeight": 25.0,
"acousticAbsorption": 0.15625
} }
}, },
"ty": "PhysicalProperties" "ty": "PhysicalProperties"

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +1,49 @@
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::str::FromStr; use std::str::FromStr;
use std::{
collections::VecDeque,
path::{Path, PathBuf},
};
use std::{
ffi::OsStr,
io::{self, Write},
};
use anyhow::{bail, format_err}; use anyhow::{bail, format_err};
use clap::Parser; use clap::Parser;
use fs_err as fs; use fs_err as fs;
use fs_err::OpenOptions; use fs_err::OpenOptions;
use memofs::{InMemoryFs, Vfs, VfsSnapshot};
use super::resolve_path; use super::resolve_path;
static MODEL_PROJECT: &str = const GIT_IGNORE_PLACEHOLDER: &str = "gitignore.txt";
include_str!("../../assets/default-model-project/default.project.json");
static MODEL_README: &str = include_str!("../../assets/default-model-project/README.md");
static MODEL_INIT: &str = include_str!("../../assets/default-model-project/src-init.luau");
static MODEL_GIT_IGNORE: &str = include_str!("../../assets/default-model-project/gitignore.txt");
static PLACE_PROJECT: &str = static TEMPLATE_BINCODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/templates.bincode"));
include_str!("../../assets/default-place-project/default.project.json");
static PLACE_README: &str = include_str!("../../assets/default-place-project/README.md");
static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt");
static PLUGIN_PROJECT: &str =
include_str!("../../assets/default-plugin-project/default.project.json");
static PLUGIN_README: &str = include_str!("../../assets/default-plugin-project/README.md");
static PLUGIN_GIT_IGNORE: &str = include_str!("../../assets/default-plugin-project/gitignore.txt");
/// Initializes a new Rojo project. /// Initializes a new Rojo project.
///
/// By default, this will attempt to initialize a 'git' repository in the
/// project directory if `git` is installed. To avoid this, pass `--skip-git`.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct InitCommand { pub struct InitCommand {
/// Path to the place to create the project. Defaults to the current directory. /// Path to the place to create the project. Defaults to the current directory.
#[clap(default_value = "")] #[clap(default_value = "")]
pub path: PathBuf, pub path: PathBuf,
/// The kind of project to create, 'place', 'plugin', or 'model'. Defaults to place. /// The kind of project to create, 'place', 'plugin', or 'model'.
#[clap(long, default_value = "place")] #[clap(long, default_value = "place")]
pub kind: InitKind, pub kind: InitKind,
/// Skips the initialization of a git repository.
#[clap(long)]
pub skip_git: bool,
} }
impl InitCommand { impl InitCommand {
pub fn run(self) -> anyhow::Result<()> { pub fn run(self) -> anyhow::Result<()> {
let template = self.kind.template();
let base_path = resolve_path(&self.path); let base_path = resolve_path(&self.path);
fs::create_dir_all(&base_path)?; fs::create_dir_all(&base_path)?;
@@ -53,10 +57,51 @@ impl InitCommand {
name: project_name.to_owned(), name: project_name.to_owned(),
}; };
match self.kind { println!(
InitKind::Place => init_place(&base_path, project_params)?, "Creating new {:?} project '{}'",
InitKind::Model => init_model(&base_path, project_params)?, self.kind, project_params.name
InitKind::Plugin => init_plugin(&base_path, project_params)?, );
let vfs = Vfs::new(template);
vfs.set_watch_enabled(false);
let mut queue = VecDeque::with_capacity(8);
for entry in vfs.read_dir("")? {
queue.push_back(entry?.path().to_path_buf())
}
while let Some(mut path) = queue.pop_front() {
let metadata = vfs.metadata(&path)?;
if metadata.is_dir() {
fs_err::create_dir(base_path.join(&path))?;
for entry in vfs.read_dir(&path)? {
queue.push_back(entry?.path().to_path_buf());
}
} else {
let content = vfs.read_to_string_lf_normalized(&path)?;
if let Some(file_stem) = path.file_name().and_then(OsStr::to_str) {
if file_stem == GIT_IGNORE_PLACEHOLDER && !self.skip_git {
path.set_file_name(".gitignore");
}
}
write_if_not_exists(
&base_path.join(&path),
&project_params.render_template(&content),
)?;
}
}
if !self.skip_git && should_git_init(&base_path) {
log::debug!("Initializing Git repository...");
let status = Command::new("git")
.arg("init")
.current_dir(&base_path)
.status()?;
if !status.success() {
bail!("git init failed: status code {:?}", status.code());
}
} }
println!("Created project successfully."); println!("Created project successfully.");
@@ -78,6 +123,32 @@ pub enum InitKind {
Plugin, Plugin,
} }
impl InitKind {
fn template(&self) -> InMemoryFs {
let template_path = match self {
Self::Place => "place",
Self::Model => "model",
Self::Plugin => "plugin",
};
let snapshot: VfsSnapshot = bincode::deserialize(TEMPLATE_BINCODE)
.expect("Rojo's templates were not properly packed into Rojo's binary");
if let VfsSnapshot::Dir { mut children } = snapshot {
if let Some(template) = children.remove(template_path) {
let mut fs = InMemoryFs::new();
fs.load_snapshot("", template)
.expect("loading a template in memory should never fail");
fs
} else {
panic!("template for project type {:?} is missing", self)
}
} else {
panic!("Rojo's templates were packed as a file instead of a directory")
}
}
}
impl FromStr for InitKind { impl FromStr for InitKind {
type Err = anyhow::Error; type Err = anyhow::Error;
@@ -94,92 +165,6 @@ impl FromStr for InitKind {
} }
} }
fn init_place(base_path: &Path, project_params: ProjectParams) -> anyhow::Result<()> {
println!("Creating new place project '{}'", project_params.name);
let project_file = project_params.render_template(PLACE_PROJECT);
try_create_project(base_path, &project_file)?;
let readme = project_params.render_template(PLACE_README);
write_if_not_exists(&base_path.join("README.md"), &readme)?;
let src = base_path.join("src");
fs::create_dir_all(&src)?;
let src_shared = src.join("shared");
fs::create_dir_all(src.join(&src_shared))?;
let src_server = src.join("server");
fs::create_dir_all(src.join(&src_server))?;
let src_client = src.join("client");
fs::create_dir_all(src.join(&src_client))?;
write_if_not_exists(
&src_shared.join("Hello.luau"),
"return function()\n\tprint(\"Hello, world!\")\nend",
)?;
write_if_not_exists(
&src_server.join("init.server.luau"),
"print(\"Hello world, from server!\")",
)?;
write_if_not_exists(
&src_client.join("init.client.luau"),
"print(\"Hello world, from client!\")",
)?;
let git_ignore = project_params.render_template(PLACE_GIT_IGNORE);
try_git_init(base_path, &git_ignore)?;
Ok(())
}
fn init_model(base_path: &Path, project_params: ProjectParams) -> anyhow::Result<()> {
println!("Creating new model project '{}'", project_params.name);
let project_file = project_params.render_template(MODEL_PROJECT);
try_create_project(base_path, &project_file)?;
let readme = project_params.render_template(MODEL_README);
write_if_not_exists(&base_path.join("README.md"), &readme)?;
let src = base_path.join("src");
fs::create_dir_all(&src)?;
let init = project_params.render_template(MODEL_INIT);
write_if_not_exists(&src.join("init.luau"), &init)?;
let git_ignore = project_params.render_template(MODEL_GIT_IGNORE);
try_git_init(base_path, &git_ignore)?;
Ok(())
}
fn init_plugin(base_path: &Path, project_params: ProjectParams) -> anyhow::Result<()> {
println!("Creating new plugin project '{}'", project_params.name);
let project_file = project_params.render_template(PLUGIN_PROJECT);
try_create_project(base_path, &project_file)?;
let readme = project_params.render_template(PLUGIN_README);
write_if_not_exists(&base_path.join("README.md"), &readme)?;
let src = base_path.join("src");
fs::create_dir_all(&src)?;
write_if_not_exists(
&src.join("init.server.luau"),
"print(\"Hello world, from plugin!\")\n",
)?;
let git_ignore = project_params.render_template(PLUGIN_GIT_IGNORE);
try_git_init(base_path, &git_ignore)?;
Ok(())
}
/// Contains parameters used in templates to create a project. /// Contains parameters used in templates to create a project.
struct ProjectParams { struct ProjectParams {
name: String, name: String,
@@ -194,23 +179,6 @@ impl ProjectParams {
} }
} }
/// Attempt to initialize a Git repository if necessary, and create .gitignore.
fn try_git_init(path: &Path, git_ignore: &str) -> Result<(), anyhow::Error> {
if should_git_init(path) {
log::debug!("Initializing Git repository...");
let status = Command::new("git").arg("init").current_dir(path).status()?;
if !status.success() {
bail!("git init failed: status code {:?}", status.code());
}
}
write_if_not_exists(&path.join(".gitignore"), git_ignore)?;
Ok(())
}
/// Tells whether we should initialize a Git repository inside the given path. /// Tells whether we should initialize a Git repository inside the given path.
/// ///
/// Will return false if the user doesn't have Git installed or if the path is /// Will return false if the user doesn't have Git installed or if the path is
@@ -251,29 +219,3 @@ fn write_if_not_exists(path: &Path, contents: &str) -> Result<(), anyhow::Error>
Ok(()) Ok(())
} }
/// Try to create a project file and fail if it already exists.
fn try_create_project(base_path: &Path, contents: &str) -> Result<(), anyhow::Error> {
let project_path = base_path.join("default.project.json");
let file_res = OpenOptions::new()
.write(true)
.create_new(true)
.open(&project_path);
let mut file = match file_res {
Ok(file) => file,
Err(err) => {
return match err.kind() {
io::ErrorKind::AlreadyExists => {
bail!("Project file already exists: {}", project_path.display())
}
_ => Err(err.into()),
}
}
};
file.write_all(contents.as_bytes())?;
Ok(())
}

View File

@@ -1,7 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{bail, format_err, Context}; use anyhow::{bail, Context};
use clap::Parser; use clap::Parser;
use memofs::Vfs; use memofs::Vfs;
use reqwest::{ use reqwest::{
@@ -91,32 +90,6 @@ impl UploadCommand {
} }
} }
/// The kind of asset to upload to the website. Affects what endpoints Rojo uses
/// and changes how the asset is built.
#[derive(Debug, Clone, Copy)]
enum UploadKind {
/// Upload to a place.
Place,
/// Upload to a model-like asset, like a Model, Plugin, or Package.
Model,
}
impl FromStr for UploadKind {
type Err = anyhow::Error;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match source {
"place" => Ok(UploadKind::Place),
"model" => Ok(UploadKind::Model),
attempted => Err(format_err!(
"Invalid upload kind '{}'. Valid kinds are: place, model",
attempted
)),
}
}
}
fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> { fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> {
let url = format!( let url = format!(
"https://data.roblox.com/Data/Upload.ashx?assetid={}", "https://data.roblox.com/Data/Upload.ashx?assetid={}",

View File

@@ -62,7 +62,7 @@ impl AmbiguousValue {
match &property.data_type { match &property.data_type {
DataType::Enum(enum_name) => { DataType::Enum(enum_name) => {
let database = rbx_reflection_database::get(); let database = rbx_reflection_database::get().unwrap();
let enum_descriptor = database.enums.get(enum_name).ok_or_else(|| { let enum_descriptor = database.enums.get(enum_name).ok_or_else(|| {
format_err!("Unknown enum {}. This is a Rojo bug!", enum_name) format_err!("Unknown enum {}. This is a Rojo bug!", enum_name)
@@ -203,7 +203,7 @@ fn find_descriptor(
class_name: &str, class_name: &str,
prop_name: &str, prop_name: &str,
) -> Option<&'static PropertyDescriptor<'static>> { ) -> Option<&'static PropertyDescriptor<'static>> {
let database = rbx_reflection_database::get(); let database = rbx_reflection_database::get().unwrap();
let mut current_class_name = class_name; let mut current_class_name = class_name;
loop { loop {

View File

@@ -221,7 +221,7 @@ pub enum InstigatingSource {
ProjectNode( ProjectNode(
#[serde(serialize_with = "path_serializer::serialize_absolute")] PathBuf, #[serde(serialize_with = "path_serializer::serialize_absolute")] PathBuf,
String, String,
ProjectNode, Box<ProjectNode>,
Option<String>, Option<String>,
), ),
} }

View File

@@ -73,7 +73,7 @@ impl RojoTree {
self.inner.root_ref() self.inner.root_ref()
} }
pub fn get_instance(&self, id: Ref) -> Option<InstanceWithMeta> { pub fn get_instance(&self, id: Ref) -> Option<InstanceWithMeta<'_>> {
if let Some(instance) = self.inner.get_by_ref(id) { if let Some(instance) = self.inner.get_by_ref(id) {
let metadata = self.metadata_map.get(&id).unwrap(); let metadata = self.metadata_map.get(&id).unwrap();
@@ -83,7 +83,7 @@ impl RojoTree {
} }
} }
pub fn get_instance_mut(&mut self, id: Ref) -> Option<InstanceWithMetaMut> { pub fn get_instance_mut(&mut self, id: Ref) -> Option<InstanceWithMetaMut<'_>> {
if let Some(instance) = self.inner.get_by_ref_mut(id) { if let Some(instance) = self.inner.get_by_ref_mut(id) {
let metadata = self.metadata_map.get_mut(&id).unwrap(); let metadata = self.metadata_map.get_mut(&id).unwrap();

View File

@@ -31,6 +31,7 @@ pub fn snapshot_lua(
script_type: ScriptType, script_type: ScriptType,
) -> anyhow::Result<Option<InstanceSnapshot>> { ) -> anyhow::Result<Option<InstanceSnapshot>> {
let run_context_enums = &rbx_reflection_database::get() let run_context_enums = &rbx_reflection_database::get()
.unwrap()
.enums .enums
.get("RunContext") .get("RunContext")
.expect("Unable to get RunContext enums!") .expect("Unable to get RunContext enums!")

View File

@@ -289,7 +289,7 @@ pub fn snapshot_project_node(
metadata.instigating_source = Some(InstigatingSource::ProjectNode( metadata.instigating_source = Some(InstigatingSource::ProjectNode(
project_path.to_path_buf(), project_path.to_path_buf(),
instance_name.to_string(), instance_name.to_string(),
node.clone(), Box::new(node.clone()),
parent_class.map(|name| name.to_owned()), parent_class.map(|name| name.to_owned()),
)); ));
@@ -313,7 +313,7 @@ fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Ustr> {
// Members of DataModel with names that match known services are // Members of DataModel with names that match known services are
// probably supposed to be those services. // probably supposed to be those services.
let descriptor = rbx_reflection_database::get().classes.get(name)?; let descriptor = rbx_reflection_database::get().unwrap().classes.get(name)?;
if descriptor.tags.contains(&ClassTag::Service) { if descriptor.tags.contains(&ClassTag::Service) {
return Some(ustr(name)); return Some(ustr(name));

View File

@@ -160,7 +160,7 @@ impl TestServeSession {
Ok(serde_json::from_str(&body).expect("Server returned malformed response")) Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
} }
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> { pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse<'_>, reqwest::Error> {
let url = format!("http://localhost:{}/api/read/{}", self.port, id); let url = format!("http://localhost:{}/api/read/{}", self.port, id);
let body = reqwest::blocking::get(url)?.text()?; let body = reqwest::blocking::get(url)?.text()?;