Compare commits

...

22 Commits

Author SHA1 Message Date
Lucien Greathouse
eddc469f95 Release 6.2.0 2021-06-10 00:45:10 -04:00
Lucien Greathouse
21a4667fe4 Fix failing snapshot 2021-06-10 00:44:53 -04:00
Lucien Greathouse
b25f2fcd5d Update dependencies 2021-06-10 00:41:35 -04:00
Lucien Greathouse
0f7c9493d2 Fix 'Open Scripts Externally' crashing studio.
Closes #369.
2021-04-23 17:08:11 -04:00
Lucien Greathouse
f1c4102d7f Update changelog 2021-04-23 16:00:45 -04:00
Lucien Greathouse
8b5bfd5f44 Mark two-way sync as experimental in UI 2021-04-23 15:59:31 -04:00
Lucien Greathouse
0599b50235 Release 6.1.0 2021-04-12 17:19:35 -04:00
Lucien Greathouse
21f7ef6186 Update dependencies 2021-04-09 18:44:03 -04:00
MSAA
de6470bb45 change server bind address (#403)
* web/mod.rs - change server bind address

127.0.0.1 is a loopback interface, and only works on the same host
0.0.0.0 will allow connections from other hosts

ideally, this should be a console arg - but it's a quick fix

* implement --address option, revert default bind address to 127.0.0.1

* revert silly autoformatting

* ok, actually using rustfmt now

* More precise --address flag description

* Use SocketAddr where available, take advantage of const-ness

* Display 'localhost' if address is loopback

* Update Changelog

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-03-31 16:44:10 -04:00
Lucien Greathouse
b84aab0960 Release v6.0.2 2021-02-09 11:45:58 -05:00
Lucien Greathouse
0e89b91c38 Implement CSRF challenge support in upload 2021-02-09 11:36:59 -05:00
Lucien Greathouse
7888a704e1 Release 6.0.1 2021-01-22 13:47:39 -07:00
Lucien Greathouse
804fd3de8e Remove Requester header to handle API change 2021-01-22 13:41:55 -07:00
Lucien Greathouse
4992c36f08 Delete ErrorDisplay, use anyhow instead! 2021-01-18 14:54:12 -07:00
Lucien Greathouse
27af0c841b Release 6.0.0 2021-01-16 18:35:27 -07:00
Lucien Greathouse
cc4f4df4f9 Support changing ClassName in Reconciler.diff 2021-01-16 18:26:30 -07:00
Lucien Greathouse
5989ab3b85 Add project to test that plugin skips unwritable properties 2021-01-16 15:34:59 -07:00
Lucien Greathouse
040b9c1452 Delete accidentally committed test bash script 2021-01-14 11:55:24 -07:00
Lucien Greathouse
bb7bd2e27e plugin: Update reflection database 2021-01-13 23:31:57 -07:00
tacheometry
02dbd4ba75 fix hyphen (#378)
This uses an em dash instead of double lines (--)
2021-01-06 13:49:05 -07:00
Lucien Greathouse
3b149cc875 Drop SnapshotError in favor of anyhow::Error 2020-12-18 12:16:05 -08:00
Lucien Greathouse
f3745c68d2 Turn rbxm/rbxmx errors into error variants 2020-12-18 11:39:41 -08:00
35 changed files with 1183 additions and 695 deletions

View File

@@ -2,6 +2,33 @@
## Unreleased Changes
## [6.2.0][6.2.0] (June 10, 2021)
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
* Fixed "Open Scripts Externally" feature crashing Studio ([#369][issue-369])
* Updated dependencies, fixing `HumanoidDescription` ID issues.
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
[6.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.2.0
## [6.1.0][6.1.0] (April 12, 2021)
* Updated dependencies, fixing OptionalCoordinateFrame-related issues.
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
[pr-403]: https://github.com/rojo-rbx/rojo/pull/403
[6.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.1.0
## [6.0.2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.2) (February 9, 2021)
* Fixed `rojo upload` to handle CSRF challenges.
## [6.0.1](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.1) (January 22, 2021)
* Fixed `rojo upload` requests being rejected by Roblox
## [6.0.0](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0) (January 16, 2021)
* Improved server error messages
* The server will now keep running in more error cases
* Fixed Rojo being unable to diff ClassName changes
## [6.0.0 Release Candidate 4](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0-rc.4) (December 14, 2020)
* Added brand new Rojo UI ([#367](https://github.com/rojo-rbx/rojo/pull/367))
* Added `projectName` to `/api/rojo` output.

744
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "rojo"
version = "6.0.0-rc.4"
version = "6.2.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0"

View File

@@ -1,10 +0,0 @@
#!/bin/sh
set -e
cargo build --release
echo "Known good:"
time rojo build ../uiblox/test-place.project.json -o UIBlox.rbxlx
echo "Current:"
time ./target/release/rojo build ../uiblox/test-place.project.json -o UIBlox.rbxlx

View File

@@ -8,7 +8,7 @@ Error.Kind = {
},
ConnectFailed = {
message = "Couldn't connect to the Rojo server.\n" ..
"Make sure the server is running -- use 'rojo serve' to run it!",
"Make sure the server is running use 'rojo serve' to run it!",
},
Timeout = {
message = "HTTP request timed out.",
@@ -63,4 +63,4 @@ function Error.fromRobloxErrorString(message)
return Error.new(Error.Kind.Unknown, message)
end
return Error
return Error

File diff suppressed because it is too large Load Diff

View File

@@ -205,7 +205,7 @@ function SettingsPage:render()
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "Editing files in Studio will sync them into the filesystem",
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
transparency = self.props.transparency,
layoutOrder = 2,
}),

View File

@@ -5,7 +5,7 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
return strict("Config", {
isDevBuild = isDevBuild,
codename = "Epiphany",
version = {6, 0, 0, "-rc.4"},
version = {6, 2, 0},
expectedServerVersionString = "6.0 or newer",
protocolVersion = 3,
defaultHost = "localhost",

View File

@@ -52,8 +52,9 @@ local function diff(instanceMap, virtualInstances, rootId)
invariant("Cannot diff an instance not present in InstanceMap\nID: {}", id)
end
local changedClassName = nil
if virtualInstance.ClassName ~= instance.ClassName then
error("unimplemented: support changing ClassName")
changedClassName = virtualInstance.ClassName
end
local changedName = nil
@@ -89,11 +90,11 @@ local function diff(instanceMap, virtualInstances, rootId)
end
end
if changedName ~= nil or not isEmpty(changedProperties) then
if changedName ~= nil or changedClassName ~= nil or not isEmpty(changedProperties) then
table.insert(patch.updated, {
id = id,
changedName = changedName,
changedClassName = nil,
changedClassName = changedClassName,
changedProperties = changedProperties,
changedMetadata = nil,
})

View File

@@ -1,4 +1,5 @@
local StudioService = game:GetService("StudioService")
local RunService = game:GetService("RunService")
local Log = require(script.Parent.Parent.Log)
local Fmt = require(script.Parent.Parent.Fmt)
@@ -150,10 +151,18 @@ function ServeSession:__onActiveScriptChanged(activeScript)
Log.debug("Trying to open script {} externally...", activeScript)
-- Force-close the script inside Studio
local existingParent = activeScript.Parent
activeScript.Parent = nil
activeScript.Parent = existingParent
-- Force-close the script inside Studio... with a small delay in the middle
-- to prevent Studio from crashing.
spawn(function()
local existingParent = activeScript.Parent
activeScript.Parent = nil
for i = 1, 3 do
RunService.Heartbeat:Wait()
end
activeScript.Parent = existingParent
end)
-- Notify the Rojo server to open this script
self.__apiContext:open(scriptId)

View File

@@ -1,6 +1,7 @@
---
source: tests/tests/build.rs
expression: contents
---
<roblox version="4">
<Item class="Folder" referent="0">
@@ -10,7 +11,7 @@ expression: contents
<Item class="IntValue" referent="1">
<Properties>
<string name="Name">simple-model</string>
<int name="Value">5</int>
<int64 name="Value">5</int64>
</Properties>
<Item class="Folder" referent="2">
<Properties>

View File

@@ -9,7 +9,6 @@ use memofs::{IoResultExt, Vfs, VfsEvent};
use rbx_dom_weak::{RbxId, RbxValue};
use crate::{
error::ErrorDisplay,
message_queue::MessageQueue,
snapshot::{
apply_patch_set, compute_patch_set, AppliedPatchSet, InstigatingSource, PatchSet, RojoTree,
@@ -292,7 +291,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Optio
return None;
}
Err(err) => {
log::error!("Snapshot error: {}", ErrorDisplay(err));
log::error!("Snapshot error: {:?}", err);
return None;
}
};
@@ -313,7 +312,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Optio
apply_patch_set(tree, patch_set)
}
Err(err) => {
log::error!("Error processing filesystem change: {}", ErrorDisplay(err));
log::error!("Error processing filesystem change: {:?}", err);
return None;
}
},
@@ -340,7 +339,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Optio
return None;
}
Err(err) => {
log::error!("{}", ErrorDisplay(err));
log::error!("{:?}", err);
return None;
}
};

View File

@@ -12,6 +12,7 @@ use std::{
env,
error::Error,
fmt,
net::IpAddr,
path::{Path, PathBuf},
str::FromStr,
};
@@ -186,7 +187,11 @@ pub struct ServeCommand {
#[structopt(default_value = "")]
pub project: PathBuf,
/// The port to listen on. Defaults to the project's preference, or 34872 if
/// The IP address to listen on. Defaults to `127.0.0.1`.
#[structopt(long)]
pub address: Option<IpAddr>,
/// The port to listen on. Defaults to the project's preference, or `34872` if
/// it has none.
#[structopt(long)]
pub port: Option<u16>,

View File

@@ -1,5 +1,7 @@
use std::{
io::{self, Write},
net::IpAddr,
net::Ipv4Addr,
sync::Arc,
};
@@ -13,6 +15,7 @@ use crate::{
web::LiveServer,
};
const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const DEFAULT_PORT: u16 = 34872;
pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
@@ -20,6 +23,8 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let session = Arc::new(ServeSession::new(vfs, &options.absolute_project())?);
let ip = options.address.unwrap_or(DEFAULT_BIND_ADDRESS.into());
let port = options
.port
.or_else(|| session.project_port())
@@ -27,13 +32,13 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let server = LiveServer::new(session);
let _ = show_start_message(port, global.color.into());
server.start(port);
let _ = show_start_message(ip, port, global.color.into());
server.start((ip, port).into());
Ok(())
}
fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> {
fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io::Result<()> {
let writer = BufferWriter::stdout(color);
let mut buffer = writer.buffer();
@@ -41,7 +46,12 @@ fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> {
write!(&mut buffer, " Address: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "localhost")?;
if bind_address.is_loopback() {
writeln!(&mut buffer, "localhost")?;
} else {
writeln!(&mut buffer, "{}", bind_address)?;
}
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, " Port: ")?;

View File

@@ -1,5 +1,8 @@
use memofs::Vfs;
use reqwest::header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT};
use reqwest::{
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
StatusCode,
};
use thiserror::Error;
use crate::{auth_cookie::get_auth_cookie, cli::UploadCommand, serve_session::ServeSession};
@@ -41,25 +44,42 @@ pub fn upload(options: UploadCommand) -> Result<(), anyhow::Error> {
.property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown);
rbx_xml::to_writer(&mut buffer, tree.inner(), &encode_ids, config)?;
do_upload(buffer, options.asset_id, &cookie)
}
fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> {
let url = format!(
"https://data.roblox.com/Data/Upload.ashx?assetid={}",
options.asset_id
asset_id
);
log::trace!("POSTing to {}", url);
let client = reqwest::Client::new();
let mut response = client
.post(&url)
.header(COOKIE, format!(".ROBLOSECURITY={}", &cookie))
.header(USER_AGENT, "Roblox/WinInet")
.header("Requester", "Client")
.header(CONTENT_TYPE, "application/xml")
.header(ACCEPT, "application/json")
.body(buffer)
.send()?;
if !response.status().is_success() {
let build_request = move || {
client
.post(&url)
.header(COOKIE, format!(".ROBLOSECURITY={}", cookie))
.header(USER_AGENT, "Roblox/WinInet")
.header(CONTENT_TYPE, "application/xml")
.header(ACCEPT, "application/json")
.body(buffer.clone())
};
log::debug!("Uploading to Roblox...");
let mut response = build_request().send()?;
// Starting in Feburary, 2021, the upload endpoint performs CSRF challenges.
// If we receive an HTTP 403 with a X-CSRF-Token reply, we should retry the
// request, echoing the value of that header.
if response.status() == StatusCode::FORBIDDEN {
if let Some(csrf_token) = response.headers().get("X-CSRF-Token") {
log::debug!("Received CSRF challenge, retrying with token...");
response = build_request().header("X-CSRF-Token", csrf_token).send()?;
}
}
let status = response.status();
if !status.is_success() {
return Err(Error::RobloxApi {
body: response.text()?,
}

View File

@@ -1,18 +0,0 @@
use std::{error::Error, fmt};
/// Wrapper type to print errors with source-chasing.
pub struct ErrorDisplay<E>(pub E);
impl<E: Error> fmt::Display for ErrorDisplay<E> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
writeln!(formatter, "{}", self.0)?;
let mut current_err: &dyn Error = &self.0;
while let Some(source) = current_err.source() {
writeln!(formatter, " caused by {}", source)?;
current_err = &*source;
}
Ok(())
}
}

View File

@@ -9,7 +9,6 @@ mod tree_view;
mod auth_cookie;
mod change_processor;
mod error;
mod glob;
mod lua_ast;
mod message_queue;

View File

@@ -22,7 +22,7 @@ use crate::{
apply_patch_set, compute_patch_set, AppliedPatchSet, InstanceContext,
InstancePropertiesWithMeta, PatchSet, RojoTree,
},
snapshot_middleware::{snapshot_from_vfs, SnapshotError},
snapshot_middleware::snapshot_from_vfs,
};
/// Contains all of the state for a Rojo serve session.
@@ -234,8 +234,8 @@ pub enum ServeSessionError {
},
#[error(transparent)]
Snapshot {
Other {
#[from]
source: SnapshotError,
source: anyhow::Error,
},
}

View File

@@ -1,5 +1,6 @@
use std::{collections::BTreeMap, path::Path};
use anyhow::Context;
use maplit::hashmap;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::RbxValue;
@@ -7,9 +8,7 @@ use serde::Serialize;
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use super::{
error::SnapshotError, meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult,
};
use super::{meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult};
pub fn snapshot_csv(
_context: &InstanceContext,
@@ -20,8 +19,12 @@ pub fn snapshot_csv(
let meta_path = path.with_file_name(format!("{}.meta.json", instance_name));
let contents = vfs.read(path)?;
let table_contents = convert_localization_csv(&contents)
.map_err(|source| SnapshotError::malformed_l10n_csv(source, path))?;
let table_contents = convert_localization_csv(&contents).with_context(|| {
format!(
"File was not a valid LocalizationTable CSV file: {}",
path.display()
)
})?;
let mut snapshot = InstanceSnapshot::new()
.name(instance_name)

View File

@@ -4,10 +4,7 @@ use memofs::{DirEntry, IoResultExt, Vfs};
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use super::{
error::SnapshotError, meta_file::DirectoryMetadata, middleware::SnapshotInstanceResult,
snapshot_from_vfs,
};
use super::{meta_file::DirectoryMetadata, middleware::SnapshotInstanceResult, snapshot_from_vfs};
pub fn snapshot_dir(context: &InstanceContext, vfs: &Vfs, path: &Path) -> SnapshotInstanceResult {
let passes_filter_rules = |child: &DirEntry| {
@@ -35,7 +32,7 @@ pub fn snapshot_dir(context: &InstanceContext, vfs: &Vfs, path: &Path) -> Snapsh
.file_name()
.expect("Could not extract file name")
.to_str()
.ok_or_else(|| SnapshotError::file_name_bad_unicode(path))?
.ok_or_else(|| anyhow::anyhow!("File name was not valid UTF-8: {}", path.display()))?
.to_string();
let meta_path = path.join("init.meta.json");

View File

@@ -1,101 +0,0 @@
use std::{io, path::PathBuf};
use thiserror::Error;
use crate::project::ProjectError;
#[derive(Debug, Error)]
pub enum SnapshotError {
#[error("file name had malformed Unicode")]
FileNameBadUnicode { path: PathBuf },
#[error("file had malformed Unicode contents at path {}", .path.display())]
FileContentsBadUnicode {
source: std::str::Utf8Error,
path: PathBuf,
},
#[error("malformed project file at path {}", .path.display())]
MalformedProject { source: ProjectError, path: PathBuf },
#[error("malformed .model.json file at path {}", .path.display())]
MalformedModelJson {
source: serde_json::Error,
path: PathBuf,
},
#[error("malformed .meta.json file at path {}", .path.display())]
MalformedMetaJson {
source: serde_json::Error,
path: PathBuf,
},
#[error("malformed JSON at path {}", .path.display())]
MalformedJson {
source: serde_json::Error,
path: PathBuf,
},
#[error("malformed CSV localization data at path {}", .path.display())]
MalformedLocalizationCsv { source: csv::Error, path: PathBuf },
#[error(transparent)]
Io {
#[from]
source: io::Error,
},
}
impl SnapshotError {
pub(crate) fn file_name_bad_unicode(path: impl Into<PathBuf>) -> Self {
Self::FileNameBadUnicode { path: path.into() }
}
pub(crate) fn file_contents_bad_unicode(
source: std::str::Utf8Error,
path: impl Into<PathBuf>,
) -> Self {
Self::FileContentsBadUnicode {
source,
path: path.into(),
}
}
pub(crate) fn malformed_project(source: ProjectError, path: impl Into<PathBuf>) -> Self {
Self::MalformedProject {
source,
path: path.into(),
}
}
pub(crate) fn malformed_model_json(
source: serde_json::Error,
path: impl Into<PathBuf>,
) -> Self {
Self::MalformedModelJson {
source,
path: path.into(),
}
}
pub(crate) fn malformed_meta_json(source: serde_json::Error, path: impl Into<PathBuf>) -> Self {
Self::MalformedMetaJson {
source,
path: path.into(),
}
}
pub(crate) fn malformed_json(source: serde_json::Error, path: impl Into<PathBuf>) -> Self {
Self::MalformedJson {
source,
path: path.into(),
}
}
pub(crate) fn malformed_l10n_csv(source: csv::Error, path: impl Into<PathBuf>) -> Self {
Self::MalformedLocalizationCsv {
source,
path: path.into(),
}
}
}

View File

@@ -1,5 +1,6 @@
use std::path::Path;
use anyhow::Context;
use maplit::hashmap;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::RbxValue;
@@ -9,9 +10,7 @@ use crate::{
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot},
};
use super::{
error::SnapshotError, meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult,
};
use super::{meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult};
pub fn snapshot_json(
context: &InstanceContext,
@@ -22,7 +21,7 @@ pub fn snapshot_json(
let contents = vfs.read(path)?;
let value: serde_json::Value = serde_json::from_slice(&contents)
.map_err(|err| SnapshotError::malformed_json(err, path))?;
.with_context(|| format!("File contains malformed JSON: {}", path.display()))?;
let as_lua = json_to_lua(value).to_string();

View File

@@ -1,5 +1,6 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use anyhow::Context;
use memofs::Vfs;
use rbx_dom_weak::UnresolvedRbxValue;
use rbx_reflection::try_resolve_value;
@@ -7,7 +8,7 @@ use serde::Deserialize;
use crate::snapshot::{InstanceContext, InstanceSnapshot};
use super::{error::SnapshotError, middleware::SnapshotInstanceResult};
use super::middleware::SnapshotInstanceResult;
pub fn snapshot_json_model(
context: &InstanceContext,
@@ -17,7 +18,7 @@ pub fn snapshot_json_model(
) -> SnapshotInstanceResult {
let contents = vfs.read(path)?;
let instance: JsonModel = serde_json::from_slice(&contents)
.map_err(|source| SnapshotError::malformed_model_json(source, path))?;
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
let mut snapshot = instance.core.into_snapshot(instance_name.to_owned());

View File

@@ -1,5 +1,6 @@
use std::{path::Path, str};
use anyhow::Context;
use maplit::hashmap;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::RbxValue;
@@ -28,9 +29,8 @@ pub fn snapshot_lua(context: &InstanceContext, vfs: &Vfs, path: &Path) -> Snapsh
let contents = vfs.read(path)?;
let contents_str = str::from_utf8(&contents)
// TODO: Turn into error type
.expect("File content was not valid UTF-8")
.to_string();
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?
.to_owned();
let meta_path = path.with_file_name(format!("{}.meta.json", instance_name));
@@ -71,10 +71,14 @@ pub fn snapshot_lua_init(
let dir_snapshot = snapshot_dir(context, vfs, folder_path)?.unwrap();
if dir_snapshot.class_name != "Folder" {
panic!(
anyhow::bail!(
"init.lua, init.server.lua, and init.client.lua can \
only be used if the instance produced by the parent \
directory would be a Folder."
only be used if the instance produced by the containing \
directory would be a Folder.\n\n\
The directory {} turned into an instance of class {}.",
folder_path.display(),
dir_snapshot.class_name
);
}

View File

@@ -1,13 +1,12 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use anyhow::Context;
use rbx_dom_weak::UnresolvedRbxValue;
use rbx_reflection::try_resolve_value;
use serde::{Deserialize, Serialize};
use crate::snapshot::InstanceSnapshot;
use super::error::SnapshotError;
/// Represents metadata in a sibling file with the same basename.
///
/// As an example, hello.meta.json next to hello.lua would allow assigning
@@ -23,9 +22,13 @@ pub struct AdjacentMetadata {
}
impl AdjacentMetadata {
pub fn from_slice(slice: &[u8], path: &Path) -> Result<Self, SnapshotError> {
serde_json::from_slice(slice)
.map_err(|source| SnapshotError::malformed_meta_json(source, path))
pub fn from_slice(slice: &[u8], path: &Path) -> anyhow::Result<Self> {
serde_json::from_slice(slice).with_context(|| {
format!(
"File contained malformed .meta.json data: {}",
path.display()
)
})
}
pub fn apply_ignore_unknown_instances(&mut self, snapshot: &mut InstanceSnapshot) {
@@ -75,9 +78,13 @@ pub struct DirectoryMetadata {
}
impl DirectoryMetadata {
pub fn from_slice(slice: &[u8], path: &Path) -> Result<Self, SnapshotError> {
serde_json::from_slice(slice)
.map_err(|source| SnapshotError::malformed_meta_json(source, path))
pub fn from_slice(slice: &[u8], path: &Path) -> anyhow::Result<Self> {
serde_json::from_slice(slice).with_context(|| {
format!(
"File contained malformed init.meta.json data: {}",
path.display()
)
})
}
pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) {

View File

@@ -1,5 +1,3 @@
use crate::snapshot::InstanceSnapshot;
use super::error::SnapshotError;
pub type SnapshotInstanceResult = Result<Option<InstanceSnapshot>, SnapshotError>;
pub type SnapshotInstanceResult = anyhow::Result<Option<InstanceSnapshot>>;

View File

@@ -5,7 +5,6 @@
mod csv;
mod dir;
mod error;
mod json;
mod json_model;
mod lua;
@@ -37,7 +36,6 @@ use self::{
util::match_file_name,
};
pub use self::error::*;
pub use self::project::snapshot_project_node;
pub fn snapshot_from_vfs(

View File

@@ -1,5 +1,6 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use anyhow::Context;
use memofs::Vfs;
use rbx_reflection::{get_class_descriptor, try_resolve_value};
@@ -10,7 +11,7 @@ use crate::{
},
};
use super::{error::SnapshotError, middleware::SnapshotInstanceResult, snapshot_from_vfs};
use super::{middleware::SnapshotInstanceResult, snapshot_from_vfs};
pub fn snapshot_project(
context: &InstanceContext,
@@ -18,7 +19,7 @@ pub fn snapshot_project(
path: &Path,
) -> SnapshotInstanceResult {
let project = Project::load_from_slice(&vfs.read(path)?, path)
.map_err(|err| SnapshotError::malformed_project(err, path))?;
.with_context(|| format!("File was not a valid Rojo project: {}", path.display()))?;
let mut context = context.clone();

View File

@@ -1,5 +1,6 @@
use std::{collections::HashMap, path::Path};
use anyhow::Context;
use memofs::Vfs;
use rbx_dom_weak::{RbxInstanceProperties, RbxTree};
@@ -21,7 +22,7 @@ pub fn snapshot_rbxm(
let root_id = temp_tree.get_root_id();
rbx_binary::decode(&mut temp_tree, root_id, vfs.read(path)?.as_slice())
.expect("TODO: Handle rbx_binary errors");
.with_context(|| format!("Malformed rbxm file: {}", path.display()))?;
let root_instance = temp_tree.get_instance(root_id).unwrap();
let children = root_instance.get_children_ids();
@@ -38,7 +39,11 @@ pub fn snapshot_rbxm(
Ok(Some(snapshot))
} else {
panic!("Rojo doesn't have support for model files with zero or more than one top-level instances yet.");
anyhow::bail!(
"Rojo doesn't have support for model files with zero or more than one top-level instances yet.\n\n \
Check the model file at path {}",
path.display()
);
}
}

View File

@@ -1,5 +1,6 @@
use std::path::Path;
use anyhow::Context;
use memofs::Vfs;
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
@@ -16,7 +17,7 @@ pub fn snapshot_rbxmx(
.property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown);
let temp_tree = rbx_xml::from_reader(vfs.read(path)?.as_slice(), options)
.expect("TODO: Handle rbx_xml errors");
.with_context(|| format!("Malformed rbxm file: {}", path.display()))?;
let root_instance = temp_tree.get_instance(temp_tree.get_root_id()).unwrap();
let children = root_instance.get_children_ids();
@@ -33,7 +34,11 @@ pub fn snapshot_rbxmx(
Ok(Some(snapshot))
} else {
panic!("Rojo doesn't have support for model files with zero or more than one top-level instances yet.");
anyhow::bail!(
"Rojo doesn't have support for model files with zero or more than one top-level instances yet.\n\n \
Check the model file at path {}",
path.display()
);
}
}

View File

@@ -1,6 +1,7 @@
---
source: src/snapshot_middleware/json_model.rs
expression: instance_snapshot
---
snapshot_id: ~
metadata:
@@ -14,7 +15,7 @@ name: foo
class_name: IntValue
properties:
Value:
Type: Int32
Type: Int64
Value: 5
children:
- snapshot_id: ~
@@ -26,3 +27,4 @@ children:
class_name: StringValue
properties: {}
children: []

View File

@@ -1,14 +1,13 @@
use std::{path::Path, str};
use anyhow::Context;
use maplit::hashmap;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::RbxValue;
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
use super::{
error::SnapshotError, meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult,
};
use super::{meta_file::AdjacentMetadata, middleware::SnapshotInstanceResult};
pub fn snapshot_txt(
context: &InstanceContext,
@@ -18,8 +17,8 @@ pub fn snapshot_txt(
) -> SnapshotInstanceResult {
let contents = vfs.read(path)?;
let contents_str = str::from_utf8(&contents)
.map_err(|err| SnapshotError::file_contents_bad_unicode(err, path))?
.to_string();
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?
.to_owned();
let properties = hashmap! {
"Value".to_owned() => RbxValue::String {

View File

@@ -4,7 +4,7 @@ pub mod interface;
mod ui;
mod util;
use std::sync::Arc;
use std::{net::SocketAddr, sync::Arc};
use futures::{
future::{self, FutureResult},
@@ -57,9 +57,7 @@ impl LiveServer {
LiveServer { serve_session }
}
pub fn start(self, port: u16) {
let address = ([127, 0, 0, 1], port).into();
pub fn start(self, address: SocketAddr) {
let server = Server::bind(&address)
.serve(move || {
let service: FutureResult<_, hyper::Error> =

View File

@@ -0,0 +1,2 @@
# script-unwritable
This project is used to test that Rojo successfully builds places with properties that are unwritable to scripts, but are ignored when the same project is used for live sync.

View File

@@ -0,0 +1,18 @@
{
"name": "script-unwritable",
"tree": {
"$className": "DataModel",
"HttpService": {
"$properties": {
"HttpEnabled": true
}
},
"StarterPlayer": {
"$properties": {
"GameSettingsAvatar": "R15"
}
}
}
}