Merge branch 'master' of https://github.com/boatbomber/rojo into headless-api

This commit is contained in:
boatbomber 2023-10-30 19:13:31 -04:00
commit 15f5a89525
82 changed files with 5314 additions and 942 deletions

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# stylua formatting
0f8e1625d572a5fe0f7b5c08653ff92cc837d346

View file

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
rust_version: [stable, 1.69.0]
rust_version: [stable, 1.70.0]
steps:
- uses: actions/checkout@v3

View file

@ -2,37 +2,155 @@
## Unreleased Changes
* Added headless API for Studio companion plugins. ([#631])
* Internally for themes, we use `Color3.fromHex` now instead of custom-made solution ([#761]).
* On failing to open a file from Roblox Studio, it is now logged appropriately instead of being ignored ([#745]).
* Significantly improved performance of `rojo sourcemap`. ([#668])
[#631]: https://github.com/rojo-rbx/rojo/pull/631
## [7.4.0-rc3] - October 25, 2023
* Changed `sourcemap --watch` to only generate the sourcemap when it's necessary ([#800])
* Switched script source property getter and setter to `ScriptEditorService` methods ([#801])
This ensures that the script editor reflects any changes Rojo makes to a script while it is open in the script editor.
* Fixed issues when handling `SecurityCapabilities` values ([#803], [#807])
* Fixed Rojo plugin erroring out when attempting to sync attributes with invalid names ([#809])
[#800]: https://github.com/rojo-rbx/rojo/pull/800
[#801]: https://github.com/rojo-rbx/rojo/pull/801
[#803]: https://github.com/rojo-rbx/rojo/pull/803
[#807]: https://github.com/rojo-rbx/rojo/pull/807
[#809]: https://github.com/rojo-rbx/rojo/pull/809
## [7.4.0-rc2] - October 3, 2023
* Fixed bug with parsing version for plugin validation ([#797])
[#797]: https://github.com/rojo-rbx/rojo/pull/797
## [7.4.0-rc1] - October 3, 2023
### Additions
#### Project format
* Added support for `.toml` files to `$path` ([#633])
* Added support for `Font` and `CFrame` attributes ([rbx-dom#299], [rbx-dom#296])
* Added the `emitLegacyScripts` field to the project format ([#765]). The behavior is outlined below:
| `emitLegacyScripts` Value | Action Taken by Rojo |
|---------------------------|------------------------------------------------------------------------------------------------------------------|
| false | Rojo emits Scripts with the appropriate `RunContext` for `*.client.lua` and `*.server.lua` files in the project. |
| true (default) | Rojo emits LocalScripts and Scripts with legacy `RunContext` (same behavior as previously). |
It can be used like this:
```json
{
"emitLegacyScripts": false,
"name": "MyCoolRunContextProject",
"tree": {
"$path": "src"
}
}
```
* Added `Terrain` classname inference, similar to services ([#771])
`Terrain` may now be defined in projects without using `$className`:
```json
"Workspace": {
"Terrain": {
"$path": "path/to/terrain.rbxm"
}
}
```
* Added support for `Terrain.MaterialColors` ([#770])
`Terrain.MaterialColors` is now represented in projects in a human readable format:
```json
"Workspace": {
"Terrain": {
"$path": "path/to/terrain.rbxm"
"$properties": {
"MaterialColors": {
"Grass": [10, 20, 30],
"Asphalt": [40, 50, 60],
"LeafyGrass": [255, 155, 55]
}
}
}
}
```
* Added better support for `Font` properties ([#731])
`FontFace` properties may now be defined using implicit property syntax:
```json
"TextBox": {
"$className": "TextBox",
"$properties": {
"FontFace": {
"family": "rbxasset://fonts/families/RobotoMono.json",
"weight": "Thin",
"style": "Normal"
}
}
}
```
#### Patch visualizer and notifications
* Added a setting to control patch confirmation behavior ([#774])
This is a new setting for controlling when the Rojo plugin prompts for confirmation before syncing. It has four options:
* Initial (default): prompts only once for a project in a given Studio session
* Always: always prompts for confirmation
* Large Changes: only prompts when there are more than X changed instances. The number of instances is configurable - an additional setting for the number of instances becomes available when this option is chosen
* Unlisted PlaceId: only prompts if the place ID is not present in servePlaceIds
* Added the ability to select Instances in patch visualizer ([#709])
Double-clicking an instance in the patch visualizer sets Roblox Studio's selection to the instance.
* Added a sync reminder notification. ([#689])
Rojo detects if you have previously synced to a place, and displays a notification reminding you to sync again:
![Rojo reminds you to sync a place that you've synced previously](https://user-images.githubusercontent.com/40185666/242397435-ccdfddf2-a63f-420c-bc18-a6e3d6455bba.png)
* Added rich Source diffs in patch visualizer ([#748])
A "View Diff" button for script sources is now present in the patch visualizer. Clicking it displays a side-by-side diff of the script changes:
![The patch visualizer contains a "view diff" button](https://user-images.githubusercontent.com/40185666/256065992-3f03558f-84b0-45a1-80eb-901f348cf067.png)
![The "View Diff" button opens a widget that displays a diff](https://user-images.githubusercontent.com/40185666/256066084-1d9d8fe8-7dad-4ee7-a542-b4aee35a5644.png)
* Patch visualizer now indicates what changes failed to apply. ([#717])
A clickable warning label is displayed when the Rojo plugin is unable to apply changes. Clicking the label displays precise information about which changes failed:
![Patch visualizer displays a clickable warning label when changes fail to apply](https://user-images.githubusercontent.com/40185666/252063660-f08399ef-1e16-4f1c-bed8-552821f98cef.png)
#### Miscellaneous
* Added `plugin` flag to the `build` command that outputs to the local plugins folder ([#735])
This is a flag that builds a Rojo project into Roblox Studio's plugins directory. This allows you to build a Rojo project and load it into Studio as a plugin without having to type the full path to the plugins directory. It can be used like this: `rojo build <PATH-TO-PROJECT> --plugin <FILE-NAME>`
* Added new plugin template to the `init` command ([#738])
This is a new template geared towards plugins. It is similar to the model template, but creates a `Script` instead of a `ModuleScript` in the `src` directory. It can be used like this: `rojo init --kind plugin`
* Added protection against syncing non-place projects as a place. ([#691])
* Add buttons for navigation on the Connected page ([#722])
### Fixes
* Significantly improved performance of `rojo serve` and `rojo build` on macOS. [#783]
* Significantly improved performance of `rojo sourcemap` ([#668])
* Fixed the diff visualizer of connected sessions. ([#674])
* Fixed disconnected session activity. ([#675])
* Skip confirming patches that contain only a datamodel name change. ([#688])
* Added sync reminder notification. ([#689])
* Added protection against syncing a model to a place. ([#691])
* Select Instances from diff tree view ([#709])
* Fix Rojo breaking when users undo/redo in Studio ([#708])
* Improved sync info text on Connected page. ([#692])
* Fix patch visualizer breaking when instances are removed during sync ([#713])
* Patch visualizer now indicates what changes failed to apply. ([#717])
* Add buttons for navigation on the Connected page ([#722])
* Improve tooltip behavior ([#723])
* Better settings controls ([#725])
* Rework patch visualizer with many fixes and improvements ([#726])
* Added support for syncing in `.toml` files ([#633])
* Add `plugin` flag to the `build` command that outputs to the local plugins folder ([#735])
* Added better support for `Font` properties ([#731])
* Add new plugin template to the `init` command ([#738])
* Added rich Source diffs in patch visualizer ([#748])
* Fix PatchTree performance issues ([#755])
* Don't override the initial enabled state for source diffing ([#760])
* Added support for `Terrain.MaterialColors` ([#770])
* Allow `Terrain` to be specified without a classname ([#771])
* Add Confirmation Behavior setting ([#774])
* Rework patch visualizer with many fixes and improvements ([#713], [#726], [#755])
[#631]: https://github.com/rojo-rbx/rojo/pull/631
[#761]: https://github.com/rojo-rbx/rojo/pull/761
[#745]: https://github.com/rojo-rbx/rojo/pull/745
[#668]: https://github.com/rojo-rbx/rojo/pull/668
[#674]: https://github.com/rojo-rbx/rojo/pull/674
[#675]: https://github.com/rojo-rbx/rojo/pull/675
@ -41,7 +159,6 @@
[#691]: https://github.com/rojo-rbx/rojo/pull/691
[#709]: https://github.com/rojo-rbx/rojo/pull/709
[#708]: https://github.com/rojo-rbx/rojo/pull/708
[#692]: https://github.com/rojo-rbx/rojo/pull/692
[#713]: https://github.com/rojo-rbx/rojo/pull/713
[#717]: https://github.com/rojo-rbx/rojo/pull/717
[#722]: https://github.com/rojo-rbx/rojo/pull/722
@ -54,10 +171,13 @@
[#738]: https://github.com/rojo-rbx/rojo/pull/738
[#748]: https://github.com/rojo-rbx/rojo/pull/748
[#755]: https://github.com/rojo-rbx/rojo/pull/755
[#760]: https://github.com/rojo-rbx/rojo/pull/760
[#765]: https://github.com/rojo-rbx/rojo/pull/765
[#770]: https://github.com/rojo-rbx/rojo/pull/770
[#771]: https://github.com/rojo-rbx/rojo/pull/771
[#774]: https://github.com/rojo-rbx/rojo/pull/774
[#783]: https://github.com/rojo-rbx/rojo/pull/783
[rbx-dom#299]: https://github.com/rojo-rbx/rbx-dom/pull/299
[rbx-dom#296]: https://github.com/rojo-rbx/rbx-dom/pull/296
## [7.3.0] - April 22, 2023
* Added `$attributes` to project format. ([#574])

865
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[package]
name = "rojo"
version = "7.3.0"
rust-version = "1.68.2"
version = "7.4.0-rc3"
rust-version = "1.70.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0"
@ -49,11 +49,11 @@ memofs = { version = "0.2.0", path = "crates/memofs" }
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "0.7.1"
rbx_dom_weak = "2.5.0"
rbx_reflection = "4.3.0"
rbx_reflection_database = "0.2.7"
rbx_xml = "0.13.1"
rbx_binary = "0.7.3"
rbx_dom_weak = "2.6.0"
rbx_reflection = "4.4.0"
rbx_reflection_database = "0.2.8"
rbx_xml = "0.13.2"
anyhow = "1.0.44"
backtrace = "0.3.61"
@ -102,6 +102,7 @@ anyhow = "1.0.44"
bincode = "1.3.3"
fs-err = "2.6.0"
maplit = "1.0.2"
semver = "1.0.19"
[dev-dependencies]
rojo-insta-ext = { path = "crates/rojo-insta-ext" }

View file

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

View file

@ -7,6 +7,7 @@ use fs_err as fs;
use fs_err::File;
use maplit::hashmap;
use memofs::VfsSnapshot;
use semver::Version;
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
println!("cargo:rerun-if-changed={}", path.display());
@ -43,6 +44,19 @@ fn main() -> Result<(), anyhow::Error> {
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let plugin_root = PathBuf::from(root_dir).join("plugin");
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
let plugin_version =
Version::parse(fs::read_to_string(plugin_root.join("Version.txt"))?.trim())?;
assert!(
our_version.major == plugin_version.major,
"plugin version does not match Cargo version"
);
assert!(
our_version.minor == plugin_version.minor,
"plugin version does not match Cargo version"
);
let snapshot = VfsSnapshot::dir(hashmap! {
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
@ -51,6 +65,7 @@ fn main() -> Result<(), anyhow::Error> {
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
"Version.txt" => snapshot_from_fs_path(&plugin_root.join("Version.txt"))?,
});
let out_path = Path::new(&out_dir).join("plugin.bincode");

View file

@ -1,6 +1,7 @@
# memofs Changelog
## Unreleased Changes
* Changed the `StdBackend` file watcher to use `PollWatcher` on macOS.
## 0.2.0 (2021-08-23)
* Updated to `crossbeam-channel` 0.5.1.
@ -15,4 +16,4 @@
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
## 0.1.0 (2020-03-10)
* Initial release
* Initial release

View file

@ -5,19 +5,34 @@ use std::thread;
use std::time::Duration;
use crossbeam_channel::Receiver;
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use notify::{DebouncedEvent, RecursiveMode, Watcher};
#[cfg(target_os = "macos")]
use notify::PollWatcher;
#[cfg(not(target_os = "macos"))]
use notify::{watcher, RecommendedWatcher};
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
/// `VfsBackend` that uses `std::fs` and the `notify` crate.
pub struct StdBackend {
// We use PollWatcher on macos because using the KQueue watcher
// can cause some gnarly performance problems.
#[cfg(target_os = "macos")]
watcher: PollWatcher,
#[cfg(not(target_os = "macos"))]
watcher: RecommendedWatcher,
watcher_receiver: Receiver<VfsEvent>,
}
impl StdBackend {
pub fn new() -> StdBackend {
let (notify_tx, notify_rx) = mpsc::channel();
#[cfg(target_os = "macos")]
let watcher = PollWatcher::new(notify_tx, Duration::from_millis(50)).unwrap();
#[cfg(not(target_os = "macos"))]
let watcher = watcher(notify_tx, Duration::from_millis(50)).unwrap();
let (tx, rx) = crossbeam_channel::unbounded();

1
plugin/Version.txt Normal file
View file

@ -0,0 +1 @@
7.4.0-rc3

View file

@ -1,25 +1,27 @@
{
"name": "Rojo",
"tree": {
"$className": "Folder",
"Plugin": {
"$path": "src"
},
"Packages": {
"$path": "Packages",
"Log": {
"$path": "log"
},
"Http": {
"$path": "http"
},
"Fmt": {
"$path": "fmt"
},
"RbxDom": {
"$path": "rbx_dom_lua"
}
}
}
}
"name": "Rojo",
"tree": {
"$className": "Folder",
"Plugin": {
"$path": "src"
},
"Packages": {
"$path": "Packages",
"Log": {
"$path": "log"
},
"Http": {
"$path": "http"
},
"Fmt": {
"$path": "fmt"
},
"RbxDom": {
"$path": "rbx_dom_lua"
}
},
"Version": {
"$path": "Version.txt"
}
}
}

View file

@ -20,8 +20,8 @@ local function serializeFloat(value)
return value
end
local ALL_AXES = {"X", "Y", "Z"}
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
local ALL_AXES = { "X", "Y", "Z" }
local ALL_FACES = { "Right", "Top", "Back", "Left", "Bottom", "Front" }
local EncodedValue = {}
@ -37,7 +37,10 @@ types = {
if ok then
output[key] = result
else
local warning = ("Could not decode attribute value of type %q: %s"):format(typeof(value), tostring(result))
local warning = ("Could not decode attribute value of type %q: %s"):format(
typeof(value),
tostring(result)
)
warn(warning)
end
end
@ -53,7 +56,10 @@ types = {
if ok then
output[key] = result
else
local warning = ("Could not encode attribute value of type %q: %s"):format(typeof(value), tostring(result))
local warning = ("Could not encode attribute value of type %q: %s"):format(
typeof(value),
tostring(result)
)
warn(warning)
end
end
@ -111,6 +117,7 @@ types = {
local pos = pod.position
local orient = pod.orientation
--stylua: ignore
return CFrame.new(
pos[1], pos[2], pos[3],
orient[1][1], orient[1][2], orient[1][3],
@ -120,17 +127,14 @@ types = {
end,
toPod = function(roblox)
local x, y, z,
r00, r01, r02,
r10, r11, r12,
r20, r21, r22 = roblox:GetComponents()
local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = roblox:GetComponents()
return {
position = {x, y, z},
position = { x, y, z },
orientation = {
{r00, r01, r02},
{r10, r11, r12},
{r20, r21, r22},
{ r00, r01, r02 },
{ r10, r11, r12 },
{ r20, r21, r22 },
},
}
end,
@ -140,7 +144,7 @@ types = {
fromPod = unpackDecoder(Color3.new),
toPod = function(roblox)
return {roblox.r, roblox.g, roblox.b}
return { roblox.r, roblox.g, roblox.b }
end,
},
@ -161,10 +165,7 @@ types = {
local keypoints = {}
for index, keypoint in ipairs(pod.keypoints) do
keypoints[index] = ColorSequenceKeypoint.new(
keypoint.time,
types.Color3.fromPod(keypoint.color)
)
keypoints[index] = ColorSequenceKeypoint.new(keypoint.time, types.Color3.fromPod(keypoint.color))
end
return ColorSequence.new(keypoints)
@ -279,7 +280,7 @@ types = {
pod[material.Name] = {
math.round(math.clamp(color.R, 0, 1) * 255),
math.round(math.clamp(color.G, 0, 1) * 255),
math.round(math.clamp(color.B, 0, 1) * 255)
math.round(math.clamp(color.B, 0, 1) * 255),
}
end
return pod
@ -290,7 +291,7 @@ types = {
fromPod = unpackDecoder(NumberRange.new),
toPod = function(roblox)
return {roblox.Min, roblox.Max}
return { roblox.Min, roblox.Max }
end,
},
@ -299,11 +300,7 @@ types = {
local keypoints = {}
for index, keypoint in ipairs(pod.keypoints) do
keypoints[index] = NumberSequenceKeypoint.new(
keypoint.time,
keypoint.value,
keypoint.envelope
)
keypoints[index] = NumberSequenceKeypoint.new(keypoint.time, keypoint.value, keypoint.envelope)
end
return NumberSequence.new(keypoints)
@ -358,10 +355,7 @@ types = {
Ray = {
fromPod = function(pod)
return Ray.new(
types.Vector3.fromPod(pod.origin),
types.Vector3.fromPod(pod.direction)
)
return Ray.new(types.Vector3.fromPod(pod.origin), types.Vector3.fromPod(pod.direction))
end,
toPod = function(roblox)
@ -374,10 +368,7 @@ types = {
Rect = {
fromPod = function(pod)
return Rect.new(
types.Vector2.fromPod(pod[1]),
types.Vector2.fromPod(pod[2])
)
return Rect.new(types.Vector2.fromPod(pod[1]), types.Vector2.fromPod(pod[2]))
end,
toPod = function(roblox)
@ -389,31 +380,28 @@ types = {
},
Ref = {
fromPod = function(_pod)
fromPod = function(_)
error("Ref cannot be decoded on its own")
end,
toPod = function(_roblox)
toPod = function(_)
error("Ref can not be encoded on its own")
end,
},
Region3 = {
fromPod = function(pod)
fromPod = function(_)
error("Region3 is not implemented")
end,
toPod = function(roblox)
toPod = function(_)
error("Region3 is not implemented")
end,
},
Region3int16 = {
fromPod = function(pod)
return Region3int16.new(
types.Vector3int16.fromPod(pod[1]),
types.Vector3int16.fromPod(pod[2])
)
return Region3int16.new(types.Vector3int16.fromPod(pod[1]), types.Vector3int16.fromPod(pod[2]))
end,
toPod = function(roblox)
@ -425,11 +413,11 @@ types = {
},
SharedString = {
fromPod = function(pod)
fromPod = function(_pod)
error("SharedString is not supported")
end,
toPod = function(roblox)
toPod = function(_roblox)
error("SharedString is not supported")
end,
},
@ -443,16 +431,13 @@ types = {
fromPod = unpackDecoder(UDim.new),
toPod = function(roblox)
return {roblox.Scale, roblox.Offset}
return { roblox.Scale, roblox.Offset }
end,
},
UDim2 = {
fromPod = function(pod)
return UDim2.new(
types.UDim.fromPod(pod[1]),
types.UDim.fromPod(pod[2])
)
return UDim2.new(types.UDim.fromPod(pod[1]), types.UDim.fromPod(pod[2]))
end,
toPod = function(roblox)
@ -483,7 +468,7 @@ types = {
fromPod = unpackDecoder(Vector2int16.new),
toPod = function(roblox)
return {roblox.X, roblox.Y}
return { roblox.X, roblox.Y }
end,
},
@ -503,7 +488,7 @@ types = {
fromPod = unpackDecoder(Vector3int16.new),
toPod = function(roblox)
return {roblox.X, roblox.Y, roblox.Z}
return { roblox.X, roblox.Y, roblox.Z }
end,
},
}

View file

@ -1,67 +0,0 @@
return function()
local HttpService = game:GetService("HttpService")
local EncodedValue = require(script.Parent.EncodedValue)
local allValues = require(script.Parent.allValues)
local function deepEq(a, b)
if typeof(a) ~= typeof(b) then
return false
end
local ty = typeof(a)
if ty == "table" then
local visited = {}
for key, valueA in a do
visited[key] = true
if not deepEq(valueA, b[key]) then
return false
end
end
for key, valueB in b do
if visited[key] then
continue
end
if not deepEq(valueB, a[key]) then
return false
end
end
return true
else
return a == b
end
end
local extraAssertions = {
CFrame = function(value)
expect(value).to.equal(CFrame.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
end,
}
for testName, testEntry in allValues do
it("round trip " .. testName, function()
local decodeOk, decoded = EncodedValue.decode(testEntry.value)
assert(decodeOk, decoded)
if extraAssertions[testName] ~= nil then
extraAssertions[testName](decoded)
end
local encodeOk, encoded = EncodedValue.encode(decoded, testEntry.ty)
assert(encodeOk, encoded)
if not deepEq(encoded, testEntry.value) then
local expected = HttpService:JSONEncode(testEntry.value)
local actual = HttpService:JSONEncode(encoded)
error(`Round-trip results did not match.\nExpected:\n{expected}\nActual:\n{actual}`)
end
end)
end
end

View file

@ -25,4 +25,4 @@ function Error:__tostring()
return ("Error(%s: %s)"):format(self.kind, tostring(self.extra))
end
return Error
return Error

View file

@ -136,4 +136,4 @@ end
return {
decode = decodeBase64,
encode = encodeBase64,
}
}

View file

@ -1,29 +0,0 @@
return function()
local base64 = require(script.Parent.base64)
it("should encode and decode", function()
local function try(str, expected)
local encoded = base64.encode(str)
expect(encoded).to.equal(expected)
expect(base64.decode(encoded)).to.equal(str)
end
try("Man", "TWFu")
try("Ma", "TWE=")
try("M", "TQ==")
try("ManM", "TWFuTQ==")
try(
[[Man is distinguished, not only by his reason, but by this ]]..
[[singular passion from other animals, which is a lust of the ]]..
[[mind, that by a perseverance of delight in the continued and ]]..
[[indefatigable generation of knowledge, exceeds the short ]]..
[[vehemence of any carnal pleasure.]],
[[TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sI]]..
[[GJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYW]]..
[[xzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJ]]..
[[zZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRl]]..
[[ZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZ]]..
[[SBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=]]
)
end)
end

View file

@ -1,4 +1,5 @@
local CollectionService = game:GetService("CollectionService")
local ScriptEditorService = game:GetService("ScriptEditorService")
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
local TERRAIN_MATERIAL_COLORS = {
@ -36,9 +37,24 @@ return {
end,
write = function(instance, _, value)
local existing = instance:GetAttributes()
local didAllWritesSucceed = true
for key, attr in pairs(value) do
instance:SetAttribute(key, attr)
for attributeName, attributeValue in pairs(value) do
local isNameValid =
-- For our SetAttribute to succeed, the attribute name must be
-- less than or equal to 100 characters...
#attributeName <= 100
-- ...must only contain alphanumeric characters, periods, hyphens,
-- underscores, or forward slashes...
and attributeName:match("[^%w%.%-_/]") == nil
-- ... and must not use the RBX prefix, which is reserved by Roblox.
and attributeName:sub(1, 3) ~= "RBX"
if isNameValid then
instance:SetAttribute(attributeName, attributeValue)
else
didAllWritesSucceed = false
end
end
for key in pairs(existing) do
@ -47,7 +63,7 @@ return {
end
end
return true
return didAllWritesSucceed
end,
},
Tags = {
@ -77,10 +93,10 @@ return {
},
LocalizationTable = {
Contents = {
read = function(instance, key)
read = function(instance, _)
return true, instance:GetContents()
end,
write = function(instance, key, value)
write = function(instance, _, value)
instance:SetContents(value)
return true
end,
@ -116,4 +132,34 @@ return {
end,
},
},
Script = {
Source = {
read = function(instance: Script)
return true, ScriptEditorService:GetEditorSource(instance)
end,
write = function(instance: Script, _, value: string)
task.spawn(function()
ScriptEditorService:UpdateSourceAsync(instance, function()
return value
end)
end)
return true
end,
},
},
ModuleScript = {
Source = {
read = function(instance: ModuleScript)
return true, ScriptEditorService:GetEditorSource(instance)
end,
write = function(instance: ModuleScript, _, value: string)
task.spawn(function()
ScriptEditorService:UpdateSourceAsync(instance, function()
return value
end)
end)
return true
end,
},
},
}

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,8 @@ local function findCanonicalPropertyDescriptor(className, propertyName)
return PropertyDescriptor.fromRaw(
currentClass.Properties[aliasData.AliasFor],
currentClassName,
aliasData.AliasFor)
aliasData.AliasFor
)
end
return nil

View file

@ -1,7 +0,0 @@
return function()
local RbxDom = require(script.Parent)
it("should load", function()
expect(RbxDom).to.be.ok()
end)
end

View file

@ -2,11 +2,24 @@ local strict = require(script.Parent.strict)
local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
local Version = script.Parent.Parent.Version
local major, minor, patch, metadata = Version.Value:match("^(%d+)%.(%d+)%.(%d+)(.*)$")
local realVersion = { major, minor, patch, metadata }
for i = 1, 3 do
local num = tonumber(realVersion[i])
if num then
realVersion[i] = num
else
error(("invalid version `%s` (field %d)"):format(realVersion[i], i))
end
end
return strict("Config", {
isDevBuild = isDevBuild,
codename = "Epiphany",
version = { 7, 3, 0 },
expectedServerVersionString = "7.2 or newer",
version = realVersion,
expectedServerVersionString = ("%d.%d or newer"):format(realVersion[1], realVersion[2]),
protocolVersion = 4,
defaultHost = "localhost",
defaultPort = "34872",

View file

@ -233,7 +233,7 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
addProp(
prop,
if currentSuccess then currentValue else "[Error]",
if incomingSuccess then incomingValue else next(incoming)
if incomingSuccess then incomingValue else select(2, next(incoming))
)
end
@ -359,7 +359,7 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
if success then
table.insert(changeList, { prop, "N/A", incomingValue })
else
table.insert(changeList, { prop, "N/A", next(incoming) })
table.insert(changeList, { prop, "N/A", select(2, next(incoming)) })
end
end

View file

@ -282,7 +282,7 @@ function ServeSession:__initialSync(serverInfo)
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
if not PatchSet.isEmpty(unappliedPatch) then
Log.warn(
Log.debug(
"Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
)
@ -309,7 +309,7 @@ function ServeSession:__mainSyncLoop()
local unappliedPatch = self.__reconciler:applyPatch(message)
if not PatchSet.isEmpty(unappliedPatch) then
Log.warn(
Log.debug(
"Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
)

View file

@ -0,0 +1,35 @@
---
source: tests/tests/build.rs
expression: contents
---
<roblox version="4">
<Item class="Folder" referent="0">
<Properties>
<string name="Name">nested_runcontext</string>
</Properties>
<Item class="Folder" referent="1">
<Properties>
<string name="Name">folder1</string>
</Properties>
<Item class="Script" referent="2">
<Properties>
<string name="Name">test</string>
<token name="RunContext">1</token>
<string name="Source"></string>
</Properties>
</Item>
</Item>
<Item class="Folder" referent="3">
<Properties>
<string name="Name">folder2</string>
</Properties>
<Item class="Script" referent="4">
<Properties>
<string name="Name">test</string>
<token name="RunContext">0</token>
<string name="Source"></string>
</Properties>
</Item>
</Item>
</Item>
</roblox>

View file

@ -11,6 +11,7 @@ expression: contents
<Properties>
<string name="Name">hello</string>
<bool name="Disabled">true</bool>
<token name="RunContext">0</token>
<string name="Source">-- This script should be marked 'Disabled'</string>
</Properties>
</Item>

View file

@ -10,6 +10,7 @@ expression: contents
<Item class="Script" referent="1">
<Properties>
<string name="Name">serverScript</string>
<token name="RunContext">0</token>
<string name="Source">-- This is a Lua server script</string>
</Properties>
</Item>

View file

@ -6,6 +6,7 @@ expression: contents
<Item class="Script" referent="0">
<Properties>
<string name="Name">server_init</string>
<token name="RunContext">0</token>
<string name="Source">return "From folder/init.server.lua"</string>
</Properties>
</Item>

View file

@ -0,0 +1,13 @@
{
"name": "nested_runcontext",
"emitLegacyScripts": false,
"tree": {
"$className": "Folder",
"folder1": {
"$path": "folder1"
},
"folder2": {
"$path": "folder2"
}
}
}

View file

@ -0,0 +1,7 @@
{
"name": "nested_runcontext",
"emitLegacyScripts": true,
"tree": {
"$path": "folder3"
}
}

View file

@ -24,6 +24,8 @@ instances:
Name: bar
Parent: id-2
Properties:
RunContext:
Enum: 0
Source:
String: "-- Hello, from bar!"
id-4:

View file

@ -24,6 +24,8 @@ instances:
Name: bar
Parent: id-2
Properties:
RunContext:
Enum: 0
Source:
String: "-- Hello, from bar!"
id-4:

View file

@ -25,7 +25,7 @@ pub use self::upload::UploadCommand;
/// Command line options that Rojo accepts, defined using the clap crate.
#[derive(Debug, Parser)]
#[clap(name = "Rojo", version, about, author)]
#[clap(name = "Rojo", version, about)]
pub struct Options {
#[clap(flatten)]
pub global: GlobalOptions,

View file

@ -14,7 +14,7 @@ use tokio::runtime::Runtime;
use crate::{
serve_session::ServeSession,
snapshot::{InstanceWithMeta, RojoTree},
snapshot::{AppliedPatchSet, InstanceWithMeta, RojoTree},
};
use super::resolve_path;
@ -90,10 +90,12 @@ impl SourcemapCommand {
loop {
let receiver = session.message_queue().subscribe(cursor);
let (new_cursor, _patch_set) = rt.block_on(receiver).unwrap();
let (new_cursor, patch_set) = rt.block_on(receiver).unwrap();
cursor = new_cursor;
write_sourcemap(&session, self.output.as_deref(), filter)?;
if patch_set_affects_sourcemap(&session, &patch_set, filter) {
write_sourcemap(&session, self.output.as_deref(), filter)?;
}
}
}
@ -116,6 +118,43 @@ fn filter_non_scripts(instance: &InstanceWithMeta) -> bool {
)
}
fn patch_set_affects_sourcemap(
session: &ServeSession,
patch_set: &[AppliedPatchSet],
filter: fn(&InstanceWithMeta) -> bool,
) -> bool {
let tree = session.tree();
// A sourcemap has probably changed when:
patch_set.par_iter().any(|set| {
// 1. An instance was removed, in which case it will no
// longer exist in the tree and we cant check the filter
!set.removed.is_empty()
// 2. A newly added instance passes the filter
|| set.added.iter().any(|referent| {
let instance = tree
.get_instance(*referent)
.expect("instance did not exist when updating sourcemap");
filter(&instance)
})
// 3. An existing instance has its class name, name,
// or file paths changed, and passes the filter
|| set.updated.iter().any(|updated| {
let changed = updated.changed_class_name.is_some()
|| updated.changed_name.is_some()
|| updated.changed_metadata.is_some();
if changed {
let instance = tree
.get_instance(updated.id)
.expect("instance did not exist when updating sourcemap");
filter(&instance)
} else {
false
}
})
})
}
fn recurse_create_node<'a>(
tree: &'a RojoTree,
referent: Ref,

View file

@ -24,7 +24,7 @@ impl<K: Hash + Eq, V: Eq> MultiMap<K, V> {
K: Borrow<Q>,
Q: Hash + Eq,
{
self.inner.get(k.borrow()).map(Vec::as_slice).unwrap_or(&[])
self.inner.get(k).map(Vec::as_slice).unwrap_or(&[])
}
pub fn insert(&mut self, k: K, v: V) {

View file

@ -8,7 +8,9 @@ use std::{
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{glob::Glob, resolution::UnresolvedValue};
use crate::{
glob::Glob, resolution::UnresolvedValue, snapshot_middleware::emit_legacy_scripts_default,
};
static PROJECT_FILENAME: &str = "default.project.json";
@ -73,6 +75,14 @@ pub struct Project {
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_address: Option<IpAddr>,
/// Determines if rojo should emit scripts with the appropriate `RunContext` for `*.client.lua` and `*.server.lua` files in the project.
/// Or, if rojo should keep the legacy behavior of emitting LocalScripts and Scripts with legacy Runcontext
#[serde(
default = "emit_legacy_scripts_default",
skip_serializing_if = "Option::is_none"
)]
pub emit_legacy_scripts: Option<bool>,
/// A list of globs, relative to the folder the project file is in, that
/// match files that should be excluded if Rojo encounters them.
#[serde(default, skip_serializing_if = "Vec::is_empty")]

View file

@ -123,7 +123,8 @@ impl ServeSession {
let root_id = tree.get_root_id();
let instance_context = InstanceContext::default();
let instance_context =
InstanceContext::with_emit_legacy_scripts(root_project.emit_legacy_scripts);
log::trace!("Generating snapshot of instances from VFS");
let snapshot = snapshot_from_vfs(&instance_context, &vfs, start_path)?;

View file

@ -6,7 +6,10 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{glob::Glob, path_serializer, project::ProjectNode};
use crate::{
glob::Glob, path_serializer, project::ProjectNode,
snapshot_middleware::emit_legacy_scripts_default,
};
/// Rojo-specific metadata that can be associated with an instance or a snapshot
/// of an instance.
@ -103,9 +106,26 @@ impl Default for InstanceMetadata {
pub struct InstanceContext {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub path_ignore_rules: Arc<Vec<PathIgnoreRule>>,
pub emit_legacy_scripts: bool,
}
impl InstanceContext {
pub fn new() -> Self {
Self {
path_ignore_rules: Arc::new(Vec::new()),
emit_legacy_scripts: emit_legacy_scripts_default().unwrap(),
}
}
pub fn with_emit_legacy_scripts(emit_legacy_scripts: Option<bool>) -> Self {
Self {
emit_legacy_scripts: emit_legacy_scripts
.or_else(emit_legacy_scripts_default)
.unwrap(),
..Self::new()
}
}
/// Extend the list of ignore rules in the context with the given new rules.
pub fn add_path_ignore_rules<I>(&mut self, new_rules: I)
where
@ -123,13 +143,15 @@ impl InstanceContext {
let rules = Arc::make_mut(&mut self.path_ignore_rules);
rules.extend(new_rules);
}
pub fn set_emit_legacy_scripts(&mut self, emit_legacy_scripts: bool) {
self.emit_legacy_scripts = emit_legacy_scripts;
}
}
impl Default for InstanceContext {
fn default() -> Self {
InstanceContext {
path_ignore_rules: Arc::new(Vec::new()),
}
Self::new()
}
}

View file

@ -1,7 +1,6 @@
---
source: src/snapshot/tests/apply.rs
expression: tree_view
---
id: id-1
name: ROOT
@ -12,6 +11,7 @@ properties:
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
children: []

View file

@ -9,5 +9,7 @@ properties: {}
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
children: []

View file

@ -1,7 +1,6 @@
---
source: src/snapshot/tests/apply.rs
expression: tree_view
---
id: id-1
name: ROOT
@ -12,6 +11,7 @@ properties:
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
children: []

View file

@ -9,5 +9,7 @@ properties: {}
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
children: []

View file

@ -10,7 +10,8 @@ added_instances:
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
name: New
class_name: Folder
properties: {}

View file

@ -1,8 +1,8 @@
use std::{path::Path, str};
use std::{collections::HashMap, path::Path, str};
use anyhow::Context;
use maplit::hashmap;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::types::Enum;
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
@ -12,6 +12,13 @@ use super::{
util::match_trailing,
};
#[derive(Debug)]
enum ScriptType {
Server,
Client,
Module,
}
/// Core routine for turning Lua files into snapshots.
pub fn snapshot_lua(
context: &InstanceContext,
@ -20,36 +27,58 @@ pub fn snapshot_lua(
) -> anyhow::Result<Option<InstanceSnapshot>> {
let file_name = path.file_name().unwrap().to_string_lossy();
let (class_name, instance_name) = if let Some(name) = match_trailing(&file_name, ".server.lua")
let run_context_enums = &rbx_reflection_database::get()
.enums
.get("RunContext")
.expect("Unable to get RunContext enums!")
.items;
let (script_type, instance_name) = if let Some(name) = match_trailing(&file_name, ".server.lua")
{
("Script", name)
(ScriptType::Server, name)
} else if let Some(name) = match_trailing(&file_name, ".client.lua") {
("LocalScript", name)
(ScriptType::Client, name)
} else if let Some(name) = match_trailing(&file_name, ".lua") {
("ModuleScript", name)
(ScriptType::Module, name)
} else if let Some(name) = match_trailing(&file_name, ".server.luau") {
("Script", name)
(ScriptType::Server, name)
} else if let Some(name) = match_trailing(&file_name, ".client.luau") {
("LocalScript", name)
(ScriptType::Client, name)
} else if let Some(name) = match_trailing(&file_name, ".luau") {
("ModuleScript", name)
(ScriptType::Module, name)
} else {
return Ok(None);
};
let (class_name, run_context) = match (context.emit_legacy_scripts, script_type) {
(false, ScriptType::Server) => ("Script", run_context_enums.get("Server")),
(false, ScriptType::Client) => ("Script", run_context_enums.get("Client")),
(true, ScriptType::Server) => ("Script", run_context_enums.get("Legacy")),
(true, ScriptType::Client) => ("LocalScript", None),
(_, ScriptType::Module) => ("ModuleScript", None),
};
let contents = vfs.read(path)?;
let contents_str = str::from_utf8(&contents)
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?
.to_owned();
let mut properties = HashMap::with_capacity(2);
properties.insert("Source".to_owned(), contents_str.into());
if let Some(run_context) = run_context {
properties.insert(
"RunContext".to_owned(),
Enum::from_u32(run_context.to_owned()).into(),
);
}
let meta_path = path.with_file_name(format!("{}.meta.json", instance_name));
let mut snapshot = InstanceSnapshot::new()
.name(instance_name)
.class_name(class_name)
.properties(hashmap! {
"Source".to_owned() => contents_str.into(),
})
.properties(properties)
.metadata(
InstanceMetadata::new()
.instigating_source(path)
@ -107,26 +136,53 @@ pub fn snapshot_lua_init(
mod test {
use super::*;
use maplit::hashmap;
use memofs::{InMemoryFs, VfsSnapshot};
#[test]
fn module_from_vfs() {
fn class_module_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
snapshot_lua(&InstanceContext::default(), &mut vfs, Path::new("/foo.lua"))
.unwrap()
.unwrap();
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/foo.lua"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn server_from_vfs() {
fn runcontext_module_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/foo.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn class_server_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@ -134,18 +190,41 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::default(),
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/foo.server.lua"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn client_from_vfs() {
fn runcontext_server_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/foo.server.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn class_client_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.client.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@ -153,14 +232,37 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::default(),
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/foo.client.lua"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn runcontext_client_from_vfs() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.client.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/foo.client.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[ignore = "init.lua functionality has moved to the root snapshot function"]
@ -177,16 +279,21 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
snapshot_lua(&InstanceContext::default(), &mut vfs, Path::new("/root"))
.unwrap()
.unwrap();
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/root"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn module_with_meta() {
fn class_module_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@ -204,16 +311,53 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
snapshot_lua(&InstanceContext::default(), &mut vfs, Path::new("/foo.lua"))
.unwrap()
.unwrap();
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/foo.lua"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn script_with_meta() {
fn runcontext_module_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
imfs.load_snapshot(
"/foo.meta.json",
VfsSnapshot::file(
r#"
{
"ignoreUnknownInstances": true
}
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/foo.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn class_script_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@ -232,18 +376,52 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::default(),
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/foo.server.lua"),
)
.unwrap()
.unwrap();
insta::assert_yaml_snapshot!(instance_snapshot);
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn script_disabled() {
fn runcontext_script_with_meta() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/foo.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
imfs.load_snapshot(
"/foo.meta.json",
VfsSnapshot::file(
r#"
{
"ignoreUnknownInstances": true
}
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/foo.server.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn class_script_disabled() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/bar.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
@ -264,7 +442,41 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::default(),
&InstanceContext::with_emit_legacy_scripts(Some(true)),
&mut vfs,
Path::new("/bar.server.lua"),
)
.unwrap()
.unwrap();
insta::with_settings!({ sort_maps => true }, {
insta::assert_yaml_snapshot!(instance_snapshot);
});
}
#[test]
fn runcontext_script_disabled() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot("/bar.server.lua", VfsSnapshot::file("Hello there!"))
.unwrap();
imfs.load_snapshot(
"/bar.meta.json",
VfsSnapshot::file(
r#"
{
"properties": {
"Disabled": true
}
}
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot = snapshot_lua(
&InstanceContext::with_emit_legacy_scripts(Some(false)),
&mut vfs,
Path::new("/bar.server.lua"),
)

View file

@ -38,7 +38,7 @@ use self::{
util::PathExt,
};
pub use self::project::snapshot_project_node;
pub use self::{project::snapshot_project_node, util::emit_legacy_scripts_default};
/// The main entrypoint to the snapshot function. This function can be pointed
/// at any path and will return something if Rojo knows how to deal with it.

View file

@ -12,7 +12,7 @@ use crate::{
},
};
use super::snapshot_from_vfs;
use super::{emit_legacy_scripts_default, snapshot_from_vfs};
pub fn snapshot_project(
context: &InstanceContext,
@ -30,6 +30,12 @@ pub fn snapshot_project(
});
context.add_path_ignore_rules(rules);
context.set_emit_legacy_scripts(
project
.emit_legacy_scripts
.or_else(emit_legacy_scripts_default)
.unwrap(),
);
match snapshot_project_node(&context, path, &project.name, &project.tree, vfs, None)? {
Some(found_snapshot) => {
@ -77,7 +83,7 @@ pub fn snapshot_project_node(
let name = Cow::Owned(instance_name.to_owned());
let mut properties = HashMap::new();
let mut children = Vec::new();
let mut metadata = InstanceMetadata::default();
let mut metadata = InstanceMetadata::new().context(context);
if let Some(path_node) = &node.path {
let path = path_node.path();

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.csv
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: LocalizationTable
properties:

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.csv
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: LocalizationTable
properties:

View file

@ -17,7 +17,8 @@ metadata:
- /foo/init.client.lua
- /foo/init.client.luau
- /foo/init.csv
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: Folder
properties: {}

View file

@ -17,7 +17,8 @@ metadata:
- /foo/init.client.lua
- /foo/init.client.luau
- /foo/init.csv
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: Folder
properties: {}
@ -37,7 +38,8 @@ children:
- /foo/Child/init.client.lua
- /foo/Child/init.client.luau
- /foo/Child/init.csv
context: {}
context:
emit_legacy_scripts: true
name: Child
class_name: Folder
properties: {}

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.json
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: ModuleScript
properties:

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo.model.json
relevant_paths:
- /foo.model.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: IntValue
properties:
@ -20,7 +21,8 @@ children:
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
name: The Child
class_name: StringValue
properties: {}

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo.model.json
relevant_paths:
- /foo.model.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: IntValue
properties:
@ -20,7 +21,8 @@ children:
metadata:
ignore_unknown_instances: false
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
name: The Child
class_name: StringValue
properties: {}

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.client.lua
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: LocalScript
properties:

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.lua
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: ModuleScript
properties:

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.lua
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: ModuleScript
properties:

View file

@ -10,12 +10,15 @@ metadata:
relevant_paths:
- /bar.server.lua
- /bar.meta.json
context: {}
context:
emit_legacy_scripts: true
name: bar
class_name: Script
properties:
Disabled:
Bool: true
RunContext:
Enum: 0
Source:
String: Hello there!
children: []

View file

@ -10,10 +10,13 @@ metadata:
relevant_paths:
- /foo.server.lua
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: Script
properties:
RunContext:
Enum: 0
Source:
String: Hello there!
children: []

View file

@ -10,10 +10,13 @@ metadata:
relevant_paths:
- /foo.server.lua
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: Script
properties:
RunContext:
Enum: 0
Source:
String: Hello there!
children: []

View file

@ -13,7 +13,8 @@ metadata:
- /root/init.lua
- /root/init.server.lua
- /root/init.client.lua
context: {}
context:
script_type: Class
name: root
class_name: ModuleScript
properties:

View file

@ -0,0 +1,23 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
Path: /foo.client.lua
relevant_paths:
- /foo.client.lua
- /foo.meta.json
context:
emit_legacy_scripts: false
name: foo
class_name: Script
properties:
RunContext:
Enum: 2
Source:
String: Hello there!
children: []

View file

@ -0,0 +1,21 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
Path: /foo.lua
relevant_paths:
- /foo.lua
- /foo.meta.json
context:
emit_legacy_scripts: false
name: foo
class_name: ModuleScript
properties:
Source:
String: Hello there!
children: []

View file

@ -0,0 +1,21 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: true
instigating_source:
Path: /foo.lua
relevant_paths:
- /foo.lua
- /foo.meta.json
context:
emit_legacy_scripts: false
name: foo
class_name: ModuleScript
properties:
Source:
String: Hello there!
children: []

View file

@ -0,0 +1,25 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
Path: /bar.server.lua
relevant_paths:
- /bar.server.lua
- /bar.meta.json
context:
emit_legacy_scripts: false
name: bar
class_name: Script
properties:
Disabled:
Bool: true
RunContext:
Enum: 1
Source:
String: Hello there!
children: []

View file

@ -0,0 +1,23 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: true
instigating_source:
Path: /foo.server.lua
relevant_paths:
- /foo.server.lua
- /foo.meta.json
context:
emit_legacy_scripts: false
name: foo
class_name: Script
properties:
RunContext:
Enum: 1
Source:
String: Hello there!
children: []

View file

@ -0,0 +1,23 @@
---
source: src/snapshot_middleware/lua.rs
expression: instance_snapshot
---
snapshot_id: "00000000000000000000000000000000"
metadata:
ignore_unknown_instances: false
instigating_source:
Path: /foo.server.lua
relevant_paths:
- /foo.server.lua
- /foo.meta.json
context:
emit_legacy_scripts: false
name: foo
class_name: Script
properties:
RunContext:
Enum: 1
Source:
String: Hello there!
children: []

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo/hello.project.json
relevant_paths:
- /foo/hello.project.json
context: {}
context:
emit_legacy_scripts: true
name: direct-project
class_name: Model
properties: {}

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo/default.project.json
relevant_paths:
- /foo/default.project.json
context: {}
context:
script_type: Class
name: indirect-project
class_name: Folder
properties: {}

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo/other.project.json
- /foo/default.project.json
context: {}
context:
emit_legacy_scripts: true
name: path-property-override
class_name: StringValue
properties:

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo.project.json
relevant_paths:
- /foo.project.json
context: {}
context:
emit_legacy_scripts: true
name: children
class_name: Folder
properties: {}
@ -24,7 +25,8 @@ children:
- $className: Model
- Folder
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
name: Child
class_name: Model
properties: {}

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo/other.project.json
- /foo/default.project.json
context: {}
context:
emit_legacy_scripts: true
name: path-project
class_name: Model
properties: {}

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo/other.project.json
- /foo/default.project.json
context: {}
context:
emit_legacy_scripts: true
name: path-child-project
class_name: Folder
properties: {}
@ -25,7 +26,8 @@ children:
- $className: Model
- Folder
relevant_paths: []
context: {}
context:
emit_legacy_scripts: true
name: SomeChild
class_name: Model
properties: {}

View file

@ -11,7 +11,8 @@ metadata:
- /foo/other.txt
- /foo/other.meta.json
- /foo/default.project.json
context: {}
context:
emit_legacy_scripts: true
name: path-project
class_name: StringValue
properties:

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo.project.json
relevant_paths:
- /foo.project.json
context: {}
context:
emit_legacy_scripts: true
name: resolved-properties
class_name: StringValue
properties:

View file

@ -9,7 +9,8 @@ metadata:
Path: /foo.project.json
relevant_paths:
- /foo.project.json
context: {}
context:
emit_legacy_scripts: true
name: unresolved-properties
class_name: StringValue
properties:

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.toml
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: ModuleScript
properties:

View file

@ -10,7 +10,8 @@ metadata:
relevant_paths:
- /foo.txt
- /foo.meta.json
context: {}
context:
emit_legacy_scripts: true
name: foo
class_name: StringValue
properties:

View file

@ -41,3 +41,8 @@ where
.with_context(|| format!("Path did not end in {}: {}", suffix, path.display()))
}
}
// TEMP function until rojo 8.0, when it can be replaced with bool::default (aka false)
pub fn emit_legacy_scripts_default() -> Option<bool> {
Some(true)
}

View file

@ -159,7 +159,7 @@ impl ApiService {
})
.unwrap();
json_ok(&WriteResponse { session_id })
json_ok(WriteResponse { session_id })
}
async fn handle_api_read(&self, request: Request<Body>) -> Response<Body> {
@ -271,7 +271,7 @@ impl ApiService {
},
};
json_ok(&OpenResponse {
json_ok(OpenResponse {
session_id: self.serve_session.session_id(),
})
}

View file

@ -44,6 +44,7 @@ gen_build_tests! {
json_model_legacy_name,
module_in_folder,
module_init,
nested_runcontext,
optional,
project_composed_default,
project_composed_file,

View file

@ -1,6 +1,6 @@
use std::fs;
use insta::assert_yaml_snapshot;
use insta::{assert_yaml_snapshot, with_settings};
use tempfile::tempdir;
use crate::rojo_test::{internable::InternAndRedact, serve_util::run_serve_test};
@ -30,10 +30,12 @@ fn scripts() {
assert_yaml_snapshot!("scripts_info", redactions.redacted_yaml(info));
let read_response = session.get_api_read(root_id).unwrap();
assert_yaml_snapshot!(
"scripts_all",
read_response.intern_and_redact(&mut redactions, root_id)
);
with_settings!({ sort_maps => true }, {
assert_yaml_snapshot!(
"scripts_all",
read_response.intern_and_redact(&mut redactions, root_id)
);
});
fs::write(session.path().join("src/foo.lua"), "Updated foo!").unwrap();
@ -44,10 +46,12 @@ fn scripts() {
);
let read_response = session.get_api_read(root_id).unwrap();
assert_yaml_snapshot!(
"scripts_all-2",
read_response.intern_and_redact(&mut redactions, root_id)
);
with_settings!({ sort_maps => true }, {
assert_yaml_snapshot!(
"scripts_all-2",
read_response.intern_and_redact(&mut redactions, root_id)
);
});
});
}