mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 05:06:29 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd3c74db0 | ||
|
|
f4e2f5aefc | ||
|
|
8ceb40a24e | ||
|
|
3e53d67412 | ||
|
|
844f51d916 | ||
|
|
26974ffd4c | ||
|
|
91f5b4a675 | ||
|
|
d179240139 | ||
|
|
67b6a7e198 | ||
|
|
3b721242c1 | ||
|
|
c6ceaa5c87 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
trust-check: false
|
trust-check: false
|
||||||
version: 'v0.2.6'
|
version: 'v0.3.0'
|
||||||
|
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
run: rojo build plugin --output Rojo.rbxm
|
run: rojo build plugin --output Rojo.rbxm
|
||||||
|
|||||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## [7.4.4] - August 22nd, 2024
|
||||||
|
* Fixed issue with reading attributes from `Lighting` in new place files
|
||||||
|
* `Instance.Archivable` will now default to `true` when building a project into a binary (`rbxm`/`rbxl`) file rather than `false`.
|
||||||
|
|
||||||
|
## [7.4.3] - August 6th, 2024
|
||||||
|
* Fixed issue with building binary files introduced in 7.4.2
|
||||||
|
* Fixed `value of type nil cannot be converted to number` warning spam in output. [#955]
|
||||||
|
|
||||||
|
[#955]: https://github.com/rojo-rbx/rojo/pull/893
|
||||||
|
|
||||||
|
## [7.4.2] - July 23, 2024
|
||||||
|
* Added Never option to Confirmation ([#893])
|
||||||
|
* Fixed removing trailing newlines ([#903])
|
||||||
|
* Updated the internal property database, correcting an issue with `SurfaceAppearance.Color` that was reported [here][Surface_Appearance_Color_1] and [here][Surface_Appearance_Color_2] ([#948])
|
||||||
|
|
||||||
|
[#893]: https://github.com/rojo-rbx/rojo/pull/893
|
||||||
|
[#903]: https://github.com/rojo-rbx/rojo/pull/903
|
||||||
|
[#948]: https://github.com/rojo-rbx/rojo/pull/948
|
||||||
|
[Surface_Appearance_Color_1]: https://devforum.roblox.com/t/jailbreak-custom-character-turned-shiny-black-no-texture/3075563
|
||||||
|
[Surface_Appearance_Color_2]: https://devforum.roblox.com/t/surfaceappearance-not-displaying-correctly/3075588
|
||||||
|
|
||||||
## [7.4.1] - February 20, 2024
|
## [7.4.1] - February 20, 2024
|
||||||
* Made the `name` field optional on project files ([#870])
|
* Made the `name` field optional on project files ([#870])
|
||||||
Files named `default.project.json` inherit the name of the folder they're in and all other projects
|
Files named `default.project.json` inherit the name of the folder they're in and all other projects
|
||||||
|
|||||||
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -1073,7 +1073,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
@@ -1586,9 +1586,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_binary"
|
name = "rbx_binary"
|
||||||
version = "0.7.4"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6314dd6bf5c21d0598cdb53cf5d241aa643ba41da8b8abf7402b4a35096f03f6"
|
checksum = "7b85057e8ff75a1ce99248200c4b3c7b481a3d52f921f1053ecd67921dcc7930"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"lz4",
|
"lz4",
|
||||||
@@ -1601,9 +1601,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_dom_weak"
|
name = "rbx_dom_weak"
|
||||||
version = "2.7.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b67b56bac99849c2e3c57547b036927f71c57cf7f4d900d04e3e4ee774ec316"
|
checksum = "fcd2a17d09e46af0805f8b311a926402172b97e8d9388745c9adf8f448901841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rbx_types",
|
"rbx_types",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1611,9 +1611,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_reflection"
|
name = "rbx_reflection"
|
||||||
version = "4.5.0"
|
version = "4.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d41509c991b53a7276a746a795eae2b9204f398164920f61976995b47fe1722"
|
checksum = "8118ac6021d700e8debe324af6b40ecfd2cef270a00247849dbdfeebb0802677"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rbx_types",
|
"rbx_types",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1622,9 +1622,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_reflection_database"
|
name = "rbx_reflection_database"
|
||||||
version = "0.2.10+roblox-607"
|
version = "0.2.12+roblox-638"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12e20c06fa41f7aadc79005c8354f592b2c2f4d0c61e1080ed5718dafc30aea0"
|
checksum = "0e29381d675420e841f8c02db5755cbb2545ed3e13f56c539546dc58702b512a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rbx_reflection",
|
"rbx_reflection",
|
||||||
@@ -1634,9 +1634,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_types"
|
name = "rbx_types"
|
||||||
version = "1.8.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ca23bfd469d067d81ef14f65fe09aeddc25abcf576a889d1a7664fe021cf18c"
|
checksum = "e30f49b2a3bb667e4074ba73c2dfb8ca0873f610b448ccf318a240acfdec6c73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
@@ -1649,9 +1649,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_xml"
|
name = "rbx_xml"
|
||||||
version = "0.13.3"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8c03f95500961c32340791d1fabd4587f6873bdbff077ecca6ae32db7960dea"
|
checksum = "2b14b3027bc9ccd82e2fc854c8bcd25ed58318e570c355bf2cf63df9cdbd5ba8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"log",
|
"log",
|
||||||
@@ -1831,7 +1831,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.4.1"
|
version = "7.4.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.4.1"
|
version = "7.4.4"
|
||||||
rust-version = "1.70.0"
|
rust-version = "1.70.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
@@ -40,7 +40,7 @@ name = "build"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||||
@@ -49,11 +49,11 @@ memofs = { version = "0.2.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 = "0.7.4"
|
rbx_binary = "0.7.7"
|
||||||
rbx_dom_weak = "2.7.0"
|
rbx_dom_weak = "2.9.0"
|
||||||
rbx_reflection = "4.5.0"
|
rbx_reflection = "4.7.0"
|
||||||
rbx_reflection_database = "0.2.10"
|
rbx_reflection_database = "0.2.12"
|
||||||
rbx_xml = "0.13.3"
|
rbx_xml = "0.13.5"
|
||||||
|
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0.44"
|
||||||
backtrace = "0.3.61"
|
backtrace = "0.3.61"
|
||||||
@@ -94,7 +94,7 @@ tracy-client = { version = "0.13.2", optional = true }
|
|||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||||
|
|
||||||
embed-resource = "1.6.4"
|
embed-resource = "1.6.4"
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0.44"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = "rojo-rbx/rojo@7.3.0"
|
rojo = "rojo-rbx/rojo@7.4.1"
|
||||||
selene = "Kampfkarren/selene@0.26.1"
|
selene = "Kampfkarren/selene@0.26.1"
|
||||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
stylua = "JohnnyMorganz/stylua@0.18.2"
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# memofs Changelog
|
# memofs Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## 0.3.0 (2024-03-15)
|
||||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
||||||
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
description = "Virtual filesystem with configurable backends."
|
description = "Virtual filesystem with configurable backends."
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ impl Vfs {
|
|||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
||||||
|
|
||||||
Ok(contents.lines().collect::<Vec<&str>>().join("\n").into())
|
Ok(contents.replace("\r\n", "\n").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a file to the VFS and the underlying backend.
|
/// Write a file to the VFS and the underlying backend.
|
||||||
@@ -473,3 +473,23 @@ impl VfsLock<'_> {
|
|||||||
self.inner.commit_event(event)
|
self.inner.commit_event(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{InMemoryFs, Vfs, VfsSnapshot};
|
||||||
|
|
||||||
|
/// https://github.com/rojo-rbx/rojo/issues/899
|
||||||
|
#[test]
|
||||||
|
fn read_to_string_lf_normalized_keeps_trailing_newline() {
|
||||||
|
let mut imfs = InMemoryFs::new();
|
||||||
|
imfs.load_snapshot("test", VfsSnapshot::file("bar\r\nfoo\r\n\r\n"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vfs.read_to_string_lf_normalized("test").unwrap().as_str(),
|
||||||
|
"bar\nfoo\n\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
7.4.1
|
7.4.4
|
||||||
@@ -26,6 +26,21 @@ local TERRAIN_MATERIAL_COLORS = {
|
|||||||
Enum.Material.Pavement,
|
Enum.Material.Pavement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function isAttributeNameValid(attributeName)
|
||||||
|
-- For SetAttribute to succeed, the attribute name must be less than or
|
||||||
|
-- equal to 100 characters...
|
||||||
|
return #attributeName <= 100
|
||||||
|
-- ...and must only contain alphanumeric characters, periods, hyphens,
|
||||||
|
-- underscores, or forward slashes.
|
||||||
|
and attributeName:match("[^%w%.%-_/]") == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isAttributeNameReserved(attributeName)
|
||||||
|
-- For SetAttribute to succeed, attribute names must not use the RBX
|
||||||
|
-- prefix, which is reserved by Roblox.
|
||||||
|
return attributeName:sub(1, 3) == "RBX"
|
||||||
|
end
|
||||||
|
|
||||||
-- Defines how to read and write properties that aren't directly scriptable.
|
-- Defines how to read and write properties that aren't directly scriptable.
|
||||||
--
|
--
|
||||||
-- The reflection database refers to these as having scriptability = "Custom"
|
-- The reflection database refers to these as having scriptability = "Custom"
|
||||||
@@ -40,26 +55,33 @@ return {
|
|||||||
local didAllWritesSucceed = true
|
local didAllWritesSucceed = true
|
||||||
|
|
||||||
for attributeName, attributeValue in pairs(value) do
|
for attributeName, attributeValue in pairs(value) do
|
||||||
local isNameValid =
|
if isAttributeNameReserved(attributeName) then
|
||||||
-- For our SetAttribute to succeed, the attribute name must be
|
-- If the attribute name is reserved, then we don't
|
||||||
-- less than or equal to 100 characters...
|
-- really care about reporting any failures about
|
||||||
#attributeName <= 100
|
-- it.
|
||||||
-- ...must only contain alphanumeric characters, periods, hyphens,
|
continue
|
||||||
-- 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
|
||||||
|
|
||||||
|
if not isAttributeNameValid(attributeName) then
|
||||||
|
didAllWritesSucceed = false
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
instance:SetAttribute(attributeName, attributeValue)
|
||||||
end
|
end
|
||||||
|
|
||||||
for key in pairs(existing) do
|
for existingAttributeName in pairs(existing) do
|
||||||
if value[key] == nil then
|
if isAttributeNameReserved(existingAttributeName) then
|
||||||
instance:SetAttribute(key, nil)
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isAttributeNameValid(existingAttributeName) then
|
||||||
|
didAllWritesSucceed = false
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if value[existingAttributeName] == nil then
|
||||||
|
instance:SetAttribute(existingAttributeName, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -113,13 +135,14 @@ return {
|
|||||||
},
|
},
|
||||||
WorldPivotData = {
|
WorldPivotData = {
|
||||||
read = function(instance)
|
read = function(instance)
|
||||||
return true, instance:GetPivot()
|
return true, instance.WorldPivot
|
||||||
end,
|
end,
|
||||||
write = function(instance, _, value)
|
write = function(instance, _, value)
|
||||||
if value == nil then
|
if value == nil then
|
||||||
return true, nil
|
return true, nil
|
||||||
else
|
else
|
||||||
return true, instance:PivotTo(value)
|
instance.WorldPivot = value
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ local function invertTbl(tbl)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local invertedLevels = invertTbl(Log.Level)
|
local invertedLevels = invertTbl(Log.Level)
|
||||||
local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted PlaceId" }
|
local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted PlaceId", "Never" }
|
||||||
|
|
||||||
local function Navbar(props)
|
local function Navbar(props)
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
|
|||||||
@@ -516,6 +516,9 @@ function App:startSession()
|
|||||||
return "Accept"
|
return "Accept"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif confirmationBehavior == "Never" then
|
||||||
|
Log.trace("Accepting patch without confirmation because behavior is set to Never")
|
||||||
|
return "Accept"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The datamodel name gets overwritten by Studio, making confirmation of it intrusive
|
-- The datamodel name gets overwritten by Studio, making confirmation of it intrusive
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ local strict = require(script.Parent.strict)
|
|||||||
local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
||||||
|
|
||||||
local Version = script.Parent.Parent.Version
|
local Version = script.Parent.Parent.Version
|
||||||
local major, minor, patch, metadata = Version.Value:match("^(%d+)%.(%d+)%.(%d+)(.*)$")
|
local trimmedVersionValue = Version.Value:gsub("^%s+", ""):gsub("%s+$", "")
|
||||||
|
local major, minor, patch, metadata = trimmedVersionValue:match("^(%d+)%.(%d+)%.(%d+)(.*)$")
|
||||||
|
|
||||||
local realVersion = { major, minor, patch, metadata }
|
local realVersion = { major, minor, patch, metadata }
|
||||||
for i = 1, 3 do
|
for i = 1, 3 do
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local Log = require(Packages.Log)
|
|||||||
local RbxDom = require(Packages.RbxDom)
|
local RbxDom = require(Packages.RbxDom)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
local function setProperty(instance, propertyName, value)
|
local function setProperty(instance: Instance, propertyName: string, value: unknown): boolean
|
||||||
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
||||||
|
|
||||||
-- We can skip unknown properties; they're not likely reflected to Lua.
|
-- We can skip unknown properties; they're not likely reflected to Lua.
|
||||||
@@ -28,6 +28,13 @@ local function setProperty(instance, propertyName, value)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if value == nil then
|
||||||
|
if descriptor.dataType == "Float32" or descriptor.dataType == "Float64" then
|
||||||
|
Log.trace("Skipping nil {} property {}.{}", descriptor.dataType, instance.ClassName, propertyName)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local writeSuccess, err = descriptor:write(instance, value)
|
local writeSuccess, err = descriptor:write(instance, value)
|
||||||
|
|
||||||
if not writeSuccess then
|
if not writeSuccess then
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-2:
|
||||||
|
Children: []
|
||||||
|
ClassName: StringValue
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: no_name_top_level_project
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties:
|
||||||
|
Value:
|
||||||
|
String: "If this isn't named `no_name_top_level_project`, something went wrong!"
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use memofs::Vfs;
|
||||||
|
|
||||||
use crate::project::Project;
|
use crate::project::Project;
|
||||||
|
|
||||||
@@ -17,8 +18,11 @@ pub struct FmtProjectCommand {
|
|||||||
|
|
||||||
impl FmtProjectCommand {
|
impl FmtProjectCommand {
|
||||||
pub fn run(self) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
|
let vfs = Vfs::new_default();
|
||||||
|
vfs.set_watch_enabled(false);
|
||||||
|
|
||||||
let base_path = resolve_path(&self.project);
|
let base_path = resolve_path(&self.project);
|
||||||
let project = Project::load_fuzzy(&base_path)?
|
let project = Project::load_fuzzy(&vfs, &base_path)?
|
||||||
.context("A project file is required to run 'rojo fmt-project'")?;
|
.context("A project file is required to run 'rojo fmt-project'")?;
|
||||||
|
|
||||||
let serialized = serde_json::to_string_pretty(&project)
|
let serialized = serde_json::to_string_pretty(&project)
|
||||||
|
|||||||
104
src/project.rs
104
src/project.rs
@@ -1,10 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
|
ffi::OsStr,
|
||||||
fs, io,
|
fs, io,
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use memofs::Vfs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -19,6 +21,14 @@ pub struct ProjectError(#[from] Error);
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
enum Error {
|
enum Error {
|
||||||
|
#[error("The folder for the provided project cannot be used as a project name: {}\n\
|
||||||
|
Consider setting the `name` field on this project.", .path.display())]
|
||||||
|
FolderNameInvalid { path: PathBuf },
|
||||||
|
|
||||||
|
#[error("The file name of the provided project cannot be used as a project name: {}.\n\
|
||||||
|
Consider setting the `name` field on this project.", .path.display())]
|
||||||
|
ProjectNameInvalid { path: PathBuf },
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io {
|
Io {
|
||||||
#[from]
|
#[from]
|
||||||
@@ -129,43 +139,91 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_from_slice(
|
/// Sets the name of a project. The order it handles is as follows:
|
||||||
|
///
|
||||||
|
/// - If the project is a `default.project.json`, uses the folder's name
|
||||||
|
/// - If a fallback is specified, uses that blindly
|
||||||
|
/// - Otherwise, loops through sync rules (including the default ones!) and
|
||||||
|
/// uses the name of the first one that matches and is a project file
|
||||||
|
fn set_file_name(&mut self, fallback: Option<&str>) -> Result<(), Error> {
|
||||||
|
let file_name = self
|
||||||
|
.file_location
|
||||||
|
.file_name()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.ok_or_else(|| Error::ProjectNameInvalid {
|
||||||
|
path: self.file_location.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// If you're editing this to be generic, make sure you also alter the
|
||||||
|
// snapshot middleware to support generic init paths.
|
||||||
|
if file_name == PROJECT_FILENAME {
|
||||||
|
let folder_name = self.folder_location().file_name().and_then(OsStr::to_str);
|
||||||
|
if let Some(folder_name) = folder_name {
|
||||||
|
self.name = Some(folder_name.to_string());
|
||||||
|
} else {
|
||||||
|
return Err(Error::FolderNameInvalid {
|
||||||
|
path: self.file_location.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if let Some(fallback) = fallback {
|
||||||
|
self.name = Some(fallback.to_string());
|
||||||
|
} else {
|
||||||
|
unimplemented!(
|
||||||
|
"7.4.X branch will hopefully never have a case where fallback isn't provided to set_file_name"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a Project file from the provided contents with its source set as
|
||||||
|
/// the provided location.
|
||||||
|
fn load_from_slice(
|
||||||
contents: &[u8],
|
contents: &[u8],
|
||||||
project_file_location: &Path,
|
project_file_location: PathBuf,
|
||||||
) -> Result<Self, ProjectError> {
|
fallback_name: Option<&str>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
let mut project: Self = serde_json::from_slice(contents).map_err(|source| Error::Json {
|
let mut project: Self = serde_json::from_slice(contents).map_err(|source| Error::Json {
|
||||||
source,
|
source,
|
||||||
path: project_file_location.to_owned(),
|
path: project_file_location.clone(),
|
||||||
})?;
|
})?;
|
||||||
|
project.file_location = project_file_location;
|
||||||
project.file_location = project_file_location.to_path_buf();
|
|
||||||
project.check_compatibility();
|
project.check_compatibility();
|
||||||
|
if project.name.is_none() {
|
||||||
|
project.set_file_name(fallback_name)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(project)
|
Ok(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_fuzzy(fuzzy_project_location: &Path) -> Result<Option<Self>, ProjectError> {
|
/// Loads a Project from a path. This will find the project if it refers to
|
||||||
|
/// a `.project.json` file or if it refers to a directory that contains a
|
||||||
|
/// file named `default.project.json`.
|
||||||
|
pub fn load_fuzzy(
|
||||||
|
vfs: &Vfs,
|
||||||
|
fuzzy_project_location: &Path,
|
||||||
|
) -> Result<Option<Self>, ProjectError> {
|
||||||
if let Some(project_path) = Self::locate(fuzzy_project_location) {
|
if let Some(project_path) = Self::locate(fuzzy_project_location) {
|
||||||
let project = Self::load_exact(&project_path)?;
|
let contents = vfs.read(&project_path).map_err(Error::from)?;
|
||||||
|
Ok(Some(Self::load_from_slice(&contents, project_path, None)?))
|
||||||
Ok(Some(project))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_exact(project_file_location: &Path) -> Result<Self, Error> {
|
/// Loads a Project from a path.
|
||||||
let contents = fs::read_to_string(project_file_location)?;
|
pub fn load_exact(
|
||||||
|
vfs: &Vfs,
|
||||||
let mut project: Project =
|
project_file_location: &Path,
|
||||||
serde_json::from_str(&contents).map_err(|source| Error::Json {
|
fallback_name: Option<&str>,
|
||||||
source,
|
) -> Result<Self, ProjectError> {
|
||||||
path: project_file_location.to_owned(),
|
let project_path = project_file_location.to_path_buf();
|
||||||
})?;
|
let contents = vfs.read(&project_path).map_err(Error::from)?;
|
||||||
|
Ok(Self::load_from_slice(
|
||||||
project.file_location = project_file_location.to_path_buf();
|
&contents,
|
||||||
project.check_compatibility();
|
project_path,
|
||||||
|
fallback_name,
|
||||||
Ok(project)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if there are any compatibility issues with this project file and
|
/// Checks if there are any compatibility issues with this project file and
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use memofs::IoResultExt;
|
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -110,43 +109,14 @@ impl ServeSession {
|
|||||||
|
|
||||||
log::debug!("Loading project file from {}", project_path.display());
|
log::debug!("Loading project file from {}", project_path.display());
|
||||||
|
|
||||||
let mut root_project = match vfs.read(&project_path).with_not_found()? {
|
let root_project = match Project::load_exact(&vfs, &project_path, None) {
|
||||||
Some(contents) => Project::load_from_slice(&contents, &project_path)?,
|
Ok(project) => project,
|
||||||
None => {
|
Err(_) => {
|
||||||
return Err(ServeSessionError::NoProjectFound {
|
return Err(ServeSessionError::NoProjectFound {
|
||||||
path: project_path.to_path_buf(),
|
path: project_path.to_path_buf(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if root_project.name.is_none() {
|
|
||||||
if let Some(file_name) = project_path.file_name().and_then(|s| s.to_str()) {
|
|
||||||
if file_name == "default.project.json" {
|
|
||||||
let folder_name = project_path
|
|
||||||
.parent()
|
|
||||||
.and_then(Path::file_name)
|
|
||||||
.and_then(|s| s.to_str());
|
|
||||||
if let Some(folder_name) = folder_name {
|
|
||||||
root_project.name = Some(folder_name.to_string());
|
|
||||||
} else {
|
|
||||||
return Err(ServeSessionError::FolderNameInvalid {
|
|
||||||
path: project_path.to_path_buf(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if let Some(trimmed) = file_name.strip_suffix(".project.json") {
|
|
||||||
root_project.name = Some(trimmed.to_string());
|
|
||||||
} else {
|
|
||||||
return Err(ServeSessionError::ProjectNameInvalid {
|
|
||||||
path: project_path.to_path_buf(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ServeSessionError::ProjectNameInvalid {
|
|
||||||
path: project_path.to_path_buf(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rebind it to make it no longer mutable
|
|
||||||
let root_project = root_project;
|
|
||||||
|
|
||||||
let mut tree = RojoTree::new(InstanceSnapshot::new());
|
let mut tree = RojoTree::new(InstanceSnapshot::new());
|
||||||
|
|
||||||
@@ -263,14 +233,6 @@ pub enum ServeSessionError {
|
|||||||
)]
|
)]
|
||||||
NoProjectFound { path: PathBuf },
|
NoProjectFound { path: PathBuf },
|
||||||
|
|
||||||
#[error("The folder for the provided project cannot be used as a project name: {}\n\
|
|
||||||
Consider setting the `name` field on this project.", .path.display())]
|
|
||||||
FolderNameInvalid { path: PathBuf },
|
|
||||||
|
|
||||||
#[error("The file name of the provided project cannot be used as a project name: {}.\n\
|
|
||||||
Consider setting the `name` field on this project.", .path.display())]
|
|
||||||
ProjectNameInvalid { path: PathBuf },
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io {
|
Io {
|
||||||
#[from]
|
#[from]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{borrow::Cow, collections::HashMap, path::Path};
|
use std::{borrow::Cow, collections::HashMap, ffi::OsStr, path::Path};
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
@@ -19,7 +19,18 @@ pub fn snapshot_project(
|
|||||||
vfs: &Vfs,
|
vfs: &Vfs,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||||
let project = Project::load_from_slice(&vfs.read(path)?, path)
|
let fallback_name = match path.file_name().and_then(OsStr::to_str) {
|
||||||
|
Some("default.project.json") => path
|
||||||
|
.parent()
|
||||||
|
.and_then(Path::file_name)
|
||||||
|
.and_then(OsStr::to_str),
|
||||||
|
Some(name) => name.strip_suffix(".project.json"),
|
||||||
|
None => anyhow::bail!(
|
||||||
|
"project file does not have valid utf-8 name: {}",
|
||||||
|
path.display()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let project = Project::load_exact(vfs, path, fallback_name)
|
||||||
.with_context(|| format!("File was not a valid Rojo project: {}", path.display()))?;
|
.with_context(|| format!("File was not a valid Rojo project: {}", path.display()))?;
|
||||||
|
|
||||||
// This is not how I would normally do this, but this is a temporary
|
// This is not how I would normally do this, but this is a temporary
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ impl TestServeSession {
|
|||||||
let port_string = port.to_string();
|
let port_string = port.to_string();
|
||||||
|
|
||||||
let rojo_process = Command::new(ROJO_PATH)
|
let rojo_process = Command::new(ROJO_PATH)
|
||||||
.args(&[
|
.args([
|
||||||
"serve",
|
"serve",
|
||||||
project_path.to_str().unwrap(),
|
project_path.to_str().unwrap(),
|
||||||
"--port",
|
"--port",
|
||||||
@@ -145,14 +145,14 @@ impl TestServeSession {
|
|||||||
|
|
||||||
pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> {
|
pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> {
|
||||||
let url = format!("http://localhost:{}/api/rojo", self.port);
|
let url = format!("http://localhost:{}/api/rojo", self.port);
|
||||||
let body = reqwest::blocking::get(&url)?.text()?;
|
let body = reqwest::blocking::get(url)?.text()?;
|
||||||
|
|
||||||
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()?;
|
||||||
|
|
||||||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ impl TestServeSession {
|
|||||||
) -> Result<SubscribeResponse<'static>, reqwest::Error> {
|
) -> Result<SubscribeResponse<'static>, reqwest::Error> {
|
||||||
let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor);
|
let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor);
|
||||||
|
|
||||||
reqwest::blocking::get(&url)?.json()
|
reqwest::blocking::get(url)?.json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -296,16 +296,27 @@ fn no_name_top_level_project() {
|
|||||||
run_serve_test("no_name_top_level_project", |session, mut redactions| {
|
run_serve_test("no_name_top_level_project", |session, mut redactions| {
|
||||||
let info = session.get_api_rojo().unwrap();
|
let info = session.get_api_rojo().unwrap();
|
||||||
let root_id = info.root_instance_id;
|
let root_id = info.root_instance_id;
|
||||||
|
|
||||||
assert_yaml_snapshot!(
|
assert_yaml_snapshot!(
|
||||||
"no_name_top_level_project_info",
|
"no_name_top_level_project_info",
|
||||||
redactions.redacted_yaml(info)
|
redactions.redacted_yaml(info)
|
||||||
);
|
);
|
||||||
|
|
||||||
let read_response = session.get_api_read(root_id).unwrap();
|
let read_response = session.get_api_read(root_id).unwrap();
|
||||||
assert_yaml_snapshot!(
|
assert_yaml_snapshot!(
|
||||||
"no_name_top_level_project_all",
|
"no_name_top_level_project_all",
|
||||||
read_response.intern_and_redact(&mut redactions, root_id)
|
read_response.intern_and_redact(&mut redactions, root_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let project_path = session.path().join("default.project.json");
|
||||||
|
let mut project_contents = fs::read_to_string(&project_path).unwrap();
|
||||||
|
project_contents.push('\n');
|
||||||
|
fs::write(&project_path, project_contents).unwrap();
|
||||||
|
|
||||||
|
// The cursor shouldn't be changing so this snapshot is fine for testing
|
||||||
|
// the response.
|
||||||
|
let read_response = session.get_api_read(root_id).unwrap();
|
||||||
|
assert_yaml_snapshot!(
|
||||||
|
"no_name_top_level_project_all-2",
|
||||||
|
read_response.intern_and_redact(&mut redactions, root_id)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user