Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0599b50235 | ||
|
|
21f7ef6186 | ||
|
|
de6470bb45 |
@@ -1,2 +0,0 @@
|
|||||||
((nil . ((eglot-luau-rojo-project-path . "plugin.project.json")
|
|
||||||
(eglot-luau-rojo-sourcemap-enabled . 't))))
|
|
||||||
@@ -23,7 +23,4 @@ insert_final_newline = true
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.lua]
|
[*.lua]
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
[*.luau]
|
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# stylua formatting
|
|
||||||
0f8e1625d572a5fe0f7b5c08653ff92cc837d346
|
|
||||||
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
*.lua linguist-language=Luau
|
|
||||||
23
.github/workflows/changelog.yml
vendored
@@ -1,23 +0,0 @@
|
|||||||
name: Changelog Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [assigned, opened, synchronize, reopened, labeled, unlabeled]
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Check Actions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Changelog check
|
|
||||||
uses: Zomzog/changelog-checker@v1.3.0
|
|
||||||
with:
|
|
||||||
fileName: CHANGELOG.md
|
|
||||||
noChangelogLabel: skip changelog
|
|
||||||
checkNotification: Simple
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
118
.github/workflows/ci.yml
vendored
@@ -11,123 +11,29 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and Test
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-22.04, windows-latest, macos-latest, windows-11-arm, ubuntu-22.04-arm]
|
rust_version: [stable, "1.43.1"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
run: rustup default ${{ matrix.rust_version }}
|
||||||
|
|
||||||
- name: Restore Rust Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
- name: Test
|
- name: Run tests
|
||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
- name: Save Rust Cache
|
- name: Rustfmt and Clippy
|
||||||
uses: actions/cache/save@v4
|
run: |
|
||||||
with:
|
cargo fmt -- --check
|
||||||
path: |
|
cargo clippy
|
||||||
~/.cargo/registry
|
if: matrix.rust_version == 'stable'
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
msrv:
|
|
||||||
name: Check MSRV
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: dtolnay/rust-toolchain@1.88.0
|
|
||||||
|
|
||||||
- name: Restore Rust Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --locked --verbose
|
|
||||||
|
|
||||||
- name: Save Rust Cache
|
|
||||||
uses: actions/cache/save@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Rustfmt, Clippy, Stylua, & Selene
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
- name: Restore Rust Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Setup Rokit
|
|
||||||
uses: CompeyDev/setup-rokit@v0.1.2
|
|
||||||
with:
|
|
||||||
version: 'v1.1.0'
|
|
||||||
|
|
||||||
- name: Stylua
|
|
||||||
run: stylua --check plugin/src
|
|
||||||
|
|
||||||
- name: Selene
|
|
||||||
run: selene plugin/src
|
|
||||||
|
|
||||||
- name: Rustfmt
|
|
||||||
run: cargo fmt -- --check
|
|
||||||
|
|
||||||
- name: Clippy
|
|
||||||
run: cargo clippy
|
|
||||||
|
|
||||||
- name: Save Rust Cache
|
|
||||||
uses: actions/cache/save@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
203
.github/workflows/release.yml
vendored
@@ -2,156 +2,65 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*"]
|
tags: ["*"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
windows:
|
||||||
name: Create Release
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
run: cargo build --verbose --locked --release
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: rojo-win64
|
||||||
|
path: target/release/rojo.exe
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
run: |
|
||||||
|
source $HOME/.cargo/env
|
||||||
|
cargo build --verbose --locked --release
|
||||||
|
env:
|
||||||
|
OPENSSL_STATIC: 1
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: rojo-macos
|
||||||
|
path: target/release/rojo
|
||||||
|
|
||||||
|
linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- name: Create Release
|
with:
|
||||||
env:
|
submodules: true
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
gh release create ${{ github.ref_name }} --draft --verify-tag --title ${{ github.ref_name }}
|
|
||||||
|
|
||||||
build-plugin:
|
- name: Build
|
||||||
needs: ["create-release"]
|
run: cargo build --locked --verbose --release
|
||||||
name: Build Roblox Studio Plugin
|
env:
|
||||||
runs-on: ubuntu-latest
|
OPENSSL_STATIC: 1
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup Rokit
|
- name: Upload artifacts
|
||||||
uses: CompeyDev/setup-rokit@v0.1.2
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
version: 'v1.1.0'
|
name: rojo-linux
|
||||||
|
path: target/release/rojo
|
||||||
- name: Build Plugin
|
|
||||||
run: rojo build plugin.project.json --output Rojo.rbxm
|
|
||||||
|
|
||||||
- name: Upload Plugin to Release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
gh release upload ${{ github.ref_name }} Rojo.rbxm
|
|
||||||
|
|
||||||
- name: Upload Plugin to Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Rojo.rbxm
|
|
||||||
path: Rojo.rbxm
|
|
||||||
|
|
||||||
- name: Upload Plugin to Roblox
|
|
||||||
env:
|
|
||||||
RBX_API_KEY: ${{ secrets.PLUGIN_UPLOAD_TOKEN }}
|
|
||||||
RBX_UNIVERSE_ID: ${{ vars.PLUGIN_CI_PLACE_ID }}
|
|
||||||
RBX_PLACE_ID: ${{ vars.PLUGIN_CI_UNIVERSE_ID }}
|
|
||||||
run: lune run upload-plugin Rojo.rbxm
|
|
||||||
|
|
||||||
build:
|
|
||||||
needs: ["create-release"]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# https://doc.rust-lang.org/rustc/platform-support.html
|
|
||||||
include:
|
|
||||||
- host: linux
|
|
||||||
os: ubuntu-22.04
|
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
label: linux-x86_64
|
|
||||||
|
|
||||||
- host: linux
|
|
||||||
os: ubuntu-22.04-arm
|
|
||||||
target: aarch64-unknown-linux-gnu
|
|
||||||
label: linux-aarch64
|
|
||||||
|
|
||||||
- host: windows
|
|
||||||
os: windows-latest
|
|
||||||
target: x86_64-pc-windows-msvc
|
|
||||||
label: windows-x86_64
|
|
||||||
|
|
||||||
- host: windows
|
|
||||||
os: windows-11-arm
|
|
||||||
target: aarch64-pc-windows-msvc
|
|
||||||
label: windows-aarch64
|
|
||||||
|
|
||||||
- host: macos
|
|
||||||
os: macos-latest
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
label: macos-x86_64
|
|
||||||
|
|
||||||
- host: macos
|
|
||||||
os: macos-latest
|
|
||||||
target: aarch64-apple-darwin
|
|
||||||
label: macos-aarch64
|
|
||||||
|
|
||||||
name: Build (${{ matrix.target }})
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
env:
|
|
||||||
BIN: rojo
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
targets: ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Restore Rust Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Build Release
|
|
||||||
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Save Rust Cache
|
|
||||||
uses: actions/cache/save@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Generate Artifact Name
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
TAG_NAME: ${{ github.ref_name }}
|
|
||||||
run: |
|
|
||||||
echo "ARTIFACT_NAME=$BIN-${TAG_NAME#v}-${{ matrix.label }}.zip" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Create Archive and Upload to Release
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
mkdir staging
|
|
||||||
|
|
||||||
if [ "${{ matrix.host }}" = "windows" ]; then
|
|
||||||
cp "target/${{ matrix.target }}/release/$BIN.exe" staging/
|
|
||||||
cd staging
|
|
||||||
7z a ../$ARTIFACT_NAME *
|
|
||||||
else
|
|
||||||
cp "target/${{ matrix.target }}/release/$BIN" staging/
|
|
||||||
cd staging
|
|
||||||
zip ../$ARTIFACT_NAME *
|
|
||||||
fi
|
|
||||||
|
|
||||||
gh release upload ${{ github.ref_name }} ../$ARTIFACT_NAME
|
|
||||||
|
|
||||||
- name: Upload Archive to Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ${{ env.ARTIFACT_NAME }}
|
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
|
||||||
12
.gitignore
vendored
@@ -10,8 +10,8 @@
|
|||||||
/*.rbxl
|
/*.rbxl
|
||||||
/*.rbxlx
|
/*.rbxlx
|
||||||
|
|
||||||
# Sourcemap for the Rojo plugin (for better intellisense)
|
# Test places for the Roblox Studio Plugin
|
||||||
/sourcemap.json
|
/plugin/*.rbxlx
|
||||||
|
|
||||||
# Roblox Studio holds 'lock' files on places
|
# Roblox Studio holds 'lock' files on places
|
||||||
*.rbxl.lock
|
*.rbxl.lock
|
||||||
@@ -20,9 +20,5 @@
|
|||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
# Macos file system junk
|
# Selene generates a roblox.toml file that should not be checked in.
|
||||||
._*
|
/roblox.toml
|
||||||
.DS_STORE
|
|
||||||
|
|
||||||
# JetBrains IDEs
|
|
||||||
/.idea/
|
|
||||||
37
.gitmodules
vendored
@@ -1,24 +1,15 @@
|
|||||||
[submodule "plugin/Packages/Roact"]
|
[submodule "plugin/modules/roact"]
|
||||||
path = plugin/Packages/Roact
|
path = plugin/modules/roact
|
||||||
url = https://github.com/roblox/roact.git
|
url = https://github.com/Roblox/roact.git
|
||||||
[submodule "plugin/Packages/Flipper"]
|
[submodule "plugin/modules/testez"]
|
||||||
path = plugin/Packages/Flipper
|
path = plugin/modules/testez
|
||||||
url = https://github.com/reselim/flipper.git
|
url = https://github.com/Roblox/testez.git
|
||||||
[submodule "plugin/Packages/Promise"]
|
[submodule "plugin/modules/promise"]
|
||||||
path = plugin/Packages/Promise
|
path = plugin/modules/promise
|
||||||
url = https://github.com/evaera/roblox-lua-promise.git
|
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
||||||
[submodule "plugin/Packages/t"]
|
[submodule "plugin/modules/t"]
|
||||||
path = plugin/Packages/t
|
path = plugin/modules/t
|
||||||
url = https://github.com/osyrisrblx/t.git
|
url = https://github.com/osyrisrblx/t.git
|
||||||
[submodule "plugin/Packages/TestEZ"]
|
[submodule "plugin/modules/flipper"]
|
||||||
path = plugin/Packages/TestEZ
|
path = plugin/modules/flipper
|
||||||
url = https://github.com/roblox/testez.git
|
url = https://github.com/Reselim/Flipper
|
||||||
[submodule "plugin/Packages/Highlighter"]
|
|
||||||
path = plugin/Packages/Highlighter
|
|
||||||
url = https://github.com/boatbomber/highlighter.git
|
|
||||||
[submodule "plugin/Packages/msgpack-luau"]
|
|
||||||
path = plugin/Packages/msgpack-luau
|
|
||||||
url = https://github.com/cipharius/msgpack-luau/
|
|
||||||
[submodule ".lune/opencloud-execute"]
|
|
||||||
path = .lune/opencloud-execute
|
|
||||||
url = https://github.com/Dekkonot/opencloud-luau-execute-lune.git
|
|
||||||
|
|||||||
58
.luacheckrc
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
stds.roblox = {
|
||||||
|
read_globals = {
|
||||||
|
game = {
|
||||||
|
other_fields = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Roblox globals
|
||||||
|
"script",
|
||||||
|
|
||||||
|
-- Extra functions
|
||||||
|
"tick", "warn", "spawn",
|
||||||
|
"wait", "settings", "typeof",
|
||||||
|
|
||||||
|
-- Types
|
||||||
|
"Vector2", "Vector3",
|
||||||
|
"Vector2int16", "Vector3int16",
|
||||||
|
"Color3",
|
||||||
|
"UDim", "UDim2",
|
||||||
|
"Rect",
|
||||||
|
"CFrame",
|
||||||
|
"Enum",
|
||||||
|
"Instance",
|
||||||
|
"DockWidgetPluginGuiInfo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stds.plugin = {
|
||||||
|
read_globals = {
|
||||||
|
"plugin",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stds.testez = {
|
||||||
|
read_globals = {
|
||||||
|
"describe",
|
||||||
|
"it", "itFOCUS", "itSKIP", "itFIXME",
|
||||||
|
"FOCUS", "SKIP", "HACK_NO_XPCALL",
|
||||||
|
"expect",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore = {
|
||||||
|
"212", -- unused arguments
|
||||||
|
"421", -- shadowing local variable
|
||||||
|
"422", -- shadowing argument
|
||||||
|
"431", -- shadowing upvalue
|
||||||
|
"432", -- shadowing upvalue argument
|
||||||
|
}
|
||||||
|
|
||||||
|
std = "lua51+roblox"
|
||||||
|
|
||||||
|
files["**/*.server.lua"] = {
|
||||||
|
std = "+plugin",
|
||||||
|
}
|
||||||
|
|
||||||
|
files["**/*.spec.lua"] = {
|
||||||
|
std = "+testez",
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
return {
|
|
||||||
luau = {
|
|
||||||
languagemode = "strict",
|
|
||||||
aliases = {
|
|
||||||
lune = "~/.lune/.typedefs/0.10.4/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
local args: any = ...
|
|
||||||
assert(args, "no arguments passed to script")
|
|
||||||
|
|
||||||
local input: buffer = args.BinaryInput
|
|
||||||
|
|
||||||
local AssetService = game:GetService("AssetService")
|
|
||||||
local SerializationService = game:GetService("SerializationService")
|
|
||||||
local EncodingService = game:GetService("EncodingService")
|
|
||||||
|
|
||||||
local input_hash: buffer = EncodingService:ComputeBufferHash(input, Enum.HashAlgorithm.Sha256)
|
|
||||||
local hex_hash: { string } = table.create(buffer.len(input_hash))
|
|
||||||
for i = 0, buffer.len(input_hash) - 1 do
|
|
||||||
table.insert(hex_hash, string.format("%02x", buffer.readu8(input_hash, i)))
|
|
||||||
end
|
|
||||||
|
|
||||||
print(`Deserializing plugin file (size: {buffer.len(input)} bytes, hash: {table.concat(hex_hash, "")})`)
|
|
||||||
local plugin = SerializationService:DeserializeInstancesAsync(input)[1]
|
|
||||||
|
|
||||||
local UploadDetails = require(plugin.UploadDetails) :: any
|
|
||||||
local PLUGIN_ID = UploadDetails.assetId
|
|
||||||
local PLUGIN_NAME = UploadDetails.name
|
|
||||||
local PLUGIN_DESCRIPTION = UploadDetails.description
|
|
||||||
local PLUGIN_CREATOR_ID = UploadDetails.creatorId
|
|
||||||
local PLUGIN_CREATOR_TYPE = UploadDetails.creatorType
|
|
||||||
|
|
||||||
assert(typeof(PLUGIN_ID) == "number", "UploadDetails did not contain a number field 'assetId'")
|
|
||||||
assert(typeof(PLUGIN_NAME) == "string", "UploadDetails did not contain a string field 'name'")
|
|
||||||
assert(typeof(PLUGIN_DESCRIPTION) == "string", "UploadDetails did not contain a string field 'description'")
|
|
||||||
assert(typeof(PLUGIN_CREATOR_ID) == "number", "UploadDetails did not contain a number field 'creatorId'")
|
|
||||||
assert(typeof(PLUGIN_CREATOR_TYPE) == "string", "UploadDetails did not contain a string field 'creatorType'")
|
|
||||||
assert(
|
|
||||||
Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE) ~= nil,
|
|
||||||
"UploadDetails field 'creatorType' was not a valid member of Enum.AssetCreatorType"
|
|
||||||
)
|
|
||||||
|
|
||||||
print(`Uploading to {PLUGIN_ID}`)
|
|
||||||
print(`Plugin Name: {PLUGIN_NAME}`)
|
|
||||||
print(`Plugin Description: {PLUGIN_DESCRIPTION}`)
|
|
||||||
|
|
||||||
local result, version_or_err = AssetService:CreateAssetVersionAsync(plugin, Enum.AssetType.Plugin, PLUGIN_ID, {
|
|
||||||
["Name"] = PLUGIN_NAME,
|
|
||||||
["Description"] = PLUGIN_DESCRIPTION,
|
|
||||||
["CreatorId"] = PLUGIN_CREATOR_ID,
|
|
||||||
["CreatorType"] = Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE),
|
|
||||||
})
|
|
||||||
|
|
||||||
if result ~= Enum.CreateAssetResult.Success then
|
|
||||||
error(`Plugin failed to upload because: {result.Name} - {version_or_err}`)
|
|
||||||
end
|
|
||||||
|
|
||||||
print(`Plugin uploaded successfully. New version is {version_or_err}.`)
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
local fs = require("@lune/fs")
|
|
||||||
local process = require("@lune/process")
|
|
||||||
local stdio = require("@lune/stdio")
|
|
||||||
|
|
||||||
local luau_execute = require("./opencloud-execute")
|
|
||||||
|
|
||||||
local UNIVERSE_ID = process.env["RBX_UNIVERSE_ID"]
|
|
||||||
local PLACE_ID = process.env["RBX_PLACE_ID"]
|
|
||||||
|
|
||||||
local version_string = fs.readFile("plugin/Version.txt")
|
|
||||||
local versions = { string.match(version_string, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
|
|
||||||
if versions[4] ~= "" then
|
|
||||||
print("This release is a pre-release. Skipping uploading plugin.")
|
|
||||||
process.exit(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local plugin_path = process.args[1]
|
|
||||||
assert(
|
|
||||||
typeof(plugin_path) == "string",
|
|
||||||
"no plugin path provided, expected usage is `lune run upload-plugin [PATH TO RBXM]`."
|
|
||||||
)
|
|
||||||
|
|
||||||
-- For local testing
|
|
||||||
if process.env["CI"] ~= "true" then
|
|
||||||
local rojo = process.exec("rojo", { "build", "plugin.project.json", "--output", plugin_path })
|
|
||||||
if not rojo.ok then
|
|
||||||
stdio.ewrite("plugin upload failed because: could not build plugin.rbxm\n\n")
|
|
||||||
stdio.ewrite(rojo.stderr)
|
|
||||||
stdio.ewrite("\n")
|
|
||||||
process.exit(1)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
assert(fs.isFile(plugin_path), `Plugin file did not exist at {plugin_path}`)
|
|
||||||
end
|
|
||||||
local plugin_content = fs.readFile(plugin_path)
|
|
||||||
|
|
||||||
local engine_script = fs.readFile(".lune/scripts/plugin-upload.luau")
|
|
||||||
|
|
||||||
print("Creating task to upload plugin")
|
|
||||||
local task = luau_execute.create_task_latest(UNIVERSE_ID, PLACE_ID, engine_script, 300, false, plugin_content)
|
|
||||||
|
|
||||||
print("Waiting for task to finish")
|
|
||||||
local success = luau_execute.await_finish(task)
|
|
||||||
if not success then
|
|
||||||
local error = luau_execute.get_error(task)
|
|
||||||
assert(error, "could not fetch error from task")
|
|
||||||
stdio.ewrite("plugin upload failed because: task did not finish successfully\n\n")
|
|
||||||
stdio.ewrite(error.code)
|
|
||||||
stdio.ewrite("\n")
|
|
||||||
stdio.ewrite(error.message)
|
|
||||||
stdio.ewrite("\n")
|
|
||||||
process.exit(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Output from task:\n")
|
|
||||||
for _, log in luau_execute.get_structured_logs(task) do
|
|
||||||
if log.messageType == "ERROR" then
|
|
||||||
stdio.write(stdio.color("red"))
|
|
||||||
stdio.write(log.message)
|
|
||||||
stdio.write("\n")
|
|
||||||
stdio.write(stdio.color("reset"))
|
|
||||||
elseif log.messageType == "INFO" then
|
|
||||||
stdio.write(stdio.color("cyan"))
|
|
||||||
stdio.write(log.message)
|
|
||||||
stdio.write("\n")
|
|
||||||
stdio.write(stdio.color("reset"))
|
|
||||||
elseif log.messageType == "WARNING" then
|
|
||||||
stdio.write(stdio.color("yellow"))
|
|
||||||
stdio.write(log.message)
|
|
||||||
stdio.write("\n")
|
|
||||||
stdio.write(stdio.color("reset"))
|
|
||||||
else
|
|
||||||
stdio.write(stdio.color("reset"))
|
|
||||||
stdio.write(log.message)
|
|
||||||
stdio.write("\n")
|
|
||||||
stdio.write(stdio.color("reset"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
8
.vscode/extensions.json
vendored
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"JohnnyMorganz.luau-lsp",
|
|
||||||
"JohnnyMorganz.stylua",
|
|
||||||
"Kampfkarren.selene-vscode",
|
|
||||||
"rust-lang.rust-analyzer"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"luau-lsp.sourcemap.rojoProjectFile": "plugin.project.json",
|
|
||||||
"luau-lsp.sourcemap.autogenerate": true
|
|
||||||
}
|
|
||||||
1274
CHANGELOG.md
@@ -14,31 +14,13 @@ Code contributions are welcome for features and bugs that have been reported in
|
|||||||
You'll want these tools to work on Rojo:
|
You'll want these tools to work on Rojo:
|
||||||
|
|
||||||
* Latest stable Rust compiler
|
* Latest stable Rust compiler
|
||||||
* Rustfmt and Clippy are used for code formatting and linting.
|
|
||||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||||
* [Rokit](https://github.com/rojo-rbx/rokit)
|
* [Foreman](https://github.com/Roblox/foreman)
|
||||||
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
|
||||||
|
|
||||||
When working on the Studio plugin, we recommend using this command to automatically rebuild the plugin when you save a change:
|
|
||||||
|
|
||||||
*(Make sure you've enabled the Studio setting to reload plugins on file change!)*
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/watch-build-plugin.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also run the plugin's unit tests with the following:
|
|
||||||
|
|
||||||
*(Make sure you have `run-in-roblox` installed first!)*
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/unit-test-plugin.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Documentation impacts way more people than the individual lines of code we write.
|
Documentation impacts way more people than the individual lines of code we write.
|
||||||
|
|
||||||
If you find any problems in the documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
If you find any problems in documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||||
|
|
||||||
## Bug Reports and Feature Requests
|
## Bug Reports and Feature Requests
|
||||||
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
||||||
@@ -47,29 +29,25 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
|||||||
|
|
||||||
Please file issues and we'll try to help figure out what the best way forward is.
|
Please file issues and we'll try to help figure out what the best way forward is.
|
||||||
|
|
||||||
## Local Development Gotchas
|
|
||||||
|
|
||||||
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
|
|
||||||
Run the command and try building again: `git submodule update --init --recursive`.
|
|
||||||
|
|
||||||
## Pushing a Rojo Release
|
## Pushing a Rojo Release
|
||||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||||
|
|
||||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||||
3. Run `cargo test` to update `Cargo.lock` and run tests
|
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
5. Commit!
|
5. Commit!
|
||||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||||
6. Tag the commit
|
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||||
* `git tag vX.Y.Z`
|
|
||||||
7. Publish the CLI
|
7. Publish the CLI
|
||||||
* `cargo publish`
|
* `cargo publish`
|
||||||
8. Publish the Plugin
|
8. Build and upload the plugin
|
||||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
* `rojo build plugin -o Rojo.rbxm`
|
||||||
|
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||||
9. Push commits and tags
|
9. Push commits and tags
|
||||||
* `git push && git push --tags`
|
* `git push && git push --tags`
|
||||||
10. Copy GitHub release content from previous release
|
10. Copy GitHub release content from previous release
|
||||||
* Update the leading text with a summary about the release
|
* Update the leading text with a summary about the release
|
||||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
* Write a small summary of each major feature
|
* Write a small summary of each major feature
|
||||||
|
* Attach release artifacts from GitHub Actions for each platform
|
||||||
3529
Cargo.lock
generated
152
Cargo.toml
@@ -1,22 +1,18 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.7.0-rc.1"
|
version = "6.1.0"
|
||||||
rust-version = "1.88"
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
authors = [
|
|
||||||
"Lucien Greathouse <me@lpghatguy.com>",
|
|
||||||
"Micah Reid <git@dekkonot.com>",
|
|
||||||
"Ken Loeffler <kenloef@gmail.com>",
|
|
||||||
]
|
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
homepage = "https://rojo.space"
|
homepage = "https://rojo.space"
|
||||||
documentation = "https://rojo.space/docs"
|
documentation = "https://rojo.space/docs"
|
||||||
repository = "https://github.com/rojo-rbx/rojo"
|
repository = "https://github.com/rojo-rbx/rojo"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
exclude = ["/test-projects/**"]
|
exclude = [
|
||||||
|
"/test-projects/**",
|
||||||
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
@@ -30,104 +26,80 @@ default = []
|
|||||||
# Enable this feature to live-reload assets from the web UI.
|
# Enable this feature to live-reload assets from the web UI.
|
||||||
dev_live_assets = []
|
dev_live_assets = []
|
||||||
|
|
||||||
# Run Rojo with this feature to open a Tracy session.
|
|
||||||
# Currently uses protocol v63, last supported in Tracy 0.9.1.
|
|
||||||
profile-with-tracy = ["profiling/profile-with-tracy"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = [
|
||||||
|
"rojo-insta-ext",
|
||||||
|
"memofs",
|
||||||
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "librojo"
|
name = "librojo"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rojo"
|
||||||
|
path = "src/bin.rs"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "build"
|
name = "build"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.3.1", path = "crates/memofs" }
|
memofs = { version = "0.1.2", path = "memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
anyhow = "1.0.27"
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary", features = [
|
backtrace = "0.3"
|
||||||
# "unstable_text_format",
|
bincode = "1.2.1"
|
||||||
# ] }
|
crossbeam-channel = "0.4.0"
|
||||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
csv = "1.1.1"
|
||||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
env_logger = "0.7.1"
|
||||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
fs-err = "2.2.0"
|
||||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
futures = "0.1.29"
|
||||||
|
globset = "0.4.4"
|
||||||
rbx_binary = { version = "2.0.1", features = ["unstable_text_format"] }
|
humantime = "1.3.0"
|
||||||
rbx_dom_weak = "4.1.0"
|
hyper = "0.12.35"
|
||||||
rbx_reflection = "6.1.0"
|
jod-thread = "0.1.0"
|
||||||
rbx_reflection_database = "2.0.2"
|
lazy_static = "1.4.0"
|
||||||
rbx_xml = "2.0.1"
|
log = "0.4.8"
|
||||||
|
maplit = "1.0.1"
|
||||||
anyhow = "1.0.80"
|
notify = "4.0.14"
|
||||||
backtrace = "0.3.69"
|
opener = "0.4.1"
|
||||||
bincode = "1.3.3"
|
rbx_binary = "0.5.0"
|
||||||
crossbeam-channel = "0.5.12"
|
rbx_dom_weak = "1.10.1"
|
||||||
csv = "1.3.0"
|
rbx_reflection = "3.3.408"
|
||||||
env_logger = "0.9.3"
|
rbx_xml = "0.11.3"
|
||||||
fs-err = "2.11.0"
|
regex = "1.3.1"
|
||||||
futures = "0.3.30"
|
reqwest = "0.9.20"
|
||||||
globset = "0.4.14"
|
|
||||||
humantime = "2.1.0"
|
|
||||||
hyper = { version = "0.14.28", features = ["server", "tcp", "http1"] }
|
|
||||||
hyper-tungstenite = "0.11.0"
|
|
||||||
jod-thread = "0.1.2"
|
|
||||||
log = "0.4.21"
|
|
||||||
num_cpus = "1.16.0"
|
|
||||||
opener = "0.5.2"
|
|
||||||
rayon = "1.9.0"
|
|
||||||
reqwest = { version = "0.11.24", default-features = false, features = [
|
|
||||||
"blocking",
|
|
||||||
"json",
|
|
||||||
"rustls-tls",
|
|
||||||
] }
|
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
roblox_install = "1.0.0"
|
rlua = "0.17.0"
|
||||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
roblox_install = "0.2.2"
|
||||||
serde_json = "1.0.145"
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
jsonc-parser = { version = "0.27.0", features = ["serde"] }
|
serde_json = "1.0"
|
||||||
strum = { version = "0.27", features = ["derive"] }
|
structopt = "0.3.5"
|
||||||
toml = "0.5.11"
|
termcolor = "1.0.5"
|
||||||
termcolor = "1.4.1"
|
thiserror = "1.0.11"
|
||||||
thiserror = "1.0.57"
|
tokio = "0.1.22"
|
||||||
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
|
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
||||||
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
|
||||||
clap = { version = "3.2.25", features = ["derive"] }
|
|
||||||
profiling = "1.0.15"
|
|
||||||
yaml-rust2 = "0.10.3"
|
|
||||||
data-encoding = "2.8.0"
|
|
||||||
pathdiff = "0.2.3"
|
|
||||||
|
|
||||||
blake3 = "1.5.0"
|
|
||||||
float-cmp = "0.9.0"
|
|
||||||
indexmap = { version = "2.10.0", features = ["serde"] }
|
|
||||||
rmp-serde = "1.3.0"
|
|
||||||
serde_bytes = "0.11.19"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.6.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.3.0", path = "crates/memofs" }
|
memofs = { version = "0.1.3", path = "memofs" }
|
||||||
|
|
||||||
embed-resource = "1.8.0"
|
anyhow = "1.0.27"
|
||||||
anyhow = "1.0.80"
|
bincode = "1.2.1"
|
||||||
bincode = "1.3.3"
|
fs-err = "2.3.0"
|
||||||
fs-err = "2.11.0"
|
maplit = "1.0.1"
|
||||||
maplit = "1.0.2"
|
|
||||||
semver = "1.0.22"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3.6"
|
criterion = "0.3"
|
||||||
insta = { version = "1.36.1", features = ["redactions", "yaml", "json"] }
|
insta = { version = "1.3.0", features = ["redactions"] }
|
||||||
paste = "1.0.14"
|
lazy_static = "1.2"
|
||||||
pretty_assertions = "1.4.0"
|
paste = "0.1"
|
||||||
serde_yaml = "0.8.26"
|
pretty_assertions = "0.6.1"
|
||||||
tempfile = "3.10.1"
|
serde_yaml = "0.8.9"
|
||||||
walkdir = "2.5.0"
|
tempfile = "3.0"
|
||||||
|
walkdir = "2.1"
|
||||||
|
|||||||
20
README.md
@@ -1,13 +1,21 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://rojo.space"><img src="assets/brand_images/logo-512.png" alt="Rojo" height="217" /></a>
|
<a href="https://rojo.space">
|
||||||
|
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div> </div>
|
<div> </div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
|
<a href="https://github.com/rojo-rbx/rojo/actions">
|
||||||
<a href="https://crates.io/crates/rojo"><img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" /></a>
|
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
||||||
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
</a>
|
||||||
|
<a href="https://crates.io/crates/rojo">
|
||||||
|
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
|
||||||
|
</a>
|
||||||
|
<a href="https://rojo.space/docs">
|
||||||
|
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@@ -40,7 +48,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.88 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.43.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -2,7 +2,7 @@
|
|||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
To build this library, use:
|
To build this library or plugin, use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rojo build -o "{project_name}.rbxmx"
|
rojo build -o "{project_name}.rbxmx"
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
# Roblox Studio lock files
|
# Roblox Studio lock files
|
||||||
/*.rbxlx.lock
|
/*.rbxlx.lock
|
||||||
/*.rbxl.lock
|
/*.rbxl.lock
|
||||||
|
|
||||||
sourcemap.json
|
|
||||||
@@ -2,4 +2,4 @@ return {
|
|||||||
hello = function()
|
hello = function()
|
||||||
print("Hello world, from {project_name}!")
|
print("Hello world, from {project_name}!")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
|
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"Shared": {
|
"Common": {
|
||||||
"$path": "src/shared"
|
"$path": "src/shared"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3,6 +3,4 @@
|
|||||||
|
|
||||||
# Roblox Studio lock files
|
# Roblox Studio lock files
|
||||||
/*.rbxlx.lock
|
/*.rbxlx.lock
|
||||||
/*.rbxl.lock
|
/*.rbxl.lock
|
||||||
|
|
||||||
sourcemap.json
|
|
||||||
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 584 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 574 B |
|
Before Width: | Height: | Size: 607 B |
@@ -17,10 +17,6 @@ html {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #e7e7e7
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width:100%;
|
max-width:100%;
|
||||||
max-height:100%;
|
max-height:100%;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -1 +0,0 @@
|
|||||||
print("Hello world, from client!")
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
print("Hello world, from server!")
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
return function()
|
|
||||||
print("Hello, world!")
|
|
||||||
end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# {project_name}
|
|
||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
To build this plugin to your local plugins folder, use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rojo build -p "{project_name}.rbxm"
|
|
||||||
```
|
|
||||||
|
|
||||||
You can include the `watch` flag to re-build it on save:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rojo build -p "{project_name}.rbxm" --watch
|
|
||||||
```
|
|
||||||
|
|
||||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "{project_name}",
|
|
||||||
"tree": {
|
|
||||||
"$path": "src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Plugin model files
|
|
||||||
/{project_name}.rbxmx
|
|
||||||
/{project_name}.rbxm
|
|
||||||
|
|
||||||
sourcemap.json
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
print("Hello world, from plugin!")
|
|
||||||
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use librojo::cli::BuildCommand;
|
use librojo::cli::{build, BuildCommand};
|
||||||
|
|
||||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||||
@@ -20,7 +20,7 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
group.bench_function("build", |b| {
|
group.bench_function("build", |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| place_setup(path),
|
|| place_setup(path),
|
||||||
|(_dir, options)| options.run().unwrap(),
|
|(_dir, options)| build(options).unwrap(),
|
||||||
BatchSize::SmallInput,
|
BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -31,12 +31,11 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let input = input_path.as_ref().to_path_buf();
|
let input = input_path.as_ref().to_path_buf();
|
||||||
let output = Some(dir.path().join("output.rbxlx"));
|
let output = dir.path().join("output.rbxlx");
|
||||||
|
|
||||||
let options = BuildCommand {
|
let options = BuildCommand {
|
||||||
project: input,
|
project: input,
|
||||||
watch: false,
|
watch: false,
|
||||||
plugin: None,
|
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
5
bin/dev-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
||||||
13
bin/install-dev-plugin.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( mktemp -d )"
|
||||||
|
PLUGIN_FILE="$DIR/Rojo.rbxm"
|
||||||
|
TESTEZ_FILE="$DIR/TestEZ.rbxm"
|
||||||
|
|
||||||
|
rojo build plugin -o "$PLUGIN_FILE"
|
||||||
|
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
|
||||||
|
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||||
5
bin/install-release-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||||
12
bin/mark-plugin-as-dev.lua
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
local pluginPath, testezPath = ...
|
||||||
|
|
||||||
|
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||||
|
local testez = remodel.readModelFile(testezPath)[1]
|
||||||
|
|
||||||
|
local marker = Instance.new("Folder")
|
||||||
|
marker.Name = "ROJO_DEV_BUILD"
|
||||||
|
marker.Parent = plugin
|
||||||
|
|
||||||
|
testez.Parent = plugin
|
||||||
|
|
||||||
|
remodel.writeModelFile(plugin, pluginPath)
|
||||||
8
bin/put-plugin-in-test-place.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
local pluginPath, placePath = ...
|
||||||
|
|
||||||
|
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||||
|
local place = remodel.readPlaceFile(placePath)
|
||||||
|
|
||||||
|
plugin.Parent = place:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
|
remodel.writePlaceFile(place, placePath)
|
||||||
6
bin/run-all-tests.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
./bin/run-cli-tests.sh
|
||||||
|
./bin/run-plugin-tests.sh
|
||||||
9
bin/run-cli-tests.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cargo test --all --locked
|
||||||
|
cargo fmt -- --check
|
||||||
|
|
||||||
|
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
|
||||||
|
cargo clippy
|
||||||
16
bin/run-plugin-tests.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( mktemp -d )"
|
||||||
|
PLUGIN_FILE="$DIR/Rojo.rbxmx"
|
||||||
|
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
|
||||||
|
|
||||||
|
rojo build plugin -o "$PLUGIN_FILE"
|
||||||
|
rojo build plugin/place.project.json -o "$PLACE_FILE"
|
||||||
|
|
||||||
|
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
|
||||||
|
|
||||||
|
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
|
||||||
|
|
||||||
|
luacheck plugin/src plugin/log plugin/http
|
||||||
70
build.rs
@@ -7,7 +7,6 @@ use fs_err as fs;
|
|||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use maplit::hashmap;
|
use maplit::hashmap;
|
||||||
use memofs::VfsSnapshot;
|
use memofs::VfsSnapshot;
|
||||||
use semver::Version;
|
|
||||||
|
|
||||||
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||||
println!("cargo:rerun-if-changed={}", path.display());
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
@@ -20,18 +19,9 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
||||||
|
|
||||||
if file_name.starts_with(".git") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
if file_name.ends_with(".spec.lua") {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore images in msgpack-luau because they aren't UTF-8 encoded.
|
|
||||||
if file_name.ends_with(".png") {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,44 +40,38 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
|
||||||
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||||
let plugin_dir = root_dir.join("plugin");
|
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
||||||
let templates_dir = root_dir.join("assets").join("project-templates");
|
|
||||||
|
|
||||||
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
let plugin_modules = plugin_root.join("modules");
|
||||||
let plugin_version =
|
|
||||||
Version::parse(fs::read_to_string(plugin_dir.join("Version.txt"))?.trim())?;
|
|
||||||
|
|
||||||
assert_eq!(
|
let snapshot = VfsSnapshot::dir(hashmap! {
|
||||||
our_version, plugin_version,
|
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
||||||
"plugin version does not match Cargo version"
|
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
|
||||||
);
|
"http" => snapshot_from_fs_path(&plugin_root.join("http"))?,
|
||||||
|
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
||||||
let template_snapshot = snapshot_from_fs_path(&templates_dir)?;
|
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
||||||
|
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
||||||
let plugin_snapshot = VfsSnapshot::dir(hashmap! {
|
"modules" => VfsSnapshot::dir(hashmap! {
|
||||||
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
|
"roact" => VfsSnapshot::dir(hashmap! {
|
||||||
"plugin" => VfsSnapshot::dir(hashmap! {
|
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
|
||||||
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
|
}),
|
||||||
"http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
|
"promise" => VfsSnapshot::dir(hashmap! {
|
||||||
"log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
|
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
|
||||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
|
}),
|
||||||
"src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
|
"t" => VfsSnapshot::dir(hashmap! {
|
||||||
"Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?,
|
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
|
||||||
"Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?,
|
}),
|
||||||
"UploadDetails.json" => snapshot_from_fs_path(&plugin_dir.join("UploadDetails.json"))?,
|
"flipper" => VfsSnapshot::dir(hashmap! {
|
||||||
|
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
let template_file = File::create(Path::new(&out_dir).join("templates.bincode"))?;
|
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
||||||
let plugin_file = File::create(Path::new(&out_dir).join("plugin.bincode"))?;
|
let out_file = File::create(&out_path)?;
|
||||||
|
|
||||||
bincode::serialize_into(plugin_file, &plugin_snapshot)?;
|
bincode::serialize_into(out_file, &snapshot)?;
|
||||||
bincode::serialize_into(template_file, &template_snapshot)?;
|
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
|
||||||
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
|
||||||
embed_resource::compile("build/windows/rojo-manifest.rc");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#define RT_MANIFEST 24
|
|
||||||
1 RT_MANIFEST "rojo.manifest"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
|
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
|
||||||
<ws2:longPathAware>true</ws2:longPathAware>
|
|
||||||
</windowsSettings>
|
|
||||||
</application>
|
|
||||||
</assembly>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# memofs Changelog
|
|
||||||
|
|
||||||
## Unreleased Changes
|
|
||||||
* Added `Vfs::canonicalize`. [#1201]
|
|
||||||
|
|
||||||
## 0.3.1 (2025-11-27)
|
|
||||||
* Added `Vfs::exists`. [#1169]
|
|
||||||
* Added `create_dir` and `create_dir_all` to allow creating directories. [#937]
|
|
||||||
|
|
||||||
[#1169]: https://github.com/rojo-rbx/rojo/pull/1169
|
|
||||||
[#937]: https://github.com/rojo-rbx/rojo/pull/937
|
|
||||||
|
|
||||||
## 0.3.0 (2024-03-15)
|
|
||||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
|
||||||
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
|
||||||
|
|
||||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
|
||||||
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
|
||||||
|
|
||||||
## 0.2.0 (2021-08-23)
|
|
||||||
* Updated to `crossbeam-channel` 0.5.1.
|
|
||||||
|
|
||||||
## 0.1.3 (2020-11-19)
|
|
||||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
|
||||||
|
|
||||||
## 0.1.2 (2020-03-29)
|
|
||||||
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
|
||||||
|
|
||||||
## 0.1.1 (2020-03-18)
|
|
||||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
|
||||||
|
|
||||||
## 0.1.0 (2020-03-10)
|
|
||||||
* Initial release
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
|
||||||
|
|
||||||
/// `VfsBackend` that returns an error on every operation.
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct NoopBackend;
|
|
||||||
|
|
||||||
impl NoopBackend {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VfsBackend for NoopBackend {
|
|
||||||
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists(&mut self, _path: &Path) -> io::Result<bool> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn canonicalize(&mut self, _path: &Path) -> io::Result<PathBuf> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
|
||||||
crossbeam_channel::never()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for NoopBackend {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
foreman.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tools]
|
||||||
|
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
|
||||||
|
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||||
15
memofs/CHANGELOG.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# memofs Changelog
|
||||||
|
|
||||||
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## 0.1.3 (2020-11-19)
|
||||||
|
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||||
|
|
||||||
|
## 0.1.2 (2020-03-29)
|
||||||
|
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
||||||
|
|
||||||
|
## 0.1.1 (2020-03-18)
|
||||||
|
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||||
|
|
||||||
|
## 0.1.0 (2020-03-10)
|
||||||
|
* Initial release
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
description = "Virtual filesystem with configurable backends."
|
description = "Virtual filesystem with configurable backends."
|
||||||
version = "0.3.1"
|
version = "0.1.3"
|
||||||
authors = [
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
"Lucien Greathouse <me@lpghatguy.com>",
|
|
||||||
"Micah Reid <git@dekkonot.com>",
|
|
||||||
"Ken Loeffler <kenloef@gmail.com>",
|
|
||||||
]
|
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -15,10 +11,7 @@ homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5.12"
|
crossbeam-channel = "0.4.0"
|
||||||
fs-err = "2.11.0"
|
fs-err = "2.3.0"
|
||||||
notify = "4.0.17"
|
notify = "4.0.15"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3.10.1"
|
|
||||||
@@ -50,12 +50,6 @@ impl InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for InMemoryFs {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InMemoryFsInner {
|
struct InMemoryFsInner {
|
||||||
entries: HashMap<PathBuf, Entry>,
|
entries: HashMap<PathBuf, Entry>,
|
||||||
@@ -157,11 +151,6 @@ impl VfsBackend for InMemoryFs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
|
||||||
let inner = self.inner.lock().unwrap();
|
|
||||||
Ok(inner.entries.contains_key(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -181,21 +170,6 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
|
||||||
let mut inner = self.inner.lock().unwrap();
|
|
||||||
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
|
||||||
let mut inner = self.inner.lock().unwrap();
|
|
||||||
let mut path_buf = path.to_path_buf();
|
|
||||||
while let Some(parent) = path_buf.parent() {
|
|
||||||
inner.load_snapshot(parent.to_path_buf(), VfsSnapshot::empty_dir())?;
|
|
||||||
path_buf.pop();
|
|
||||||
}
|
|
||||||
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -232,33 +206,6 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We rely on Rojo to prepend cwd to any relative path before storing paths
|
|
||||||
// in MemoFS. The current implementation will error if no prepended absolute path
|
|
||||||
// is found. It really only normalizes paths within the provided path's context.
|
|
||||||
// Example: "/Users/username/project/../other/file.txt" ->
|
|
||||||
// "/Users/username/other/file.txt"
|
|
||||||
// Erroneous example: "/Users/../../other/file.txt" -> "/other/file.txt"
|
|
||||||
// This is not very robust. We should implement proper path normalization here or otherwise
|
|
||||||
// warn if we are missing context and can not fully canonicalize the path correctly.
|
|
||||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
|
||||||
let mut normalized = PathBuf::new();
|
|
||||||
for component in path.components() {
|
|
||||||
match component {
|
|
||||||
std::path::Component::ParentDir => {
|
|
||||||
normalized.pop();
|
|
||||||
}
|
|
||||||
std::path::Component::CurDir => {}
|
|
||||||
_ => normalized.push(component),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner = self.inner.lock().unwrap();
|
|
||||||
match inner.entries.get(&normalized) {
|
|
||||||
Some(_) => Ok(normalized),
|
|
||||||
None => not_found(&normalized),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -275,17 +222,23 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::other(format!(
|
Err(io::Error::new(
|
||||||
"path {} was a directory, but must be a file",
|
io::ErrorKind::Other,
|
||||||
path.display()
|
format!(
|
||||||
)))
|
"path {} was a directory, but must be a file",
|
||||||
|
path.display()
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::other(format!(
|
Err(io::Error::new(
|
||||||
"path {} was a file, but must be a directory",
|
io::ErrorKind::Other,
|
||||||
path.display()
|
format!(
|
||||||
)))
|
"path {} was a file, but must be a directory",
|
||||||
|
path.display()
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found<T>(path: &Path) -> io::Result<T> {
|
fn not_found<T>(path: &Path) -> io::Result<T> {
|
||||||
@@ -22,9 +22,9 @@ mod noop_backend;
|
|||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod std_backend;
|
mod std_backend;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use std::{io, str};
|
|
||||||
|
|
||||||
pub use in_memory_fs::InMemoryFs;
|
pub use in_memory_fs::InMemoryFs;
|
||||||
pub use noop_backend::NoopBackend;
|
pub use noop_backend::NoopBackend;
|
||||||
@@ -70,14 +70,10 @@ impl<T> IoResultExt<T> for io::Result<T> {
|
|||||||
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
||||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
||||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
||||||
fn exists(&mut self, path: &Path) -> io::Result<bool>;
|
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
||||||
fn create_dir(&mut self, path: &Path) -> io::Result<()>;
|
|
||||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
|
||||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
||||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf>;
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
||||||
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
||||||
@@ -159,29 +155,6 @@ impl VfsInner {
|
|||||||
Ok(Arc::new(contents))
|
Ok(Arc::new(contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_to_string<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<String>> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let contents = self.backend.read(path)?;
|
|
||||||
|
|
||||||
if self.watch_enabled {
|
|
||||||
self.backend.watch(path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contents_str = str::from_utf8(&contents).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("File was not valid UTF-8: {}", path.display()),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Arc::new(contents_str.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists<P: AsRef<Path>>(&mut self, path: P) -> io::Result<bool> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.backend.exists(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let contents = contents.as_ref();
|
let contents = contents.as_ref();
|
||||||
@@ -199,16 +172,6 @@ impl VfsInner {
|
|||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.backend.create_dir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.backend.create_dir_all(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let _ = self.backend.unwatch(path);
|
let _ = self.backend.unwatch(path);
|
||||||
@@ -226,18 +189,16 @@ impl VfsInner {
|
|||||||
self.backend.metadata(path)
|
self.backend.metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.backend.canonicalize(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
self.backend.event_receiver()
|
self.backend.event_receiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||||
if let VfsEvent::Remove(path) = event {
|
match event {
|
||||||
let _ = self.backend.unwatch(path);
|
VfsEvent::Remove(path) => {
|
||||||
|
let _ = self.backend.unwatch(&path);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -300,33 +261,6 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().read(path)
|
self.inner.lock().unwrap().read(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a file from the VFS (or from the underlying backend if it isn't
|
|
||||||
/// resident) into a string.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string].
|
|
||||||
///
|
|
||||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
|
||||||
#[inline]
|
|
||||||
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.lock().unwrap().read_to_string(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a file from the VFS (or the underlying backend if it isn't
|
|
||||||
/// resident) into a string, and normalize its line endings to LF.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string], but also performs
|
|
||||||
/// line ending normalization.
|
|
||||||
///
|
|
||||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
|
||||||
#[inline]
|
|
||||||
pub fn read_to_string_lf_normalized<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
|
||||||
|
|
||||||
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.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||||
@@ -350,42 +284,6 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().read_dir(path)
|
self.inner.lock().unwrap().read_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return whether the given path exists.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::exists`][std::fs::exists].
|
|
||||||
///
|
|
||||||
/// [std::fs::exists]: https://doc.rust-lang.org/stable/std/fs/fn.exists.html
|
|
||||||
#[inline]
|
|
||||||
pub fn exists<P: AsRef<Path>>(&self, path: P) -> io::Result<bool> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.lock().unwrap().exists(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory at the provided location.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
|
||||||
/// Similiar to that function, this function will fail if the parent of the
|
|
||||||
/// path does not exist.
|
|
||||||
///
|
|
||||||
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
|
||||||
#[inline]
|
|
||||||
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.lock().unwrap().create_dir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory at the provided location, recursively creating
|
|
||||||
/// all parent components if they are missing.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
|
||||||
///
|
|
||||||
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
|
||||||
#[inline]
|
|
||||||
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.lock().unwrap().create_dir_all(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a file.
|
/// Remove a file.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||||
@@ -419,19 +317,6 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().metadata(path)
|
self.inner.lock().unwrap().metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize a path via the underlying backend.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::canonicalize`][std::fs::canonicalize]. Relative paths are
|
|
||||||
/// resolved against the backend's current working directory (if applicable) and errors are
|
|
||||||
/// surfaced directly from the backend.
|
|
||||||
///
|
|
||||||
/// [std::fs::canonicalize]: https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html
|
|
||||||
#[inline]
|
|
||||||
pub fn canonicalize<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.lock().unwrap().canonicalize(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
@@ -501,31 +386,6 @@ impl VfsLock<'_> {
|
|||||||
self.inner.read_dir(path)
|
self.inner.read_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a directory at the provided location.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
|
||||||
/// Similiar to that function, this function will fail if the parent of the
|
|
||||||
/// path does not exist.
|
|
||||||
///
|
|
||||||
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
|
||||||
#[inline]
|
|
||||||
pub fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.create_dir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory at the provided location, recursively creating
|
|
||||||
/// all parent components if they are missing.
|
|
||||||
///
|
|
||||||
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
|
||||||
///
|
|
||||||
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
|
||||||
#[inline]
|
|
||||||
pub fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.create_dir_all(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a file.
|
/// Remove a file.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||||
@@ -559,13 +419,6 @@ impl VfsLock<'_> {
|
|||||||
self.inner.metadata(path)
|
self.inner.metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize a path via the underlying backend.
|
|
||||||
#[inline]
|
|
||||||
pub fn normalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.inner.canonicalize(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
@@ -578,83 +431,3 @@ impl VfsLock<'_> {
|
|||||||
self.inner.commit_event(event)
|
self.inner.commit_event(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::{InMemoryFs, StdBackend, Vfs, VfsSnapshot};
|
|
||||||
use std::io;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/// 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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// https://github.com/rojo-rbx/rojo/issues/1200
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_in_memory_success() {
|
|
||||||
let mut imfs = InMemoryFs::new();
|
|
||||||
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
|
||||||
|
|
||||||
imfs.load_snapshot("/test/file.txt", VfsSnapshot::file(contents.to_string()))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vfs = Vfs::new(imfs);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
vfs.canonicalize("/test/nested/../file.txt").unwrap(),
|
|
||||||
PathBuf::from("/test/file.txt")
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
vfs.read_to_string(vfs.canonicalize("/test/nested/../file.txt").unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
contents.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_in_memory_missing_errors() {
|
|
||||||
let imfs = InMemoryFs::new();
|
|
||||||
let vfs = Vfs::new(imfs);
|
|
||||||
|
|
||||||
let err = vfs.canonicalize("test").unwrap_err();
|
|
||||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_std_backend_success() {
|
|
||||||
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
|
||||||
let dir = tempfile::tempdir().unwrap();
|
|
||||||
let file_path = dir.path().join("file.txt");
|
|
||||||
fs_err::write(&file_path, contents.to_string()).unwrap();
|
|
||||||
|
|
||||||
let vfs = Vfs::new(StdBackend::new());
|
|
||||||
let canonicalized = vfs.canonicalize(&file_path).unwrap();
|
|
||||||
assert_eq!(canonicalized, file_path.canonicalize().unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
vfs.read_to_string(&canonicalized).unwrap().to_string(),
|
|
||||||
contents.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_std_backend_missing_errors() {
|
|
||||||
let dir = tempfile::tempdir().unwrap();
|
|
||||||
let file_path = dir.path().join("test");
|
|
||||||
|
|
||||||
let vfs = Vfs::new(StdBackend::new());
|
|
||||||
let err = vfs.canonicalize(&file_path).unwrap_err();
|
|
||||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
memofs/src/noop_backend.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||||
|
|
||||||
|
/// `VfsBackend` that returns an error on every operation.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct NoopBackend;
|
||||||
|
|
||||||
|
impl NoopBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsBackend for NoopBackend {
|
||||||
|
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
|
crossbeam_channel::never()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"NoopBackend doesn't do anything",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{collections::HashSet, io};
|
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
@@ -13,7 +13,6 @@ use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
|||||||
pub struct StdBackend {
|
pub struct StdBackend {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
watcher_receiver: Receiver<VfsEvent>,
|
watcher_receiver: Receiver<VfsEvent>,
|
||||||
watches: HashSet<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdBackend {
|
impl StdBackend {
|
||||||
@@ -49,7 +48,6 @@ impl StdBackend {
|
|||||||
Self {
|
Self {
|
||||||
watcher,
|
watcher,
|
||||||
watcher_receiver: rx,
|
watcher_receiver: rx,
|
||||||
watches: HashSet::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,10 +61,6 @@ impl VfsBackend for StdBackend {
|
|||||||
fs_err::write(path, data)
|
fs_err::write(path, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
|
||||||
std::fs::exists(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||||
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
||||||
let mut entries = entries?;
|
let mut entries = entries?;
|
||||||
@@ -82,14 +76,6 @@ impl VfsBackend for StdBackend {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
|
||||||
fs_err::create_dir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
|
||||||
fs_err::create_dir_all(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||||
fs_err::remove_file(path)
|
fs_err::remove_file(path)
|
||||||
}
|
}
|
||||||
@@ -106,37 +92,19 @@ impl VfsBackend for StdBackend {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
|
||||||
fs_err::canonicalize(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
self.watcher_receiver.clone()
|
self.watcher_receiver.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
if self.watches.contains(path)
|
self.watcher
|
||||||
|| path
|
.watch(path, RecursiveMode::NonRecursive)
|
||||||
.ancestors()
|
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||||
.any(|ancestor| self.watches.contains(ancestor))
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
self.watches.insert(path.to_path_buf());
|
|
||||||
self.watcher
|
|
||||||
.watch(path, RecursiveMode::Recursive)
|
|
||||||
.map_err(io::Error::other)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
self.watches.remove(path);
|
self.watcher
|
||||||
self.watcher.unwatch(path).map_err(io::Error::other)
|
.unwatch(path)
|
||||||
}
|
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StdBackend {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Rojo",
|
|
||||||
"tree": {
|
|
||||||
"$className": "Folder",
|
|
||||||
"Plugin": {
|
|
||||||
"$path": "plugin/src"
|
|
||||||
},
|
|
||||||
"Packages": {
|
|
||||||
"$path": "plugin/Packages",
|
|
||||||
"Log": {
|
|
||||||
"$path": "plugin/log"
|
|
||||||
},
|
|
||||||
"Http": {
|
|
||||||
"$path": "plugin/http"
|
|
||||||
},
|
|
||||||
"Fmt": {
|
|
||||||
"$path": "plugin/fmt"
|
|
||||||
},
|
|
||||||
"RbxDom": {
|
|
||||||
"$path": "plugin/rbx_dom_lua"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Version": {
|
|
||||||
"$path": "plugin/Version.txt"
|
|
||||||
},
|
|
||||||
"UploadDetails": {
|
|
||||||
"$path": "plugin/UploadDetails.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"assetId": 13916111004,
|
|
||||||
"name": "Rojo",
|
|
||||||
"description": "The plugin portion of Rojo, a tool to enable professional tooling for Roblox developers.",
|
|
||||||
"creatorId": 32644114,
|
|
||||||
"creatorType": "Group"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
7.7.0-rc.1
|
|
||||||
33
plugin/default.project.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "Rojo",
|
||||||
|
"tree": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"Plugin": {
|
||||||
|
"$path": "src"
|
||||||
|
},
|
||||||
|
"Log": {
|
||||||
|
"$path": "log"
|
||||||
|
},
|
||||||
|
"Http": {
|
||||||
|
"$path": "http"
|
||||||
|
},
|
||||||
|
"Fmt": {
|
||||||
|
"$path": "fmt"
|
||||||
|
},
|
||||||
|
"RbxDom": {
|
||||||
|
"$path": "rbx_dom_lua"
|
||||||
|
},
|
||||||
|
"Roact": {
|
||||||
|
"$path": "modules/roact/src"
|
||||||
|
},
|
||||||
|
"Promise": {
|
||||||
|
"$path": "modules/promise/lib"
|
||||||
|
},
|
||||||
|
"t": {
|
||||||
|
"$path": "modules/t/lib"
|
||||||
|
},
|
||||||
|
"Flipper": {
|
||||||
|
"$path": "modules/flipper/src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
local function defaultTableDebug(buffer, input)
|
local function defaultTableDebug(buffer, input)
|
||||||
buffer:writeRaw("{")
|
buffer:writeRaw("{")
|
||||||
|
|
||||||
for key, value in input do
|
for key, value in pairs(input) do
|
||||||
buffer:write("[{:?}] = {:?}", key, value)
|
buffer:write("[{:?}] = {:?}", key, value)
|
||||||
|
|
||||||
if next(input, key) ~= nil then
|
if next(input, key) ~= nil then
|
||||||
@@ -50,7 +50,7 @@ local function defaultTableDebugExtended(buffer, input)
|
|||||||
buffer:writeLineRaw("{")
|
buffer:writeLineRaw("{")
|
||||||
buffer:indent()
|
buffer:indent()
|
||||||
|
|
||||||
for key, value in input do
|
for key, value in pairs(input) do
|
||||||
buffer:writeLine("[{:?}] = {:#?},", key, value)
|
buffer:writeLine("[{:?}] = {:#?},", key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ local function debugImpl(buffer, value, extendedForm)
|
|||||||
elseif valueType == "table" then
|
elseif valueType == "table" then
|
||||||
local valueMeta = getmetatable(value)
|
local valueMeta = getmetatable(value)
|
||||||
|
|
||||||
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
||||||
-- This type implement's the metamethod we made up to line up with
|
-- This type implement's the metamethod we made up to line up with
|
||||||
-- Rust's 'Debug' trait.
|
-- Rust's 'Debug' trait.
|
||||||
|
|
||||||
@@ -242,4 +242,4 @@ return {
|
|||||||
debugOutputBuffer = debugOutputBuffer,
|
debugOutputBuffer = debugOutputBuffer,
|
||||||
fmt = fmt,
|
fmt = fmt,
|
||||||
debugify = debugify,
|
debugify = debugify,
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,12 @@ Error.__index = Error
|
|||||||
|
|
||||||
Error.Kind = {
|
Error.Kind = {
|
||||||
HttpNotEnabled = {
|
HttpNotEnabled = {
|
||||||
message = "Rojo requires HTTP access, which is not enabled.\n"
|
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
||||||
.. "Check your game settings, located in the 'Home' tab of Studio.",
|
"Check your game settings, located in the 'Home' tab of Studio.",
|
||||||
},
|
},
|
||||||
ConnectFailed = {
|
ConnectFailed = {
|
||||||
message = "Couldn't connect to the Rojo server.\n"
|
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 = {
|
Timeout = {
|
||||||
message = "HTTP request timed out.",
|
message = "HTTP request timed out.",
|
||||||
@@ -63,13 +63,4 @@ function Error.fromRobloxErrorString(message)
|
|||||||
return Error.new(Error.Kind.Unknown, message)
|
return Error.new(Error.Kind.Unknown, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Error.fromResponse(response)
|
|
||||||
local lower = (response.body or ""):lower()
|
|
||||||
if response.code == 408 or response.code == 504 or lower:find("timed? ?out") then
|
|
||||||
return Error.new(Error.Kind.Timeout)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Error.new(Error.Kind.Unknown, string.format("%s: %s", tostring(response.code), tostring(response.body)))
|
|
||||||
end
|
|
||||||
|
|
||||||
return Error
|
return Error
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
local msgpack = require(script.Parent.Parent.msgpack)
|
|
||||||
|
|
||||||
local stringTemplate = [[
|
local stringTemplate = [[
|
||||||
Http.Response {
|
Http.Response {
|
||||||
code: %d
|
code: %d
|
||||||
@@ -33,8 +31,4 @@ function Response:json()
|
|||||||
return HttpService:JSONDecode(self.body)
|
return HttpService:JSONDecode(self.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Response:msgpack()
|
return Response
|
||||||
return msgpack.decode(self.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Response
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
local Log = require(script.Parent.Log)
|
|
||||||
local msgpack = require(script.Parent.msgpack)
|
|
||||||
local Promise = require(script.Parent.Promise)
|
local Promise = require(script.Parent.Promise)
|
||||||
|
local Log = require(script.Parent.Log)
|
||||||
|
|
||||||
local HttpError = require(script.Error)
|
local HttpError = require(script.Error)
|
||||||
local HttpResponse = require(script.Response)
|
local HttpResponse = require(script.Response)
|
||||||
@@ -31,13 +30,8 @@ local function performRequest(requestParams)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
Log.trace("Request {} success, response {:#?}", requestId, response)
|
Log.trace("Request {} success, status code {}", requestId, response.StatusCode)
|
||||||
local httpResponse = HttpResponse.fromRobloxResponse(response)
|
resolve(HttpResponse.fromRobloxResponse(response))
|
||||||
if httpResponse:isSuccess() then
|
|
||||||
resolve(httpResponse)
|
|
||||||
else
|
|
||||||
reject(HttpError.fromResponse(httpResponse))
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
Log.trace("Request {} failure: {:?}", requestId, response)
|
Log.trace("Request {} failure: {:?}", requestId, response)
|
||||||
reject(HttpError.fromRobloxErrorString(response))
|
reject(HttpError.fromRobloxErrorString(response))
|
||||||
@@ -69,12 +63,4 @@ function Http.jsonDecode(source)
|
|||||||
return HttpService:JSONDecode(source)
|
return HttpService:JSONDecode(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Http.msgpackEncode(object)
|
return Http
|
||||||
return msgpack.encode(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Http.msgpackDecode(source)
|
|
||||||
return msgpack.decode(source)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Http
|
|
||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -57,4 +57,4 @@ function Log.error(template, ...)
|
|||||||
error(Fmt.fmt(template, ...))
|
error(Fmt.fmt(template, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
return Log
|
return Log
|
||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||