mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-23 14:15:24 +00:00
Remove plugin source, moved to rojo-plugin
This commit is contained in:
@@ -4,11 +4,6 @@ root = true
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
[*.lua]
|
|
||||||
indent_style = tab
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.rs]
|
[*.rs]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|||||||
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -1,15 +0,0 @@
|
|||||||
[submodule "modules/roact"]
|
|
||||||
path = modules/roact
|
|
||||||
url = https://github.com/Roblox/roact.git
|
|
||||||
[submodule "modules/rodux"]
|
|
||||||
path = modules/rodux
|
|
||||||
url = https://github.com/Roblox/rodux.git
|
|
||||||
[submodule "modules/roact-rodux"]
|
|
||||||
path = modules/roact-rodux
|
|
||||||
url = https://github.com/Roblox/roact-rodux.git
|
|
||||||
[submodule "modules/testez"]
|
|
||||||
path = modules/testez
|
|
||||||
url = https://github.com/Roblox/testez.git
|
|
||||||
[submodule "modules/lemur"]
|
|
||||||
path = modules/lemur
|
|
||||||
url = https://github.com/LPGhatguy/lemur.git
|
|
||||||
29
.travis.yml
29
.travis.yml
@@ -1,26 +1,5 @@
|
|||||||
language: python
|
language: rust
|
||||||
|
|
||||||
env:
|
rust:
|
||||||
- RUST="stable" LUA="lua=5.1"
|
- stable
|
||||||
- RUST="beta" LUA="lua=5.1"
|
- beta
|
||||||
|
|
||||||
before_install:
|
|
||||||
- pip install hererocks
|
|
||||||
- hererocks lua_install -r^ --$LUA
|
|
||||||
- curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST
|
|
||||||
- export PATH=$PATH:$PWD/lua_install/bin:$HOME/.cargo/bin
|
|
||||||
|
|
||||||
install:
|
|
||||||
- luarocks install luafilesystem
|
|
||||||
- luarocks install busted
|
|
||||||
- luarocks install luacov
|
|
||||||
- luarocks install luacov-coveralls
|
|
||||||
- luarocks install luacheck
|
|
||||||
|
|
||||||
script:
|
|
||||||
- cd plugin && luacheck . && cd ..
|
|
||||||
- lua -lluacov spec.lua
|
|
||||||
- cargo test --verbose
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- luacov-coveralls -e $TRAVIS_BUILD_DIR/lua_install
|
|
||||||
Submodule modules/lemur deleted from c7b1cc335d
Submodule modules/roact deleted from 8eaabac5c1
Submodule modules/roact-rodux deleted from 57f3fc6220
Submodule modules/rodux deleted from 6e1dcc9c4d
Submodule modules/testez deleted from 3c42175897
@@ -1,56 +0,0 @@
|
|||||||
stds.roblox = {
|
|
||||||
read_globals = {
|
|
||||||
game = {
|
|
||||||
other_fields = true,
|
|
||||||
},
|
|
||||||
|
|
||||||
-- Roblox globals
|
|
||||||
"script",
|
|
||||||
|
|
||||||
-- Extra functions
|
|
||||||
"tick", "warn", "spawn",
|
|
||||||
"wait", "settings",
|
|
||||||
|
|
||||||
-- Types
|
|
||||||
"Vector2", "Vector3",
|
|
||||||
"Color3",
|
|
||||||
"UDim", "UDim2",
|
|
||||||
"Rect",
|
|
||||||
"CFrame",
|
|
||||||
"Enum",
|
|
||||||
"Instance",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.plugin = {
|
|
||||||
read_globals = {
|
|
||||||
"plugin",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.testez = {
|
|
||||||
read_globals = {
|
|
||||||
"describe",
|
|
||||||
"it", "itFOCUS", "itSKIP",
|
|
||||||
"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,5 +0,0 @@
|
|||||||
return {
|
|
||||||
pollingRate = 0.3,
|
|
||||||
version = "v0.2.3",
|
|
||||||
dev = false,
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
return function()
|
|
||||||
local Config = require(script.Parent.Config)
|
|
||||||
|
|
||||||
it("should have 'dev' disabled", function()
|
|
||||||
expect(Config.dev).to.equal(false)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
|
||||||
|
|
||||||
local HTTP_DEBUG = false
|
|
||||||
|
|
||||||
local Promise = require(script.Parent.Promise)
|
|
||||||
local HttpError = require(script.Parent.HttpError)
|
|
||||||
local HttpResponse = require(script.Parent.HttpResponse)
|
|
||||||
|
|
||||||
local function dprint(...)
|
|
||||||
if HTTP_DEBUG then
|
|
||||||
print(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Http = {}
|
|
||||||
Http.__index = Http
|
|
||||||
|
|
||||||
function Http.new(baseUrl)
|
|
||||||
assert(type(baseUrl) == "string", "Http.new needs a baseUrl!")
|
|
||||||
|
|
||||||
local http = {
|
|
||||||
baseUrl = baseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(http, Http)
|
|
||||||
|
|
||||||
return http
|
|
||||||
end
|
|
||||||
|
|
||||||
function Http:get(endpoint)
|
|
||||||
dprint("\nGET", endpoint)
|
|
||||||
return Promise.new(function(resolve, reject)
|
|
||||||
spawn(function()
|
|
||||||
local ok, result = pcall(function()
|
|
||||||
return HttpService:GetAsync(self.baseUrl .. endpoint, true)
|
|
||||||
end)
|
|
||||||
|
|
||||||
if ok then
|
|
||||||
dprint("\t", result, "\n")
|
|
||||||
resolve(HttpResponse.new(result))
|
|
||||||
else
|
|
||||||
reject(HttpError.fromErrorString(result))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Http:post(endpoint, body)
|
|
||||||
dprint("\nPOST", endpoint)
|
|
||||||
dprint(body)
|
|
||||||
return Promise.new(function(resolve, reject)
|
|
||||||
spawn(function()
|
|
||||||
local ok, result = pcall(function()
|
|
||||||
return HttpService:PostAsync(self.baseUrl .. endpoint, body)
|
|
||||||
end)
|
|
||||||
|
|
||||||
if ok then
|
|
||||||
dprint("\t", result, "\n")
|
|
||||||
resolve(HttpResponse.new(result))
|
|
||||||
else
|
|
||||||
reject(HttpError.fromErrorString(result))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Http
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
local HttpError = {}
|
|
||||||
HttpError.__index = HttpError
|
|
||||||
|
|
||||||
HttpError.Error = {
|
|
||||||
HttpNotEnabled = {
|
|
||||||
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
|
||||||
"Check your game settings, located in the 'Home' tab of Studio.",
|
|
||||||
},
|
|
||||||
ConnectFailed = {
|
|
||||||
message = "Rojo plugin couldn't connect to the Rojo server.\n" ..
|
|
||||||
"Make sure the server is running -- use 'Rojo serve' to run it!",
|
|
||||||
},
|
|
||||||
Unknown = {
|
|
||||||
message = "Rojo encountered an unknown error: {{message}}",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function HttpError.new(type, extraMessage)
|
|
||||||
extraMessage = extraMessage or ""
|
|
||||||
local message = type.message:gsub("{{message}}", extraMessage)
|
|
||||||
|
|
||||||
local err = {
|
|
||||||
type = type,
|
|
||||||
message = message,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(err, HttpError)
|
|
||||||
|
|
||||||
return err
|
|
||||||
end
|
|
||||||
|
|
||||||
function HttpError:__tostring()
|
|
||||||
return self.message
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
This method shouldn't have to exist. Ugh.
|
|
||||||
]]
|
|
||||||
function HttpError.fromErrorString(err)
|
|
||||||
err = err:lower()
|
|
||||||
|
|
||||||
if err:find("^http requests are not enabled") then
|
|
||||||
return HttpError.new(HttpError.Error.HttpNotEnabled)
|
|
||||||
end
|
|
||||||
|
|
||||||
if err:find("^curl error") then
|
|
||||||
return HttpError.new(HttpError.Error.ConnectFailed)
|
|
||||||
end
|
|
||||||
|
|
||||||
return HttpError.new(HttpError.Error.Unknown, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
function HttpError:report()
|
|
||||||
warn(self.message)
|
|
||||||
end
|
|
||||||
|
|
||||||
return HttpError
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
|
||||||
|
|
||||||
local HttpResponse = {}
|
|
||||||
HttpResponse.__index = HttpResponse
|
|
||||||
|
|
||||||
function HttpResponse.new(body)
|
|
||||||
local response = {
|
|
||||||
body = body,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(response, HttpResponse)
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
function HttpResponse:json()
|
|
||||||
return HttpService:JSONDecode(self.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
return HttpResponse
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
if not plugin then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local Plugin = require(script.Parent.Plugin)
|
|
||||||
local Config = require(script.Parent.Config)
|
|
||||||
|
|
||||||
local function main()
|
|
||||||
local pluginInstance = Plugin.new()
|
|
||||||
|
|
||||||
local displayedVersion = Config.dev and "DEV" or Config.version
|
|
||||||
|
|
||||||
local toolbar = plugin:CreateToolbar("Rojo Plugin " .. displayedVersion)
|
|
||||||
|
|
||||||
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
|
|
||||||
.Click:Connect(function()
|
|
||||||
pluginInstance:connect()
|
|
||||||
:catch(function(err)
|
|
||||||
warn(err)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
|
|
||||||
.Click:Connect(function()
|
|
||||||
pluginInstance:syncIn()
|
|
||||||
:catch(function(err)
|
|
||||||
warn(err)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
|
|
||||||
.Click:Connect(function()
|
|
||||||
spawn(function()
|
|
||||||
pluginInstance:togglePolling()
|
|
||||||
:catch(function(err)
|
|
||||||
warn(err)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
main()
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
local Config = require(script.Parent.Config)
|
|
||||||
local Http = require(script.Parent.Http)
|
|
||||||
local Server = require(script.Parent.Server)
|
|
||||||
local Promise = require(script.Parent.Promise)
|
|
||||||
local Reconciler = require(script.Parent.Reconciler)
|
|
||||||
|
|
||||||
local function collectMatch(source, pattern)
|
|
||||||
local result = {}
|
|
||||||
|
|
||||||
for match in source:gmatch(pattern) do
|
|
||||||
table.insert(result, match)
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
local Plugin = {}
|
|
||||||
Plugin.__index = Plugin
|
|
||||||
|
|
||||||
function Plugin.new()
|
|
||||||
local address = "localhost"
|
|
||||||
local port = Config.dev and 8001 or 8000
|
|
||||||
|
|
||||||
local remote = ("http://%s:%d"):format(address, port)
|
|
||||||
|
|
||||||
local foop = {
|
|
||||||
_http = Http.new(remote),
|
|
||||||
_server = nil,
|
|
||||||
_polling = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(foop, Plugin)
|
|
||||||
|
|
||||||
do
|
|
||||||
local screenGui = Instance.new("ScreenGui")
|
|
||||||
screenGui.Name = "Rojo UI"
|
|
||||||
screenGui.Parent = game.CoreGui
|
|
||||||
screenGui.DisplayOrder = -1
|
|
||||||
screenGui.Enabled = false
|
|
||||||
|
|
||||||
local label = Instance.new("TextLabel")
|
|
||||||
label.Font = Enum.Font.SourceSans
|
|
||||||
label.TextSize = 20
|
|
||||||
label.Text = "Rojo polling..."
|
|
||||||
label.BackgroundColor3 = Color3.fromRGB(31, 31, 31)
|
|
||||||
label.BackgroundTransparency = 0.5
|
|
||||||
label.BorderSizePixel = 0
|
|
||||||
label.TextColor3 = Color3.new(1, 1, 1)
|
|
||||||
label.Size = UDim2.new(0, 120, 0, 28)
|
|
||||||
label.Position = UDim2.new(0, 0, 0, 0)
|
|
||||||
label.Parent = screenGui
|
|
||||||
|
|
||||||
foop._label = screenGui
|
|
||||||
end
|
|
||||||
|
|
||||||
return foop
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:server()
|
|
||||||
if not self._server then
|
|
||||||
self._server = Server.connect(self._http)
|
|
||||||
:catch(function(err)
|
|
||||||
self._server = nil
|
|
||||||
return Promise.reject(err)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self._server
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:connect()
|
|
||||||
print("Testing connection...")
|
|
||||||
|
|
||||||
return self:server()
|
|
||||||
:andThen(function(server)
|
|
||||||
return server:getInfo()
|
|
||||||
end)
|
|
||||||
:andThen(function(result)
|
|
||||||
print("Server found!")
|
|
||||||
print("Protocol version:", result.protocolVersion)
|
|
||||||
print("Server version:", result.serverVersion)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:togglePolling()
|
|
||||||
if self._polling then
|
|
||||||
self:stopPolling()
|
|
||||||
|
|
||||||
return Promise.resolve(nil)
|
|
||||||
else
|
|
||||||
return self:startPolling()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:stopPolling()
|
|
||||||
if not self._polling then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Stopped polling.")
|
|
||||||
|
|
||||||
self._polling = false
|
|
||||||
self._label.Enabled = false
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:_pull(server, project, routes)
|
|
||||||
local items = server:read(routes):await()
|
|
||||||
|
|
||||||
for index = 1, #routes do
|
|
||||||
local route = routes[index]
|
|
||||||
local partitionName = route[1]
|
|
||||||
local partition = project.partitions[partitionName]
|
|
||||||
local item = items[index]
|
|
||||||
|
|
||||||
local fullRoute = collectMatch(partition.target, "[^.]+")
|
|
||||||
for i = 2, #route do
|
|
||||||
table.insert(fullRoute, routes[index][i])
|
|
||||||
end
|
|
||||||
|
|
||||||
Reconciler.reconcileRoute(fullRoute, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:startPolling()
|
|
||||||
if self._polling then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Starting to poll...")
|
|
||||||
|
|
||||||
self._polling = true
|
|
||||||
self._label.Enabled = true
|
|
||||||
|
|
||||||
return self:server()
|
|
||||||
:andThen(function(server)
|
|
||||||
self:syncIn():await()
|
|
||||||
|
|
||||||
local project = server:getInfo():await().project
|
|
||||||
|
|
||||||
while self._polling do
|
|
||||||
local changes = server:getChanges():await()
|
|
||||||
|
|
||||||
local routes = {}
|
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
|
||||||
table.insert(routes, change.route)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:_pull(server, project, routes)
|
|
||||||
|
|
||||||
wait(Config.pollingRate)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
:catch(function()
|
|
||||||
self:stopPolling()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Plugin:syncIn()
|
|
||||||
print("Syncing from server...")
|
|
||||||
|
|
||||||
return self:server()
|
|
||||||
:andThen(function(server)
|
|
||||||
local project = server:getInfo():await().project
|
|
||||||
|
|
||||||
local routes = {}
|
|
||||||
|
|
||||||
for name in pairs(project.partitions) do
|
|
||||||
table.insert(routes, {name})
|
|
||||||
end
|
|
||||||
|
|
||||||
self:_pull(server, project, routes)
|
|
||||||
|
|
||||||
print("Sync successful!")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Plugin
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
--[[
|
|
||||||
An implementation of Promises similar to Promise/A+.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local PROMISE_DEBUG = false
|
|
||||||
|
|
||||||
-- If promise debugging is on, use a version of pcall that warns on failure.
|
|
||||||
-- This is useful for finding errors that happen within Promise itself.
|
|
||||||
local wpcall
|
|
||||||
if PROMISE_DEBUG then
|
|
||||||
wpcall = function(f, ...)
|
|
||||||
local result = { pcall(f, ...) }
|
|
||||||
|
|
||||||
if not result[1] then
|
|
||||||
warn(result[2])
|
|
||||||
end
|
|
||||||
|
|
||||||
return unpack(result)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
wpcall = pcall
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Creates a function that invokes a callback with correct error handling and
|
|
||||||
resolution mechanisms.
|
|
||||||
]]
|
|
||||||
local function createAdvancer(callback, resolve, reject)
|
|
||||||
return function(...)
|
|
||||||
local result = { wpcall(callback, ...) }
|
|
||||||
local ok = table.remove(result, 1)
|
|
||||||
|
|
||||||
if ok then
|
|
||||||
resolve(unpack(result))
|
|
||||||
else
|
|
||||||
reject(unpack(result))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isEmpty(t)
|
|
||||||
return next(t) == nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local Promise = {}
|
|
||||||
Promise.__index = Promise
|
|
||||||
|
|
||||||
Promise.Status = {
|
|
||||||
Started = "Started",
|
|
||||||
Resolved = "Resolved",
|
|
||||||
Rejected = "Rejected",
|
|
||||||
}
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Constructs a new Promise with the given initializing callback.
|
|
||||||
|
|
||||||
This is generally only called when directly wrapping a non-promise API into
|
|
||||||
a promise-based version.
|
|
||||||
|
|
||||||
The callback will receive 'resolve' and 'reject' methods, used to start
|
|
||||||
invoking the promise chain.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
local function get(url)
|
|
||||||
return Promise.new(function(resolve, reject)
|
|
||||||
spawn(function()
|
|
||||||
resolve(HttpService:GetAsync(url))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
get("https://google.com")
|
|
||||||
:andThen(function(stuff)
|
|
||||||
print("Got some stuff!", stuff)
|
|
||||||
end)
|
|
||||||
]]
|
|
||||||
function Promise.new(callback)
|
|
||||||
local promise = {
|
|
||||||
-- Used to locate where a promise was created
|
|
||||||
_source = debug.traceback(),
|
|
||||||
|
|
||||||
-- A tag to identify us as a promise
|
|
||||||
_type = "Promise",
|
|
||||||
|
|
||||||
_status = Promise.Status.Started,
|
|
||||||
|
|
||||||
-- A table containing a list of all results, whether success or failure.
|
|
||||||
-- Only valid if _status is set to something besides Started
|
|
||||||
_value = nil,
|
|
||||||
|
|
||||||
-- If an error occurs with no observers, this will be set.
|
|
||||||
_unhandledRejection = false,
|
|
||||||
|
|
||||||
-- Queues representing functions we should invoke when we update!
|
|
||||||
_queuedResolve = {},
|
|
||||||
_queuedReject = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(promise, Promise)
|
|
||||||
|
|
||||||
local function resolve(...)
|
|
||||||
promise:_resolve(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function reject(...)
|
|
||||||
promise:_reject(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, err = wpcall(callback, resolve, reject)
|
|
||||||
|
|
||||||
if not ok and promise._status == Promise.Status.Started then
|
|
||||||
reject(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
return promise
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Create a promise that represents the immediately resolved value.
|
|
||||||
]]
|
|
||||||
function Promise.resolve(value)
|
|
||||||
return Promise.new(function(resolve)
|
|
||||||
resolve(value)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Create a promise that represents the immediately rejected value.
|
|
||||||
]]
|
|
||||||
function Promise.reject(value)
|
|
||||||
return Promise.new(function(_, reject)
|
|
||||||
reject(value)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Returns a new promise that:
|
|
||||||
* is resolved when all input promises resolve
|
|
||||||
* is rejected if ANY input promises reject
|
|
||||||
]]
|
|
||||||
function Promise.all(...)
|
|
||||||
error("unimplemented", 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Is the given object a Promise instance?
|
|
||||||
]]
|
|
||||||
function Promise.is(object)
|
|
||||||
if type(object) ~= "table" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return object._type == "Promise"
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Creates a new promise that receives the result of this promise.
|
|
||||||
|
|
||||||
The given callbacks are invoked depending on that result.
|
|
||||||
]]
|
|
||||||
function Promise:andThen(successHandler, failureHandler)
|
|
||||||
self._unhandledRejection = false
|
|
||||||
|
|
||||||
-- Create a new promise to follow this part of the chain
|
|
||||||
return Promise.new(function(resolve, reject)
|
|
||||||
-- Our default callbacks just pass values onto the next promise.
|
|
||||||
-- This lets success and failure cascade correctly!
|
|
||||||
|
|
||||||
local successCallback = resolve
|
|
||||||
if successHandler then
|
|
||||||
successCallback = createAdvancer(successHandler, resolve, reject)
|
|
||||||
end
|
|
||||||
|
|
||||||
local failureCallback = reject
|
|
||||||
if failureHandler then
|
|
||||||
failureCallback = createAdvancer(failureHandler, resolve, reject)
|
|
||||||
end
|
|
||||||
|
|
||||||
if self._status == Promise.Status.Started then
|
|
||||||
-- If we haven't resolved yet, put ourselves into the queue
|
|
||||||
table.insert(self._queuedResolve, successCallback)
|
|
||||||
table.insert(self._queuedReject, failureCallback)
|
|
||||||
elseif self._status == Promise.Status.Resolved then
|
|
||||||
-- This promise has already resolved! Trigger success immediately.
|
|
||||||
successCallback(unpack(self._value))
|
|
||||||
elseif self._status == Promise.Status.Rejected then
|
|
||||||
-- This promise died a terrible death! Trigger failure immediately.
|
|
||||||
failureCallback(unpack(self._value))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Used to catch any errors that may have occurred in the promise.
|
|
||||||
]]
|
|
||||||
function Promise:catch(failureCallback)
|
|
||||||
return self:andThen(nil, failureCallback)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Yield until the promise is completed.
|
|
||||||
|
|
||||||
This matches the execution model of normal Roblox functions.
|
|
||||||
]]
|
|
||||||
function Promise:await()
|
|
||||||
self._unhandledRejection = false
|
|
||||||
|
|
||||||
if self._status == Promise.Status.Started then
|
|
||||||
local result
|
|
||||||
local bindable = Instance.new("BindableEvent")
|
|
||||||
|
|
||||||
self:andThen(function(...)
|
|
||||||
result = {...}
|
|
||||||
bindable:Fire(true)
|
|
||||||
end, function(...)
|
|
||||||
result = {...}
|
|
||||||
bindable:Fire(false)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local ok = bindable.Event:Wait()
|
|
||||||
bindable:Destroy()
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
error(tostring(result[1]), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return unpack(result)
|
|
||||||
elseif self._status == Promise.Status.Resolved then
|
|
||||||
return unpack(self._value)
|
|
||||||
elseif self._status == Promise.Status.Rejected then
|
|
||||||
error(tostring(self._value[1]), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Promise:_resolve(...)
|
|
||||||
if self._status ~= Promise.Status.Started then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If the resolved value was a Promise, we chain onto it!
|
|
||||||
if Promise.is((...)) then
|
|
||||||
-- Without this warning, arguments sometimes mysteriously disappear
|
|
||||||
if select("#", ...) > 1 then
|
|
||||||
local message = (
|
|
||||||
"When returning a Promise from andThen, extra arguments are " ..
|
|
||||||
"discarded! See:\n\n%s"
|
|
||||||
):format(
|
|
||||||
self._source
|
|
||||||
)
|
|
||||||
warn(message)
|
|
||||||
end
|
|
||||||
|
|
||||||
(...):andThen(function(...)
|
|
||||||
self:_resolve(...)
|
|
||||||
end, function(...)
|
|
||||||
self:_reject(...)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
self._status = Promise.Status.Resolved
|
|
||||||
self._value = {...}
|
|
||||||
|
|
||||||
-- We assume that these callbacks will not throw errors.
|
|
||||||
for _, callback in ipairs(self._queuedResolve) do
|
|
||||||
callback(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Promise:_reject(...)
|
|
||||||
if self._status ~= Promise.Status.Started then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
self._status = Promise.Status.Rejected
|
|
||||||
self._value = {...}
|
|
||||||
|
|
||||||
-- If there are any rejection handlers, call those!
|
|
||||||
if not isEmpty(self._queuedReject) then
|
|
||||||
-- We assume that these callbacks will not throw errors.
|
|
||||||
for _, callback in ipairs(self._queuedReject) do
|
|
||||||
callback(...)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- At this point, no one was able to observe the error.
|
|
||||||
-- An error handler might still be attached if the error occurred
|
|
||||||
-- synchronously. We'll wait one tick, and if there are still no
|
|
||||||
-- observers, then we should put a message in the console.
|
|
||||||
|
|
||||||
self._unhandledRejection = true
|
|
||||||
local err = tostring((...))
|
|
||||||
|
|
||||||
spawn(function()
|
|
||||||
-- Someone observed the error, hooray!
|
|
||||||
if not self._unhandledRejection then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build a reasonable message
|
|
||||||
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
|
||||||
err,
|
|
||||||
self._source
|
|
||||||
)
|
|
||||||
warn(message)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return Promise
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
return function()
|
|
||||||
local Promise = require(script.Parent.Promise)
|
|
||||||
|
|
||||||
describe("Promise.new", function()
|
|
||||||
it("should instantiate with a callback", function()
|
|
||||||
local promise = Promise.new(function() end)
|
|
||||||
|
|
||||||
expect(promise).to.be.ok()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("should invoke the given callback with resolve and reject", function()
|
|
||||||
local callCount = 0
|
|
||||||
local resolveArg
|
|
||||||
local rejectArg
|
|
||||||
|
|
||||||
local promise = Promise.new(function(resolve, reject)
|
|
||||||
callCount = callCount + 1
|
|
||||||
resolveArg = resolve
|
|
||||||
rejectArg = reject
|
|
||||||
end)
|
|
||||||
|
|
||||||
expect(promise).to.be.ok()
|
|
||||||
|
|
||||||
expect(callCount).to.equal(1)
|
|
||||||
expect(resolveArg).to.be.a("function")
|
|
||||||
expect(rejectArg).to.be.a("function")
|
|
||||||
expect(promise._status).to.equal(Promise.Status.Started)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("should resolve promises on resolve()", function()
|
|
||||||
local callCount = 0
|
|
||||||
|
|
||||||
local promise = Promise.new(function(resolve)
|
|
||||||
callCount = callCount + 1
|
|
||||||
resolve()
|
|
||||||
end)
|
|
||||||
|
|
||||||
expect(promise).to.be.ok()
|
|
||||||
expect(callCount).to.equal(1)
|
|
||||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("should reject promises on reject()", function()
|
|
||||||
local callCount = 0
|
|
||||||
|
|
||||||
local promise = Promise.new(function(resolve, reject)
|
|
||||||
callCount = callCount + 1
|
|
||||||
reject()
|
|
||||||
end)
|
|
||||||
|
|
||||||
expect(promise).to.be.ok()
|
|
||||||
expect(callCount).to.equal(1)
|
|
||||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("should reject on error in callback", function()
|
|
||||||
local callCount = 0
|
|
||||||
|
|
||||||
local promise = Promise.new(function()
|
|
||||||
callCount = callCount + 1
|
|
||||||
error("hahah")
|
|
||||||
end)
|
|
||||||
|
|
||||||
expect(promise).to.be.ok()
|
|
||||||
expect(callCount).to.equal(1)
|
|
||||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
|
||||||
expect(promise._value[1]:find("hahah")).to.be.ok()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
local Reconciler = {}
|
|
||||||
|
|
||||||
--[[
|
|
||||||
The set of file names that should pass as init files
|
|
||||||
These files usurp their parents.
|
|
||||||
]]
|
|
||||||
local initNames = {
|
|
||||||
["init.lua"] = true,
|
|
||||||
["init.server.lua"] = true,
|
|
||||||
["init.client.lua"] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function isInit(item, itemFileName)
|
|
||||||
if item and item.type == "dir" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
return initNames[itemFileName] or false
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Determines if the given VFS item has an init file. Yields information about
|
|
||||||
the file.
|
|
||||||
]]
|
|
||||||
local function findInit(item)
|
|
||||||
if item.type ~= "dir" then
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
|
||||||
if isInit(childItem, childFileName) then
|
|
||||||
return childItem, childFileName
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Given a VFS item, returns a Name and ClassName for a corresponding Roblox
|
|
||||||
instance.
|
|
||||||
|
|
||||||
Doesn't take into account init files.
|
|
||||||
]]
|
|
||||||
local function itemToName(item, fileName)
|
|
||||||
if item and item.type == "dir" then
|
|
||||||
return fileName, "Folder"
|
|
||||||
elseif item and item.type == "file" or not item then
|
|
||||||
if fileName:find("%.server%.lua$") then
|
|
||||||
return fileName:match("^(.-)%.server%.lua$"), "Script"
|
|
||||||
elseif fileName:find("%.client%.lua$") then
|
|
||||||
return fileName:match("^(.-)%.client%.lua$"), "LocalScript"
|
|
||||||
elseif fileName:find("%.lua") then
|
|
||||||
return fileName:match("^(.-)%.lua$"), "ModuleScript"
|
|
||||||
else
|
|
||||||
return fileName, "StringValue"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Given a VFS item, assigns all relevant values (except Name!) to a Roblox
|
|
||||||
instance.
|
|
||||||
]]
|
|
||||||
local function setValues(rbx, item, fileName)
|
|
||||||
local _, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
if className:find("Script") then
|
|
||||||
rbx.Source = item.contents
|
|
||||||
else
|
|
||||||
rbx.Value = item.contents
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Reconciler._reifyShallow(item, fileName)
|
|
||||||
if item.type == "dir" then
|
|
||||||
local initItem, initFileName = findInit(item)
|
|
||||||
|
|
||||||
if initItem then
|
|
||||||
local rbx = Reconciler._reify(initItem, initFileName)
|
|
||||||
rbx.Name = fileName
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
local rbx = Instance.new("Folder")
|
|
||||||
rbx.Name = fileName
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
end
|
|
||||||
elseif item.type == "file" then
|
|
||||||
local objectName, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
local rbx = Instance.new(className)
|
|
||||||
rbx.Name = objectName
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Construct a new Roblox instance tree that corresponds to the given VFS item.
|
|
||||||
]]
|
|
||||||
function Reconciler._reify(item, fileName, parent)
|
|
||||||
local rbx = Reconciler._reifyShallow(item, fileName)
|
|
||||||
|
|
||||||
if item.type == "dir" then
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
|
||||||
if not isInit(childItem, childFileName) then
|
|
||||||
local childRbx = Reconciler._reify(childItem, childFileName)
|
|
||||||
childRbx.Parent = rbx
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
rbx.Parent = parent
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
end
|
|
||||||
|
|
||||||
function Reconciler.reconcile(rbx, item, fileName, parent)
|
|
||||||
-- Item was deleted!
|
|
||||||
if not item then
|
|
||||||
if isInit(item, fileName) then
|
|
||||||
if not parent then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Un-usurp parent!
|
|
||||||
local newParent = Instance.new("Folder")
|
|
||||||
newParent.Name = parent.Name
|
|
||||||
|
|
||||||
for _, child in ipairs(parent:GetChildren()) do
|
|
||||||
child.Parent = newParent
|
|
||||||
end
|
|
||||||
|
|
||||||
newParent.Parent = parent.Parent
|
|
||||||
parent:Destroy()
|
|
||||||
|
|
||||||
return
|
|
||||||
else
|
|
||||||
if rbx then
|
|
||||||
rbx:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if item.type == "dir" then
|
|
||||||
-- Folder was created!
|
|
||||||
if not rbx then
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
local initItem, initFileName = findInit(item)
|
|
||||||
|
|
||||||
if initItem then
|
|
||||||
local _, initClassName = itemToName(initItem, initFileName)
|
|
||||||
|
|
||||||
if rbx.ClassName == initClassName then
|
|
||||||
setValues(rbx, initItem, initFileName)
|
|
||||||
else
|
|
||||||
rbx:Destroy()
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if rbx.ClassName ~= "Folder" then
|
|
||||||
-- Certain objects (services) can't be destroyed.
|
|
||||||
-- If we target one of these, leave it alone!
|
|
||||||
local ok = pcall(rbx.Destroy, rbx)
|
|
||||||
|
|
||||||
if ok then
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local visitedChildren = {}
|
|
||||||
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
|
||||||
if not isInit(childItem, childFileName) then
|
|
||||||
local childName = itemToName(childItem, childFileName)
|
|
||||||
|
|
||||||
visitedChildren[childName] = true
|
|
||||||
|
|
||||||
Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName, rbx)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, childRbx in ipairs(rbx:GetChildren()) do
|
|
||||||
-- Child was deleted!
|
|
||||||
if not visitedChildren[childRbx.Name] then
|
|
||||||
childRbx:Destroy()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
elseif item.type == "file" then
|
|
||||||
if isInit(item, fileName) then
|
|
||||||
-- Usurp our container!
|
|
||||||
local _, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
if parent.ClassName == className then
|
|
||||||
rbx = parent
|
|
||||||
else
|
|
||||||
rbx = Reconciler._reify(item, fileName, parent.Parent)
|
|
||||||
rbx.Name = parent.Name
|
|
||||||
|
|
||||||
for _, child in ipairs(parent:GetChildren()) do
|
|
||||||
child.Parent = rbx
|
|
||||||
end
|
|
||||||
|
|
||||||
parent:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
if not rbx then
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
local _, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
if rbx.ClassName ~= className then
|
|
||||||
rbx:Destroy()
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Reconciler.reconcileRoute(route, item)
|
|
||||||
local location = game
|
|
||||||
|
|
||||||
for i = 1, #route - 1 do
|
|
||||||
local piece = route[i]
|
|
||||||
local newLocation = location:FindFirstChild(piece)
|
|
||||||
|
|
||||||
if not newLocation then
|
|
||||||
newLocation = Instance.new("Folder")
|
|
||||||
newLocation.Name = piece
|
|
||||||
newLocation.Parent = location
|
|
||||||
end
|
|
||||||
|
|
||||||
location = newLocation
|
|
||||||
end
|
|
||||||
|
|
||||||
local fileName = route[#route]
|
|
||||||
|
|
||||||
local name = itemToName(item, fileName)
|
|
||||||
local rbx = location:FindFirstChild(name)
|
|
||||||
Reconciler.reconcile(rbx, item, fileName, location)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Reconciler
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
|
||||||
|
|
||||||
local Server = {}
|
|
||||||
Server.__index = Server
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Create a new Server using the given HTTP implementation and replacer.
|
|
||||||
|
|
||||||
If the context becomes invalid, `replacer` will be invoked with a new
|
|
||||||
context that should be suitable to replace this one.
|
|
||||||
|
|
||||||
Attempting to invoke methods on an invalid conext will throw errors!
|
|
||||||
]]
|
|
||||||
function Server.connect(http)
|
|
||||||
local context = {
|
|
||||||
http = http,
|
|
||||||
serverId = nil,
|
|
||||||
currentTime = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(context, Server)
|
|
||||||
|
|
||||||
return context:_start()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Server:_start()
|
|
||||||
return self:getInfo()
|
|
||||||
:andThen(function(response)
|
|
||||||
self.serverId = response.serverId
|
|
||||||
self.currentTime = response.currentTime
|
|
||||||
|
|
||||||
return self
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Server:getInfo()
|
|
||||||
return self.http:get("/")
|
|
||||||
:andThen(function(response)
|
|
||||||
response = response:json()
|
|
||||||
|
|
||||||
return response
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Server:read(paths)
|
|
||||||
local body = HttpService:JSONEncode(paths)
|
|
||||||
|
|
||||||
return self.http:post("/read", body)
|
|
||||||
:andThen(function(response)
|
|
||||||
response = response:json()
|
|
||||||
|
|
||||||
return response.items
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Server:getChanges()
|
|
||||||
local url = ("/changes/%f"):format(self.currentTime)
|
|
||||||
|
|
||||||
return self.http:get(url)
|
|
||||||
:andThen(function(response)
|
|
||||||
response = response:json()
|
|
||||||
|
|
||||||
self.currentTime = response.currentTime
|
|
||||||
|
|
||||||
return response.changes
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Server
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
return function()
|
|
||||||
local TestEZ = require(script.Parent.Parent.TestEZ)
|
|
||||||
TestEZ.TestBootstrap:run(script.Parent)
|
|
||||||
end
|
|
||||||
26
rojo.json
26
rojo.json
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "rojo",
|
|
||||||
"servePort": 8000,
|
|
||||||
"partitions": {
|
|
||||||
"plugin": {
|
|
||||||
"path": "plugin/src",
|
|
||||||
"target": "ReplicatedStorage.Rojo"
|
|
||||||
},
|
|
||||||
"modules/roact": {
|
|
||||||
"path": "modules/roact/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.Roact"
|
|
||||||
},
|
|
||||||
"modules/rodux": {
|
|
||||||
"path": "modules/rodux/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.Rodux"
|
|
||||||
},
|
|
||||||
"modules/roact-rodux": {
|
|
||||||
"path": "modules/roact-rodux/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.RoactRodux"
|
|
||||||
},
|
|
||||||
"modules/testez": {
|
|
||||||
"path": "modules/testez/lib",
|
|
||||||
"target": "ReplicatedStorage.TestEZ"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
spec.lua
69
spec.lua
@@ -1,69 +0,0 @@
|
|||||||
--[[
|
|
||||||
Loads our library and all of its dependencies, then runs tests using TestEZ.
|
|
||||||
]]
|
|
||||||
|
|
||||||
-- If you add any dependencies, add them to this table so they'll be loaded!
|
|
||||||
local LOAD_MODULES = {
|
|
||||||
{"plugin/src", "Plugin"},
|
|
||||||
{"modules/testez/lib", "TestEZ"},
|
|
||||||
}
|
|
||||||
|
|
||||||
-- This makes sure we can load Lemur and other libraries that depend on init.lua
|
|
||||||
package.path = package.path .. ";?/init.lua"
|
|
||||||
|
|
||||||
-- If this fails, make sure you've run `lua bin/install-dependencies.lua` first!
|
|
||||||
local lemur = require("modules.lemur")
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Collapses ModuleScripts named 'init' into their parent folders.
|
|
||||||
|
|
||||||
This is the same result as the collapsing mechanism from Rojo.
|
|
||||||
]]
|
|
||||||
local function collapse(root)
|
|
||||||
local init = root:FindFirstChild("init")
|
|
||||||
if init then
|
|
||||||
init.Name = root.Name
|
|
||||||
init.Parent = root.Parent
|
|
||||||
|
|
||||||
for _, child in ipairs(root:GetChildren()) do
|
|
||||||
child.Parent = init
|
|
||||||
end
|
|
||||||
|
|
||||||
root:Destroy()
|
|
||||||
root = init
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, child in ipairs(root:GetChildren()) do
|
|
||||||
if child:IsA("Folder") then
|
|
||||||
collapse(child)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return root
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create a virtual Roblox tree
|
|
||||||
local habitat = lemur.Habitat.new()
|
|
||||||
|
|
||||||
-- We'll put all of our library code and dependencies here
|
|
||||||
local Root = lemur.Instance.new("Folder")
|
|
||||||
Root.Name = "Root"
|
|
||||||
|
|
||||||
-- Load all of the modules specified above
|
|
||||||
for _, module in ipairs(LOAD_MODULES) do
|
|
||||||
local container = lemur.Instance.new("Folder", Root)
|
|
||||||
container.Name = module[2]
|
|
||||||
habitat:loadFromFs(module[1], container)
|
|
||||||
end
|
|
||||||
|
|
||||||
collapse(Root)
|
|
||||||
|
|
||||||
-- Load TestEZ and run our tests
|
|
||||||
local TestEZ = habitat:require(Root.TestEZ)
|
|
||||||
|
|
||||||
local results = TestEZ.TestBootstrap:run(Root.Plugin, TestEZ.Reporters.TextReporter)
|
|
||||||
|
|
||||||
-- Did something go wrong?
|
|
||||||
if results.failureCount > 0 then
|
|
||||||
os.exit(1)
|
|
||||||
end
|
|
||||||
Reference in New Issue
Block a user