From 19e7f57284720c71d3fbb99576a0dae4d471ce6c Mon Sep 17 00:00:00 2001
From: Micah <micah@uplift.games>
Date: Mon, 24 Mar 2025 11:24:36 -0700
Subject: [PATCH 1/5] Loosen Lune version string requirements (#294)

---
 Cargo.lock                              |  3 ++-
 crates/lune-utils/Cargo.toml            |  1 +
 crates/lune-utils/src/version_string.rs | 11 +++++------
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5bc12be..9babfbc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "addr2line"
@@ -1716,6 +1716,7 @@ dependencies = [
  "once_cell",
  "path-clean",
  "pathdiff",
+ "semver 1.0.23",
  "tokio",
 ]
 
diff --git a/crates/lune-utils/Cargo.toml b/crates/lune-utils/Cargo.toml
index 09459dd..138f680 100644
--- a/crates/lune-utils/Cargo.toml
+++ b/crates/lune-utils/Cargo.toml
@@ -22,3 +22,4 @@ dunce = "1.0"
 once_cell = "1.17"
 path-clean = "1.0"
 pathdiff = "0.2"
+semver = "1.0"
diff --git a/crates/lune-utils/src/version_string.rs b/crates/lune-utils/src/version_string.rs
index 6c4bbcf..29f1d87 100644
--- a/crates/lune-utils/src/version_string.rs
+++ b/crates/lune-utils/src/version_string.rs
@@ -2,6 +2,7 @@ use std::sync::Arc;
 
 use mlua::prelude::*;
 use once_cell::sync::Lazy;
+use semver::Version;
 
 static LUAU_VERSION: Lazy<Arc<String>> = Lazy::new(create_luau_version_string);
 
@@ -20,12 +21,10 @@ pub fn get_version_string(lune_version: impl AsRef<str>) -> String {
     let lune_version = lune_version.as_ref();
 
     assert!(!lune_version.is_empty(), "Lune version string is empty");
-    assert!(
-        lune_version.chars().all(is_valid_version_char),
-        "Lune version string contains invalid characters"
-    );
-
-    format!("Lune {lune_version}+{}", *LUAU_VERSION)
+    match Version::parse(lune_version) {
+        Ok(semver) => format!("Lune {semver}+{}", *LUAU_VERSION),
+        Err(e) => panic!("Lune version string is not valid semver: {e}"),
+    }
 }
 
 fn create_luau_version_string() -> Arc<String> {

From 6cd0234a5f02987bc29e345dc0fb7cd0bf01e93e Mon Sep 17 00:00:00 2001
From: Erica Marigold <hi@devcomp.xyz>
Date: Mon, 24 Mar 2025 23:56:02 +0530
Subject: [PATCH 2/5] Allow toggling JIT in the CLI (#265)

---
 crates/lune-std-luau/src/lib.rs | 10 ++++++++--
 crates/lune-utils/src/jit.rs    | 30 ++++++++++++++++++++++++++++++
 crates/lune-utils/src/lib.rs    |  1 +
 crates/lune/src/cli/run.rs      | 12 ++++++++++--
 crates/lune/src/rt/runtime.rs   | 16 ++++++++++++++++
 crates/lune/src/tests.rs        | 16 +++++++++-------
 6 files changed, 74 insertions(+), 11 deletions(-)
 create mode 100644 crates/lune-utils/src/jit.rs

diff --git a/crates/lune-std-luau/src/lib.rs b/crates/lune-std-luau/src/lib.rs
index 21eb912..f3a61bd 100644
--- a/crates/lune-std-luau/src/lib.rs
+++ b/crates/lune-std-luau/src/lib.rs
@@ -2,7 +2,7 @@
 
 use mlua::prelude::*;
 
-use lune_utils::TableBuilder;
+use lune_utils::{jit::JitStatus, TableBuilder};
 
 mod options;
 
@@ -78,7 +78,13 @@ fn load_source<'lua>(
     // changed, otherwise disable JIT since it'll fall back anyways
     lua.enable_jit(options.codegen_enabled && !env_changed);
     let function = chunk.into_function()?;
-    lua.enable_jit(true);
+    lua.enable_jit(
+        lua.app_data_ref::<JitStatus>()
+            .ok_or(LuaError::runtime(
+                "Failed to get current JitStatus ref from AppData",
+            ))?
+            .enabled(),
+    );
 
     Ok(function)
 }
diff --git a/crates/lune-utils/src/jit.rs b/crates/lune-utils/src/jit.rs
new file mode 100644
index 0000000..e2ee475
--- /dev/null
+++ b/crates/lune-utils/src/jit.rs
@@ -0,0 +1,30 @@
+#[derive(Debug, Clone, Copy, Default)]
+pub struct JitStatus(bool);
+
+impl JitStatus {
+    #[must_use]
+    pub fn new(enabled: bool) -> Self {
+        Self(enabled)
+    }
+
+    pub fn set_status(&mut self, enabled: bool) {
+        self.0 = enabled;
+    }
+
+    #[must_use]
+    pub fn enabled(self) -> bool {
+        self.0
+    }
+}
+
+impl From<JitStatus> for bool {
+    fn from(val: JitStatus) -> Self {
+        val.enabled()
+    }
+}
+
+impl From<bool> for JitStatus {
+    fn from(val: bool) -> Self {
+        Self::new(val)
+    }
+}
diff --git a/crates/lune-utils/src/lib.rs b/crates/lune-utils/src/lib.rs
index 52743a3..828426f 100644
--- a/crates/lune-utils/src/lib.rs
+++ b/crates/lune-utils/src/lib.rs
@@ -4,6 +4,7 @@ mod table_builder;
 mod version_string;
 
 pub mod fmt;
+pub mod jit;
 pub mod path;
 
 pub use self::table_builder::TableBuilder;
diff --git a/crates/lune/src/cli/run.rs b/crates/lune/src/cli/run.rs
index 6267ed7..dd1fa63 100644
--- a/crates/lune/src/cli/run.rs
+++ b/crates/lune/src/cli/run.rs
@@ -1,4 +1,4 @@
-use std::process::ExitCode;
+use std::{env, process::ExitCode};
 
 use anyhow::{Context, Result};
 use clap::Parser;
@@ -41,7 +41,15 @@ impl RunCommand {
         };
 
         // Create a new lune runtime with all globals & run the script
-        let mut rt = Runtime::new().with_args(self.script_args);
+        let mut rt = Runtime::new()
+            .with_args(self.script_args)
+            // Enable JIT compilation unless it was requested to be disabled
+            .with_jit(
+                !matches!(
+                    env::var("LUNE_LUAU_JIT").ok(), 
+                    Some(jit_enabled) if jit_enabled == "0" || jit_enabled == "false" || jit_enabled == "off"
+                )
+             );
 
         let result = rt
             .run(&script_display_name, strip_shebang(script_contents))
diff --git a/crates/lune/src/rt/runtime.rs b/crates/lune/src/rt/runtime.rs
index 31e5b03..3f7cce6 100644
--- a/crates/lune/src/rt/runtime.rs
+++ b/crates/lune/src/rt/runtime.rs
@@ -8,6 +8,7 @@ use std::{
     },
 };
 
+use lune_utils::jit::JitStatus;
 use mlua::prelude::*;
 use mlua_luau_scheduler::{Functions, Scheduler};
 use self_cell::self_cell;
@@ -100,6 +101,7 @@ impl RuntimeInner {
 */
 pub struct Runtime {
     inner: RuntimeInner,
+    jit_status: JitStatus,
 }
 
 impl Runtime {
@@ -113,6 +115,7 @@ impl Runtime {
     pub fn new() -> Self {
         Self {
             inner: RuntimeInner::create().expect("Failed to create runtime"),
+            jit_status: JitStatus::default(),
         }
     }
 
@@ -130,6 +133,15 @@ impl Runtime {
         self
     }
 
+    /**
+        Enables or disables JIT compilation.
+    */
+    #[must_use]
+    pub fn with_jit(mut self, jit_status: impl Into<JitStatus>) -> Self {
+        self.jit_status = jit_status.into();
+        self
+    }
+
     /**
         Runs a Lune script inside of the current runtime.
 
@@ -155,6 +167,10 @@ impl Runtime {
             eprintln!("{}", RuntimeError::from(e));
         });
 
+        // Enable / disable the JIT as requested and store the current status as AppData
+        lua.set_app_data(self.jit_status);
+        lua.enable_jit(self.jit_status.enabled());
+
         // Load our "main" thread
         let main = lua
             .load(script_contents.as_ref())
diff --git a/crates/lune/src/tests.rs b/crates/lune/src/tests.rs
index d5f5640..ecf01b2 100644
--- a/crates/lune/src/tests.rs
+++ b/crates/lune/src/tests.rs
@@ -31,13 +31,15 @@ macro_rules! create_tests {
             // The rest of the test logic can continue as normal
             let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
             let script = read_to_string(&full_name).await?;
-            let mut lune = Runtime::new().with_args(
-                ARGS
-                    .clone()
-                    .iter()
-                    .map(ToString::to_string)
-                    .collect::<Vec<_>>()
-            );
+            let mut lune = Runtime::new()
+                .with_jit(true)
+                .with_args(
+                    ARGS
+                        .clone()
+                        .iter()
+                        .map(ToString::to_string)
+                        .collect::<Vec<_>>()
+                );
             let script_name = full_name
 				.trim_end_matches(".luau")
 				.trim_end_matches(".lua")

From 822dd1939362c6af57a69b708db4ff3c956c8971 Mon Sep 17 00:00:00 2001
From: Micah <micah@uplift.games>
Date: Mon, 24 Mar 2025 11:29:22 -0700
Subject: [PATCH 3/5] Add functions for getting Roblox Studio locations to
 roblox library (#284)

---
 Cargo.lock                        | 104 ++++++++++++++++++++++--------
 crates/lune-std-roblox/Cargo.toml |   1 +
 crates/lune-std-roblox/src/lib.rs |  29 +++++++++
 types/roblox.luau                 |  91 ++++++++++++++++++++++++++
 4 files changed, 197 insertions(+), 28 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 9babfbc..39f065e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,7 +23,7 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "cipher",
  "cpufeatures",
 ]
@@ -206,7 +206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
 dependencies = [
  "async-lock",
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
  "futures-io",
  "futures-lite",
@@ -254,7 +254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
 dependencies = [
  "addr2line",
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "miniz_oxide",
  "object",
@@ -318,7 +318,7 @@ dependencies = [
  "arrayref",
  "arrayvec 0.7.6",
  "cc",
- "cfg-if",
+ "cfg-if 1.0.0",
  "constant_time_eq 0.3.1",
  "digest",
 ]
@@ -433,6 +433,12 @@ dependencies = [
  "shlex",
 ]
 
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -650,7 +656,7 @@ version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -744,7 +750,7 @@ version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
 dependencies = [
- "dirs-sys",
+ "dirs-sys 0.4.1",
 ]
 
 [[package]]
@@ -758,6 +764,27 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "dirs"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "dirs-sys 0.3.7",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users 0.4.6",
+ "winapi",
+]
+
 [[package]]
 name = "dirs-sys"
 version = "0.4.1"
@@ -805,7 +832,7 @@ version = "0.8.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -890,7 +917,7 @@ version = "4.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "rustix",
  "windows-sys 0.52.0",
 ]
@@ -999,7 +1026,7 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "log",
  "rustversion",
@@ -1022,7 +1049,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
@@ -1033,7 +1060,7 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
 ]
@@ -1432,7 +1459,7 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "windows-targets 0.52.6",
 ]
 
@@ -1490,7 +1517,7 @@ version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "generator",
  "scoped-tls",
  "tracing",
@@ -1660,6 +1687,7 @@ dependencies = [
  "mlua-luau-scheduler",
  "once_cell",
  "rbx_cookie",
+ "roblox_install",
 ]
 
 [[package]]
@@ -1764,7 +1792,7 @@ version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "digest",
 ]
 
@@ -1845,7 +1873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebe026d6bd1583a9cf9080e189030ddaea7e6f5f0deb366a8e26f8a26c4135b8"
 dependencies = [
  "cc",
- "cfg-if",
+ "cfg-if 1.0.0",
  "luau0-src",
  "pkg-config",
 ]
@@ -1866,7 +1894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
 dependencies = [
  "bitflags 2.6.0",
- "cfg-if",
+ "cfg-if 1.0.0",
  "cfg_aliases",
  "libc",
 ]
@@ -1993,7 +2021,7 @@ version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "redox_syscall 0.5.7",
  "smallvec",
@@ -2102,7 +2130,7 @@ version = "3.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
  "hermit-abi 0.4.0",
  "pin-project-lite",
@@ -2250,7 +2278,7 @@ checksum = "d8a61b073240f4c13b1e780a8999a113dfa28bc93f2cf9fc41c6f36e7aceb5bf"
 dependencies = [
  "byteorder 0.5.3",
  "cookie",
- "dirs",
+ "dirs 1.0.5",
  "log",
  "plist",
  "winapi",
@@ -2448,7 +2476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
 dependencies = [
  "cc",
- "cfg-if",
+ "cfg-if 1.0.0",
  "getrandom 0.2.15",
  "libc",
  "spin",
@@ -2478,6 +2506,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "roblox_install"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bb8c693a387f1ae8d2026d82d8b0c175cc4777b97c1f7b12fdb3be595bb13"
+dependencies = [
+ "dirs 2.0.2",
+ "thiserror",
+ "winreg 0.6.2",
+]
+
 [[package]]
 name = "rust-argon2"
 version = "0.8.3"
@@ -2614,7 +2653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
 dependencies = [
  "bitflags 2.6.0",
- "cfg-if",
+ "cfg-if 1.0.0",
  "clipboard-win",
  "fd-lock",
  "home",
@@ -2785,7 +2824,7 @@ version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "cpufeatures",
  "digest",
 ]
@@ -2802,7 +2841,7 @@ version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "cpufeatures",
  "digest",
 ]
@@ -3009,7 +3048,7 @@ version = "3.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "fastrand",
  "once_cell",
  "rustix",
@@ -3042,7 +3081,7 @@ version = "1.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "once_cell",
 ]
 
@@ -3504,7 +3543,7 @@ version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "once_cell",
  "wasm-bindgen-macro",
 ]
@@ -3530,7 +3569,7 @@ version = "0.4.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -3851,6 +3890,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "winreg"
 version = "0.10.1"
@@ -3866,7 +3914,7 @@ version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "windows-sys 0.48.0",
 ]
 
diff --git a/crates/lune-std-roblox/Cargo.toml b/crates/lune-std-roblox/Cargo.toml
index 599a768..c2eb114 100644
--- a/crates/lune-std-roblox/Cargo.toml
+++ b/crates/lune-std-roblox/Cargo.toml
@@ -18,6 +18,7 @@ mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
 
 once_cell = "1.17"
 rbx_cookie = { version = "0.1.4", default-features = false }
+roblox_install = "1.0.0"
 
 lune-utils = { version = "0.1.3", path = "../lune-utils" }
 lune-roblox = { version = "0.1.4", path = "../lune-roblox" }
diff --git a/crates/lune-std-roblox/src/lib.rs b/crates/lune-std-roblox/src/lib.rs
index d1f6a97..ec574b7 100644
--- a/crates/lune-std-roblox/src/lib.rs
+++ b/crates/lune-std-roblox/src/lib.rs
@@ -13,6 +13,7 @@ use lune_roblox::{
 static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
 
 use lune_utils::TableBuilder;
+use roblox_install::RobloxStudio;
 
 /**
     Creates the `roblox` standard library module.
@@ -39,6 +40,10 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
         .with_function("getReflectionDatabase", get_reflection_database)?
         .with_function("implementProperty", implement_property)?
         .with_function("implementMethod", implement_method)?
+        .with_function("studioApplicationPath", studio_application_path)?
+        .with_function("studioContentPath", studio_content_path)?
+        .with_function("studioPluginPath", studio_plugin_path)?
+        .with_function("studioBuiltinPluginPath", studio_builtin_plugin_path)?
         .build_readonly()
 }
 
@@ -147,3 +152,27 @@ fn implement_method(
     InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;
     Ok(())
 }
+
+fn studio_application_path(_: &Lua, _: ()) -> LuaResult<String> {
+    RobloxStudio::locate()
+        .map(|rs| rs.application_path().display().to_string())
+        .map_err(LuaError::external)
+}
+
+fn studio_content_path(_: &Lua, _: ()) -> LuaResult<String> {
+    RobloxStudio::locate()
+        .map(|rs| rs.content_path().display().to_string())
+        .map_err(LuaError::external)
+}
+
+fn studio_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
+    RobloxStudio::locate()
+        .map(|rs| rs.plugins_path().display().to_string())
+        .map_err(LuaError::external)
+}
+
+fn studio_builtin_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
+    RobloxStudio::locate()
+        .map(|rs| rs.built_in_plugins_path().display().to_string())
+        .map_err(LuaError::external)
+}
diff --git a/types/roblox.luau b/types/roblox.luau
index b79ad60..4428598 100644
--- a/types/roblox.luau
+++ b/types/roblox.luau
@@ -504,4 +504,95 @@ roblox.Instance = (nil :: any) :: {
 	new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance),
 }
 
+--[=[
+	@within Roblox
+	@tag must_use
+
+	Returns the path to the system's Roblox Studio executable.
+
+	There is no guarantee that this will exist, but if Studio is installed this
+	is where it will be.
+
+	### Example usage
+
+	```lua
+	local roblox = require("@lune/roblox")
+
+	local pathToStudio = roblox.studioApplicationPath()
+	print("Studio is located at:", pathToStudio)
+	```
+]=]
+function roblox.studioApplicationPath(): string
+	return nil :: any
+end
+
+--[=[
+	@within Roblox
+	@tag must_use
+
+	Returns the path to the `Content` folder of the system's current Studio
+	install.
+
+	This folder will always exist if Studio is installed.
+
+	### Example usage
+
+	```lua
+	local roblox = require("@lune/roblox")
+
+	local pathToContent = roblox.studioContentPath()
+	print("Studio's content folder is located at:", pathToContent)
+	```
+]=]
+function roblox.studioContentPath(): string
+	return nil :: any
+end
+
+--[=[
+	@within Roblox
+	@tag must_use
+
+	Returns the path to the `plugin` folder of the system's current Studio
+	install. This is the path where local plugins are installed.
+
+	This folder may not exist if the user has never installed a local plugin.
+	It will also not necessarily take into account custom plugin directories
+	set from Studio.
+
+	### Example usage
+
+	```lua
+	local roblox = require("@lune/roblox")
+
+	local pathToPluginFolder = roblox.studioPluginPath()
+	print("Studio's plugin folder is located at:", pathToPluginFolder)
+	```
+]=]
+function roblox.studioPluginPath(): string
+	return nil :: any
+end
+
+--[=[
+	@within Roblox
+	@tag must_use
+
+	Returns the path to the `BuiltInPlugin` folder of the system's current
+	Studio install. This is the path where built-in plugins like the ToolBox
+	are installed.
+
+	This folder will always exist if Studio is installed.
+
+	### Example usage
+
+	```lua
+	local roblox = require("@lune/roblox")
+
+	local pathToPluginFolder = roblox.studioBuiltinPluginPath()
+	print("Studio's built-in plugin folder is located at:", pathToPluginFolder)
+	```
+]=]
+function roblox.studioBuiltinPluginPath(): string
+	return nil :: any
+end
+
 return roblox

From dc08b91314a24df9f3005b226691b1185de0565d Mon Sep 17 00:00:00 2001
From: dai <daimondmailtm@gmail.com>
Date: Mon, 24 Mar 2025 19:34:51 +0100
Subject: [PATCH 4/5] Fix deadlock in stdio.format calls in tostring metamethod
 (#288)

---
 Cargo.lock                             |  1 +
 crates/lune-utils/Cargo.toml           |  1 +
 crates/lune-utils/src/fmt/value/mod.rs | 12 +++++-------
 tests/stdio/format.luau                | 20 ++++++++++++++++++++
 4 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 39f065e..8544491 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1742,6 +1742,7 @@ dependencies = [
  "dunce",
  "mlua",
  "once_cell",
+ "parking_lot",
  "path-clean",
  "pathdiff",
  "semver 1.0.23",
diff --git a/crates/lune-utils/Cargo.toml b/crates/lune-utils/Cargo.toml
index 138f680..35cc291 100644
--- a/crates/lune-utils/Cargo.toml
+++ b/crates/lune-utils/Cargo.toml
@@ -22,4 +22,5 @@ dunce = "1.0"
 once_cell = "1.17"
 path-clean = "1.0"
 pathdiff = "0.2"
+parking_lot = "0.12.3"
 semver = "1.0"
diff --git a/crates/lune-utils/src/fmt/value/mod.rs b/crates/lune-utils/src/fmt/value/mod.rs
index 3a3e310..bd29f55 100644
--- a/crates/lune-utils/src/fmt/value/mod.rs
+++ b/crates/lune-utils/src/fmt/value/mod.rs
@@ -1,11 +1,9 @@
-use std::{
-    collections::HashSet,
-    sync::{Arc, Mutex},
-};
+use std::{collections::HashSet, sync::Arc};
 
 use console::{colors_enabled as get_colors_enabled, set_colors_enabled};
 use mlua::prelude::*;
 use once_cell::sync::Lazy;
+use parking_lot::ReentrantMutex;
 
 mod basic;
 mod config;
@@ -20,7 +18,7 @@ pub use self::config::ValueFormatConfig;
 // NOTE: Since the setting for colors being enabled is global,
 // and these functions may be called in parallel, we use this global
 // lock to make sure that we don't mess up the colors for other threads.
-static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(())));
+static COLORS_LOCK: Lazy<Arc<ReentrantMutex<()>>> = Lazy::new(|| Arc::new(ReentrantMutex::new(())));
 
 /**
     Formats a Lua value into a pretty string using the given config.
@@ -28,7 +26,7 @@ static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(()))
 #[must_use]
 #[allow(clippy::missing_panics_doc)]
 pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String {
-    let _guard = COLORS_LOCK.lock().unwrap();
+    let _guard = COLORS_LOCK.lock();
 
     let were_colors_enabled = get_colors_enabled();
     set_colors_enabled(were_colors_enabled && config.colors_enabled);
@@ -48,7 +46,7 @@ pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> Stri
 #[must_use]
 #[allow(clippy::missing_panics_doc)]
 pub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String {
-    let _guard = COLORS_LOCK.lock().unwrap();
+    let _guard = COLORS_LOCK.lock();
 
     let were_colors_enabled = get_colors_enabled();
     set_colors_enabled(were_colors_enabled && config.colors_enabled);
diff --git a/tests/stdio/format.luau b/tests/stdio/format.luau
index c0cc7cf..8d8f879 100644
--- a/tests/stdio/format.luau
+++ b/tests/stdio/format.luau
@@ -122,3 +122,23 @@ stdio.ewrite(typeof(errorMessage))
 assertContains("Should format errors similarly to userdata", stdio.format(errorMessage), "<LuaErr")
 assertContains("Should format errors with stack begins", stdio.format(errorMessage), "Stack Begin")
 assertContains("Should format errors with stack ends", stdio.format(errorMessage), "Stack End")
+
+-- Check that calling stdio.format in a __tostring metamethod by print doesn't cause a deadlock
+
+local inner = {}
+setmetatable(inner, {
+	__tostring = function()
+		return stdio.format(5)
+	end,
+})
+
+print(inner)
+
+local outer = {}
+setmetatable(outer, {
+	__tostring = function()
+		return stdio.format(inner)
+	end,
+})
+
+print(outer)

From 6902ecaa7c1a0701467ab94efbab60f5b6bd94ef Mon Sep 17 00:00:00 2001
From: Filip Tibell <filip.tibell@gmail.com>
Date: Mon, 24 Mar 2025 19:43:53 +0100
Subject: [PATCH 5/5] Fix various new clippy lints

---
 crates/lune-roblox/src/document/postprocessing.rs | 2 +-
 crates/lune-roblox/src/shared/userdata.rs         | 2 +-
 crates/lune-std-datetime/src/values.rs            | 4 ++--
 crates/lune-std-process/src/tee_writer.rs         | 2 +-
 crates/lune-std-serde/src/compress_decompress.rs  | 4 ++--
 crates/lune-std/src/globals/require/context.rs    | 4 ++--
 crates/lune/src/cli/utils/files.rs                | 4 ++--
 crates/mlua-luau-scheduler/src/traits.rs          | 2 +-
 8 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/crates/lune-roblox/src/document/postprocessing.rs b/crates/lune-roblox/src/document/postprocessing.rs
index 69481f5..28ec7ad 100644
--- a/crates/lune-roblox/src/document/postprocessing.rs
+++ b/crates/lune-roblox/src/document/postprocessing.rs
@@ -41,7 +41,7 @@ where
 }
 
 fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
-    if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
+    if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
         inst.properties.remove(name);
     }
 }
diff --git a/crates/lune-roblox/src/shared/userdata.rs b/crates/lune-roblox/src/shared/userdata.rs
index eaaa129..d197600 100644
--- a/crates/lune-roblox/src/shared/userdata.rs
+++ b/crates/lune-roblox/src/shared/userdata.rs
@@ -23,7 +23,7 @@ pub fn make_list_writer() -> Box<ListWriter> {
     })
 }
 
-/**
+/*
     Userdata metamethod implementations
 
     Note that many of these return [`LuaResult`] even though they don't
diff --git a/crates/lune-std-datetime/src/values.rs b/crates/lune-std-datetime/src/values.rs
index 4193d63..e270bfa 100644
--- a/crates/lune-std-datetime/src/values.rs
+++ b/crates/lune-std-datetime/src/values.rs
@@ -60,7 +60,7 @@ where
     }
 }
 
-/**
+/*
     Conversion methods between `DateTimeValues` and plain lua tables
 
     Note that the `IntoLua` implementation here uses a read-only table,
@@ -117,7 +117,7 @@ impl IntoLua<'_> for DateTimeValues {
     }
 }
 
-/**
+/*
     Conversion methods between chrono's timezone-aware `DateTime` to
     and from our non-timezone-aware `DateTimeValues` values struct
 */
diff --git a/crates/lune-std-process/src/tee_writer.rs b/crates/lune-std-process/src/tee_writer.rs
index fee7776..9a8a54f 100644
--- a/crates/lune-std-process/src/tee_writer.rs
+++ b/crates/lune-std-process/src/tee_writer.rs
@@ -33,7 +33,7 @@ where
     }
 }
 
-impl<'a, W> AsyncWrite for AsyncTeeWriter<'a, W>
+impl<W> AsyncWrite for AsyncTeeWriter<'_, W>
 where
     W: AsyncWrite + Unpin,
 {
diff --git a/crates/lune-std-serde/src/compress_decompress.rs b/crates/lune-std-serde/src/compress_decompress.rs
index 86b5c87..b0f560a 100644
--- a/crates/lune-std-serde/src/compress_decompress.rs
+++ b/crates/lune-std-serde/src/compress_decompress.rs
@@ -117,7 +117,7 @@ impl<'lua> FromLua<'lua> for CompressDecompressFormat {
 
     Errors when the compression fails.
 */
-pub async fn compress<'lua>(
+pub async fn compress(
     source: impl AsRef<[u8]>,
     format: CompressDecompressFormat,
     level: Option<i32>,
@@ -163,7 +163,7 @@ pub async fn compress<'lua>(
 
     Errors when the decompression fails.
 */
-pub async fn decompress<'lua>(
+pub async fn decompress(
     source: impl AsRef<[u8]>,
     format: CompressDecompressFormat,
 ) -> LuaResult<Vec<u8>> {
diff --git a/crates/lune-std/src/globals/require/context.rs b/crates/lune-std/src/globals/require/context.rs
index 0355d27..3734b6c 100644
--- a/crates/lune-std/src/globals/require/context.rs
+++ b/crates/lune-std/src/globals/require/context.rs
@@ -150,9 +150,9 @@ impl RequireContext {
         self.get_from_cache(lua, abs_path.as_ref())
     }
 
-    async fn load<'lua>(
+    async fn load(
         &self,
-        lua: &'lua Lua,
+        lua: &Lua,
         abs_path: impl AsRef<Path>,
         rel_path: impl AsRef<Path>,
     ) -> LuaResult<LuaRegistryKey> {
diff --git a/crates/lune/src/cli/utils/files.rs b/crates/lune/src/cli/utils/files.rs
index 2e02bb8..c808b4c 100644
--- a/crates/lune/src/cli/utils/files.rs
+++ b/crates/lune/src/cli/utils/files.rs
@@ -64,8 +64,8 @@ pub fn discover_script_path(path: impl AsRef<str>, in_home_dir: bool) -> Result<
     // NOTE: We use metadata directly here to try to
     // avoid accessing the file path more than once
     let file_meta = file_path.metadata();
-    let is_file = file_meta.as_ref().map_or(false, Metadata::is_file);
-    let is_dir = file_meta.as_ref().map_or(false, Metadata::is_dir);
+    let is_file = file_meta.as_ref().is_ok_and(Metadata::is_file);
+    let is_dir = file_meta.as_ref().is_ok_and(Metadata::is_dir);
     let is_abs = file_path.is_absolute();
     let ext = file_path.extension();
     if is_file {
diff --git a/crates/mlua-luau-scheduler/src/traits.rs b/crates/mlua-luau-scheduler/src/traits.rs
index caca387..943bc97 100644
--- a/crates/mlua-luau-scheduler/src/traits.rs
+++ b/crates/mlua-luau-scheduler/src/traits.rs
@@ -334,7 +334,7 @@ impl<'lua> LuaSchedulerExt<'lua> for Lua {
     }
 }
 
-impl<'lua> LuaSpawnExt<'lua> for Lua {
+impl LuaSpawnExt<'_> for Lua {
     fn spawn<F, T>(&self, fut: F) -> Task<T>
     where
         F: Future<Output = T> + Send + 'static,