mirror of
https://github.com/lune-org/docs.git
synced 2024-12-12 04:50:36 +00:00
Initial commit
This commit is contained in:
commit
4a9aa952ca
40 changed files with 12196 additions and 0 deletions
16
.editorconfig
Normal file
16
.editorconfig
Normal file
|
@ -0,0 +1,16 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
||||
[*.{yml,yaml,rs}]
|
||||
indent_style = space
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
15
.eslintrc
Normal file
15
.eslintrc
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"env": { "browser": true, "es2020": true },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
|
||||
"plugins": ["react-refresh"],
|
||||
"rules": {
|
||||
"semi": ["error", "never"],
|
||||
"react-refresh/only-export-components": "warn"
|
||||
}
|
||||
}
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
/.DS_Store
|
||||
/node_modules
|
||||
/temp
|
||||
/out
|
||||
/.next
|
||||
/.env
|
||||
|
||||
/**/.DS_Store
|
||||
/**/node_modules
|
||||
/**/temp
|
||||
/**/out
|
||||
/**/.next
|
||||
/**/.env
|
23
.justfile
Normal file
23
.justfile
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Installs development tooling for documentation
|
||||
install-dev-tools:
|
||||
aftman install
|
||||
npm install
|
||||
cargo binstall moonwave
|
||||
|
||||
# Extract documentation from the main repository using moonwave
|
||||
extract-documentation COMMIT="":
|
||||
lune download "{{COMMIT}}"
|
||||
lune extract
|
||||
lune generate
|
||||
|
||||
# Re-generates documentation from the main repository using moonwave
|
||||
generate-documentation:
|
||||
lune generate
|
||||
|
||||
# Builds and generates a static site directory
|
||||
build:
|
||||
npm run build
|
||||
|
||||
# Starts a local development server for the docs site
|
||||
dev:
|
||||
npm run dev
|
56
.lune/download.luau
Normal file
56
.lune/download.luau
Normal file
|
@ -0,0 +1,56 @@
|
|||
local MAIN_REPOSITORY_URL = "https://github.com/filiptibell/lune"
|
||||
|
||||
local fs = require("@lune/fs")
|
||||
local net = require("@lune/net")
|
||||
local process = require("@lune/process")
|
||||
|
||||
-- Find the url we should download from, either from a
|
||||
-- given commit or by looking up the latest release tag
|
||||
local name
|
||||
local commit = process.args[1]
|
||||
if commit ~= nil and #commit > 0 then
|
||||
name = commit
|
||||
else
|
||||
print("Looking for the latest tag")
|
||||
local tagsResult = process.spawn("git", {
|
||||
"ls-remote",
|
||||
"--tags",
|
||||
"--sort=-v:refname",
|
||||
MAIN_REPOSITORY_URL,
|
||||
})
|
||||
assert(tagsResult.ok, tagsResult.stderr)
|
||||
|
||||
local lines = string.split(tagsResult.stdout, "\n")
|
||||
assert(#lines > 0, "No tags were found for the repository")
|
||||
|
||||
local latestTag = string.match(lines[1], "%s*refs/tags/(%S+)%s*$")
|
||||
assert(latestTag ~= nil, "Failed to find latest tag for repository")
|
||||
name = latestTag
|
||||
end
|
||||
|
||||
-- Remove any previously downloaded repository folder
|
||||
if fs.isDir("temp/repository") then
|
||||
fs.removeDir("temp/repository")
|
||||
end
|
||||
|
||||
-- Download the repository using the given tag or commit, unzip it, remove zip
|
||||
print(`Downloading '{name}'`)
|
||||
local downloaded = net.request(`{MAIN_REPOSITORY_URL}/archive/{name}.zip`)
|
||||
assert(downloaded.ok, downloaded.statusMessage)
|
||||
|
||||
fs.writeFile("temp/download.zip", downloaded.body)
|
||||
|
||||
local unzipResult = process.spawn("unzip", {
|
||||
"temp/download.zip",
|
||||
"-d",
|
||||
"temp/download",
|
||||
})
|
||||
assert(unzipResult.ok, unzipResult.stderr)
|
||||
|
||||
fs.removeFile("temp/download.zip")
|
||||
|
||||
-- Move the repository folder we just downloaded, which we do not know
|
||||
-- the name of, but we know there is only one, into a known location
|
||||
local repoFolderName = fs.readDir("temp/download")[1]
|
||||
fs.move("temp/download/" .. repoFolderName, "temp/repository")
|
||||
fs.removeDir("temp/download")
|
27
.lune/extract.luau
Normal file
27
.lune/extract.luau
Normal file
|
@ -0,0 +1,27 @@
|
|||
local fs = require("@lune/fs")
|
||||
local process = require("@lune/process")
|
||||
local serde = require("@lune/serde")
|
||||
|
||||
-- Make sure we have a repository folder downloaded
|
||||
assert(fs.isDir("temp/repository"), "Missing downloaded repository folder")
|
||||
|
||||
-- Look for where type definitions are stored
|
||||
local sourceDir
|
||||
if fs.isDir("temp/repository/docs/typedefs") then
|
||||
sourceDir = "temp/repository/docs/typedefs"
|
||||
elseif fs.isDir("temp/repository/typedefs") then
|
||||
sourceDir = "temp/repository/typedefs"
|
||||
elseif fs.isDir("temp/repository/types") then
|
||||
sourceDir = "temp/repository/types"
|
||||
else
|
||||
error("Failed to find typedefs folder in repository")
|
||||
end
|
||||
|
||||
-- Run moonwave to parse typedef files and extract documentation, write to file
|
||||
local moonwaveResult = process.spawn("moonwave-extractor", { "extract", sourceDir })
|
||||
assert(moonwaveResult.ok and #moonwaveResult.stderr <= 0, moonwaveResult.stderr)
|
||||
fs.writeFile("temp/moonwave.json", moonwaveResult.stdout)
|
||||
|
||||
-- Let the user know how many typedefs we have extracted
|
||||
local arr = serde.decode("json", moonwaveResult.stdout)
|
||||
print("Extracted", #arr, "type definitions")
|
53
.lune/generate.luau
Normal file
53
.lune/generate.luau
Normal file
|
@ -0,0 +1,53 @@
|
|||
local fs = require("@lune/fs")
|
||||
local process = require("@lune/process")
|
||||
local serde = require("@lune/serde")
|
||||
|
||||
local moonwave = require("./moonwave")
|
||||
local writeMarkdown = require("./writer")
|
||||
|
||||
-- Parse the newly extracted moonwave file
|
||||
local typedefsFile = fs.readFile("temp/moonwave.json")
|
||||
local items: { moonwave.Item } = serde.decode("json", typedefsFile)
|
||||
|
||||
-- Generate markdown for all of the libraries
|
||||
local generatedFiles = {}
|
||||
for _, item in items do
|
||||
local file = item.source.path
|
||||
local name = string.match(file, "(.+)%.luau")
|
||||
assert(name ~= nil, "Failed to remove luau suffix from file name")
|
||||
table.insert(generatedFiles, {
|
||||
displayName = item.name,
|
||||
name = string.lower(name),
|
||||
content = writeMarkdown(item),
|
||||
})
|
||||
end
|
||||
|
||||
-- Remove any old files, generate new ones
|
||||
if fs.isDir("pages/api-reference") then
|
||||
fs.removeDir("pages/api-reference")
|
||||
end
|
||||
fs.writeDir("pages/api-reference")
|
||||
for _, file in generatedFiles do
|
||||
fs.writeFile(`pages/api-reference/{file.name}.md`, file.content)
|
||||
end
|
||||
|
||||
-- Also generate a meta file to make the sidebar look nicer, note that
|
||||
-- we generate it manually instead of serializing as json because that
|
||||
-- would not preserve order and the sidebar is order-sensitive
|
||||
local meta = "{\n"
|
||||
for index, file in generatedFiles do
|
||||
meta ..= ` "{file.name}": "{file.displayName}"`
|
||||
if index == #generatedFiles then
|
||||
meta ..= "\n}"
|
||||
else
|
||||
meta ..= ",\n"
|
||||
end
|
||||
end
|
||||
fs.writeFile(`pages/api-reference/_meta.json`, meta)
|
||||
|
||||
-- Finally, call out to prettier to ensure that our
|
||||
-- generated markdown files are formatted properly
|
||||
process.spawn("prettier", {
|
||||
"--write",
|
||||
"pages/api-reference/",
|
||||
})
|
50
.lune/moonwave.luau
Normal file
50
.lune/moonwave.luau
Normal file
|
@ -0,0 +1,50 @@
|
|||
export type Source = {
|
||||
path: string,
|
||||
line: number,
|
||||
}
|
||||
|
||||
export type FunctionParam = {
|
||||
name: string,
|
||||
desc: string,
|
||||
lua_type: string,
|
||||
}
|
||||
|
||||
export type FunctionReturn = {
|
||||
desc: string,
|
||||
lua_type: string,
|
||||
}
|
||||
|
||||
export type Function = {
|
||||
name: string,
|
||||
desc: string,
|
||||
params: { FunctionParam },
|
||||
returns: { FunctionReturn },
|
||||
function_type: string,
|
||||
tags: { string },
|
||||
source: Source,
|
||||
}
|
||||
|
||||
export type Property = {
|
||||
name: string,
|
||||
desc: string,
|
||||
lua_type: string,
|
||||
tags: { string },
|
||||
source: Source,
|
||||
}
|
||||
|
||||
export type Type = {
|
||||
name: string,
|
||||
desc: string,
|
||||
source: Source,
|
||||
}
|
||||
|
||||
export type Item = {
|
||||
name: string,
|
||||
desc: string,
|
||||
functions: { Function },
|
||||
properties: { Property },
|
||||
types: { Type },
|
||||
source: Source,
|
||||
}
|
||||
|
||||
return {}
|
104
.lune/writer.luau
Normal file
104
.lune/writer.luau
Normal file
|
@ -0,0 +1,104 @@
|
|||
local moonwave = require("./moonwave")
|
||||
|
||||
local buffer = {}
|
||||
local function write(text: string)
|
||||
table.insert(buffer, text)
|
||||
end
|
||||
|
||||
local function writeDesc(desc: string)
|
||||
desc = string.gsub(desc, "###", "####")
|
||||
write(`{desc}\n\n`)
|
||||
end
|
||||
|
||||
local function writeTypeAndDesc(typ: string, desc: string, inline: boolean)
|
||||
if #typ > 0 and #desc <= 0 then
|
||||
-- HACK: Got empty desc but we have a type, this is a doc comment not a type
|
||||
if inline then
|
||||
write(" ")
|
||||
end
|
||||
write(typ)
|
||||
if not inline then
|
||||
write("\n\n")
|
||||
end
|
||||
elseif #desc > 0 then
|
||||
if #typ > 0 then
|
||||
if inline then
|
||||
write(" ")
|
||||
end
|
||||
write("`" .. typ .. "`")
|
||||
if not inline then
|
||||
write("\n\n")
|
||||
end
|
||||
end
|
||||
if inline then
|
||||
write(" ")
|
||||
end
|
||||
write(desc)
|
||||
if not inline then
|
||||
write("\n\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function writeParams(params: { moonwave.FunctionParam })
|
||||
if #params > 0 then
|
||||
write(`#### Parameters\n\n`)
|
||||
for _, param in params do
|
||||
write(`- \`{param.name}\``)
|
||||
writeTypeAndDesc(param.lua_type, param.desc, true)
|
||||
write("\n\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function writeReturns(returns: { moonwave.FunctionReturn })
|
||||
if #returns > 0 then
|
||||
write(`#### Returns\n\n`)
|
||||
for _, ret in returns do
|
||||
write(`- `)
|
||||
writeTypeAndDesc(ret.lua_type, ret.desc, true)
|
||||
write("\n\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function writeMarkdown(item: moonwave.Item)
|
||||
write(`# {item.name}\n\n`)
|
||||
writeDesc(item.desc)
|
||||
|
||||
if #item.properties > 0 then
|
||||
write(`## Properties\n\n`)
|
||||
for _, prop in item.properties do
|
||||
write(`### {prop.name}\n\n`)
|
||||
writeTypeAndDesc(prop.lua_type, prop.desc, false)
|
||||
write("\n\n")
|
||||
write(`---\n\n`)
|
||||
end
|
||||
end
|
||||
|
||||
if #item.functions > 0 then
|
||||
write(`## Functions\n\n`)
|
||||
for _, fn in item.functions do
|
||||
write(`### {fn.name}\n\n`)
|
||||
writeDesc(fn.desc)
|
||||
writeParams(fn.params)
|
||||
writeReturns(fn.returns)
|
||||
write(`---\n\n`)
|
||||
end
|
||||
end
|
||||
|
||||
if #item.types > 0 then
|
||||
write(`## Types\n\n`)
|
||||
for _, typ in item.types do
|
||||
write(`### {typ.name}\n\n`)
|
||||
writeDesc(typ.desc)
|
||||
write(`---\n\n`)
|
||||
end
|
||||
end
|
||||
|
||||
local result = table.concat(buffer, "")
|
||||
table.clear(buffer)
|
||||
return result
|
||||
end
|
||||
|
||||
return writeMarkdown
|
17
.prettierrc
Normal file
17
.prettierrc
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": true,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"luau-lsp.types.roblox": false,
|
||||
"luau-lsp.sourcemap.enabled": false,
|
||||
"luau-lsp.ignoreGlobs": ["temp/**"],
|
||||
"luau-lsp.require.mode": "relativeToFile",
|
||||
"luau-lsp.require.directoryAliases": {
|
||||
"@lune/": "~/.lune/.typedefs/0.7.4/"
|
||||
}
|
||||
}
|
5
aftman.toml
Normal file
5
aftman.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[tools]
|
||||
just = "readysetplay/just@1.8.0"
|
||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.22.0"
|
||||
lune = "filiptibell/lune@0.7.4"
|
||||
stylua = "JohnnyMorganz/StyLua@0.18.0"
|
283
modules/remodel.luau
Normal file
283
modules/remodel.luau
Normal file
|
@ -0,0 +1,283 @@
|
|||
--!strict
|
||||
|
||||
local fs = require("@lune/fs")
|
||||
local net = require("@lune/net")
|
||||
local serde = require("@lune/serde")
|
||||
local process = require("@lune/process")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
export type LuneDataModel = roblox.DataModel
|
||||
export type LuneInstance = roblox.Instance
|
||||
|
||||
local function getAuthCookieWithFallbacks()
|
||||
local cookie = roblox.getAuthCookie()
|
||||
if cookie then
|
||||
return cookie
|
||||
end
|
||||
|
||||
local cookieFromEnv = process.env.REMODEL_AUTH
|
||||
if cookieFromEnv and #cookieFromEnv > 0 then
|
||||
return `.ROBLOSECURITY={cookieFromEnv}`
|
||||
end
|
||||
|
||||
for index, arg in process.args do
|
||||
if arg == "--auth" then
|
||||
local cookieFromArgs = process.args[index + 1]
|
||||
if cookieFromArgs and #cookieFromArgs > 0 then
|
||||
return `.ROBLOSECURITY={cookieFromArgs}`
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
error([[
|
||||
Failed to find ROBLOSECURITY cookie for authentication!
|
||||
Make sure you have logged into studio, or set the ROBLOSECURITY environment variable.
|
||||
]])
|
||||
end
|
||||
|
||||
local function downloadAssetId(assetId: number)
|
||||
-- 1. Try to find the auth cookie for the current user
|
||||
local cookie = getAuthCookieWithFallbacks()
|
||||
|
||||
-- 2. Send a request to the asset delivery API,
|
||||
-- which will respond with cdn download link(s)
|
||||
local assetApiResponse = net.request({
|
||||
url = `https://assetdelivery.roblox.com/v2/assetId/{assetId}`,
|
||||
headers = {
|
||||
Accept = "application/json",
|
||||
Cookie = cookie,
|
||||
},
|
||||
})
|
||||
if not assetApiResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to fetch asset download link for asset id %s!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(assetApiResponse.statusCode),
|
||||
tostring(assetApiResponse.statusMessage),
|
||||
tostring(assetApiResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 3. Make sure we got a valid response body
|
||||
local assetApiBody = serde.decode("json", assetApiResponse.body)
|
||||
if type(assetApiBody) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned an invalid response body!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
elseif type(assetApiBody.locations) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned an invalid response body!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 4. Grab the first asset download location - we only
|
||||
-- requested one in our query, so this will be correct
|
||||
local firstLocation = assetApiBody.locations[1]
|
||||
if type(firstLocation) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned no download locations!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
elseif type(firstLocation.location) ~= "string" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned no valid download locations!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 5. Fetch the place contents from the cdn
|
||||
local cdnResponse = net.request({
|
||||
url = firstLocation.location,
|
||||
headers = {
|
||||
Cookie = cookie,
|
||||
},
|
||||
})
|
||||
if not cdnResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to download asset with id %s from the Roblox cdn!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(cdnResponse.statusCode),
|
||||
tostring(cdnResponse.statusMessage),
|
||||
tostring(cdnResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 6. The response body should now be the contents of the asset file
|
||||
return cdnResponse.body
|
||||
end
|
||||
|
||||
local function uploadAssetId(assetId: number, contents: string)
|
||||
-- 1. Try to find the auth cookie for the current user
|
||||
local cookie = getAuthCookieWithFallbacks()
|
||||
|
||||
-- 2. Create request headers in advance, we might re-use them for CSRF challenges
|
||||
local headers = {
|
||||
["User-Agent"] = "Roblox/WinInet",
|
||||
["Content-Type"] = "application/octet-stream",
|
||||
Accept = "application/json",
|
||||
Cookie = cookie,
|
||||
}
|
||||
|
||||
-- 3. Create and send a request to the upload url
|
||||
local uploadResponse = net.request({
|
||||
url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`,
|
||||
body = contents,
|
||||
method = "POST",
|
||||
headers = headers,
|
||||
})
|
||||
|
||||
-- 4. Check if we got a valid response, we might have gotten a CSRF
|
||||
-- challenge and need to send the request with a token included
|
||||
if
|
||||
not uploadResponse.ok
|
||||
and uploadResponse.statusCode == 403
|
||||
and uploadResponse.headers["x-csrf-token"] ~= nil
|
||||
then
|
||||
headers["X-CSRF-Token"] = uploadResponse.headers["x-csrf-token"]
|
||||
uploadResponse = net.request({
|
||||
url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`,
|
||||
body = contents,
|
||||
method = "POST",
|
||||
headers = headers,
|
||||
})
|
||||
end
|
||||
|
||||
if not uploadResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to upload asset with id %s to Roblox!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(uploadResponse.statusCode),
|
||||
tostring(uploadResponse.statusMessage),
|
||||
tostring(uploadResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local remodel = {}
|
||||
|
||||
--[=[
|
||||
Load an `rbxl` or `rbxlx` file from the filesystem.
|
||||
|
||||
Returns a `DataModel` instance, equivalent to `game` from within Roblox.
|
||||
]=]
|
||||
function remodel.readPlaceFile(filePath: string)
|
||||
local placeFile = fs.readFile(filePath)
|
||||
local place = roblox.deserializePlace(placeFile)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Load an `rbxm` or `rbxmx` file from the filesystem.
|
||||
|
||||
Note that this function returns a **list of instances** instead of a single instance!
|
||||
This is because models can contain mutliple top-level instances.
|
||||
]=]
|
||||
function remodel.readModelFile(filePath: string)
|
||||
local modelFile = fs.readFile(filePath)
|
||||
local model = roblox.deserializeModel(modelFile)
|
||||
return model
|
||||
end
|
||||
|
||||
--[=[
|
||||
Reads a place asset from Roblox, equivalent to `remodel.readPlaceFile`.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.readPlaceAsset(assetId: number)
|
||||
local contents = downloadAssetId(assetId)
|
||||
local place = roblox.deserializePlace(contents)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Reads a model asset from Roblox, equivalent to `remodel.readModelFile`.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.readModelAsset(assetId: number)
|
||||
local contents = downloadAssetId(assetId)
|
||||
local place = roblox.deserializeModel(contents)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Saves an `rbxl` or `rbxlx` file out of the given `DataModel` instance.
|
||||
|
||||
If the instance is not a `DataModel`, this function will throw.
|
||||
Models should be saved with `writeModelFile` instead.
|
||||
]=]
|
||||
function remodel.writePlaceFile(filePath: string, dataModel: LuneDataModel)
|
||||
local asBinary = string.sub(filePath, -5) == ".rbxl"
|
||||
local asXml = string.sub(filePath, -6) == ".rbxlx"
|
||||
assert(asBinary or asXml, "File path must have .rbxl or .rbxlx extension")
|
||||
local placeFile = roblox.serializePlace(dataModel, asXml)
|
||||
fs.writeFile(filePath, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Saves an `rbxm` or `rbxmx` file out of the given `Instance`.
|
||||
|
||||
If the instance is a `DataModel`, this function will throw.
|
||||
Places should be saved with `writePlaceFile` instead.
|
||||
]=]
|
||||
function remodel.writeModelFile(filePath: string, instance: LuneInstance)
|
||||
local asBinary = string.sub(filePath, -5) == ".rbxm"
|
||||
local asXml = string.sub(filePath, -6) == ".rbxmx"
|
||||
assert(asBinary or asXml, "File path must have .rbxm or .rbxmx extension")
|
||||
local placeFile = roblox.serializeModel({ instance }, asXml)
|
||||
fs.writeFile(filePath, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Uploads the given `DataModel` instance to Roblox, overwriting an existing place.
|
||||
|
||||
If the instance is not a `DataModel`, this function will throw.
|
||||
Models should be uploaded with `writeExistingModelAsset` instead.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.writeExistingPlaceAsset(dataModel: LuneDataModel, assetId: number)
|
||||
local placeFile = roblox.serializePlace(dataModel)
|
||||
uploadAssetId(assetId, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Uploads the given instance to Roblox, overwriting an existing model.
|
||||
|
||||
If the instance is a `DataModel`, this function will throw.
|
||||
Places should be uploaded with `writeExistingPlaceAsset` instead.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.writeExistingModelAsset(instance: LuneInstance, assetId: number)
|
||||
local modelFile = roblox.serializeModel({ instance })
|
||||
uploadAssetId(assetId, modelFile)
|
||||
end
|
||||
|
||||
remodel.readFile = fs.readFile
|
||||
remodel.readDir = fs.readDir
|
||||
remodel.writeFile = fs.writeFile
|
||||
remodel.createDirAll = fs.writeDir
|
||||
remodel.removeFile = fs.removeFile
|
||||
remodel.removeDir = fs.removeDir
|
||||
remodel.isFile = fs.isFile
|
||||
remodel.isDir = fs.isDir
|
||||
|
||||
return remodel
|
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
12
next.config.js
Normal file
12
next.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
const withNextra = require("nextra")({
|
||||
theme: "nextra-theme-docs",
|
||||
themeConfig: "./theme.config.jsx",
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
...withNextra(),
|
||||
basePath: "/docs",
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
}
|
9089
package-lock.json
generated
Normal file
9089
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
package.json
Normal file
30
package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "lune-docs",
|
||||
"version": "0.0.1",
|
||||
"description": "Documentation site for Lune",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build && next export"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lune-org/docs.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/lune-orc/docs/issues"
|
||||
},
|
||||
"homepage": "https://github.com/lune-orc/docs#readme",
|
||||
"dependencies": {
|
||||
"next": "^13.4.12",
|
||||
"nextra": "^2.10.0",
|
||||
"nextra-theme-docs": "^2.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.10",
|
||||
"eslint": "^8.45.0",
|
||||
"next": "^13.4.12",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
6
pages/_meta.json
Normal file
6
pages/_meta.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"index": "Home",
|
||||
"getting-started": "Getting Started",
|
||||
"roblox": "Roblox",
|
||||
"api-reference": "API Reference"
|
||||
}
|
9
pages/api-reference/_meta.json
Normal file
9
pages/api-reference/_meta.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"fs": "FS",
|
||||
"net": "Net",
|
||||
"process": "Process",
|
||||
"roblox": "Roblox",
|
||||
"serde": "Serde",
|
||||
"stdio": "Stdio",
|
||||
"task": "Task"
|
||||
}
|
277
pages/api-reference/fs.md
Normal file
277
pages/api-reference/fs.md
Normal file
|
@ -0,0 +1,277 @@
|
|||
# FS
|
||||
|
||||
Built-in library for filesystem access
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
|
||||
-- Reading a file
|
||||
local myTextFile: string = fs.readFile("myFileName.txt")
|
||||
|
||||
-- Reading entries (files & dirs) in a directory
|
||||
for _, entryName in fs.readDir("myDirName") do
|
||||
if fs.isFile("myDirName/" .. entryName) then
|
||||
print("Found file " .. entryName)
|
||||
elseif fs.isDir("myDirName/" .. entryName) then
|
||||
print("Found subdirectory " .. entryName)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### readFile
|
||||
|
||||
Reads a file at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- `path` does not point to an existing file.
|
||||
- The current process lacks permissions to read the file.
|
||||
- The contents of the file cannot be read as a UTF-8 string.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The path to the file to read
|
||||
|
||||
#### Returns
|
||||
|
||||
- The contents of the file
|
||||
|
||||
---
|
||||
|
||||
### readDir
|
||||
|
||||
Reads entries in a directory at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- `path` does not point to an existing directory.
|
||||
- The current process lacks permissions to read the contents of the directory.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The directory path to search in
|
||||
|
||||
#### Returns
|
||||
|
||||
- A list of files & directories found
|
||||
|
||||
---
|
||||
|
||||
### writeFile
|
||||
|
||||
Writes to a file at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The file's parent directory does not exist.
|
||||
- The current process lacks permissions to write to the file.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The path of the file
|
||||
|
||||
- `contents` The contents of the file
|
||||
|
||||
---
|
||||
|
||||
### writeDir
|
||||
|
||||
Creates a directory and its parent directories if they are missing.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- `path` already points to an existing file or directory.
|
||||
- The current process lacks permissions to create the directory or its missing parents.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The directory to create
|
||||
|
||||
---
|
||||
|
||||
### removeFile
|
||||
|
||||
Removes a file.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- `path` does not point to an existing file.
|
||||
- The current process lacks permissions to remove the file.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The file to remove
|
||||
|
||||
---
|
||||
|
||||
### removeDir
|
||||
|
||||
Removes a directory and all of its contents.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- `path` is not an existing and empty directory.
|
||||
- The current process lacks permissions to remove the directory.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The directory to remove
|
||||
|
||||
---
|
||||
|
||||
### metadata
|
||||
|
||||
Gets metadata for the given path.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The current process lacks permissions to read at `path`.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The path to get metadata for
|
||||
|
||||
#### Returns
|
||||
|
||||
- Metadata for the path
|
||||
|
||||
---
|
||||
|
||||
### isFile
|
||||
|
||||
Checks if a given path is a file.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The current process lacks permissions to read at `path`.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The file path to check
|
||||
|
||||
#### Returns
|
||||
|
||||
- If the path is a file or not
|
||||
|
||||
---
|
||||
|
||||
### isDir
|
||||
|
||||
Checks if a given path is a directory.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The current process lacks permissions to read at `path`.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `path` The directory path to check
|
||||
|
||||
#### Returns
|
||||
|
||||
- If the path is a directory or not
|
||||
|
||||
---
|
||||
|
||||
### move
|
||||
|
||||
Moves a file or directory to a new path.
|
||||
|
||||
Throws an error if a file or directory already exists at the target path. This can be bypassed by
|
||||
passing `true` as the third argument, or a dictionary of options. Refer to the documentation for
|
||||
`WriteOptions` for specific option keys and their values.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The current process lacks permissions to read at `from` or write at `to`.
|
||||
- The new path exists on a different mount point.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `from` The path to move from
|
||||
|
||||
- `to` The path to move to
|
||||
|
||||
- `overwriteOrOptions` Options for the target path, such as if should be overwritten if it already
|
||||
exists
|
||||
|
||||
---
|
||||
|
||||
### copy
|
||||
|
||||
Copies a file or directory recursively to a new path.
|
||||
|
||||
Throws an error if a file or directory already exists at the target path. This can be bypassed by
|
||||
passing `true` as the third argument, or a dictionary of options. Refer to the documentation for
|
||||
`WriteOptions` for specific option keys and their values.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
- The current process lacks permissions to read at `from` or write at `to`.
|
||||
- Some other I/O error occurred.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `from` The path to copy from
|
||||
|
||||
- `to` The path to copy to
|
||||
|
||||
- `overwriteOrOptions` Options for the target path, such as if should be overwritten if it already
|
||||
exists
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### MetadataPermissions
|
||||
|
||||
Permissions for the given file or directory.
|
||||
|
||||
This is a dictionary that will contain the following values:
|
||||
|
||||
- `readOnly` - If the target path is read-only or not
|
||||
|
||||
---
|
||||
|
||||
### Metadata
|
||||
|
||||
Metadata for the given file or directory.
|
||||
|
||||
This is a dictionary that will contain the following values:
|
||||
|
||||
- `kind` - If the target path is a `file`, `dir` or `symlink`
|
||||
- `exists` - If the target path exists
|
||||
- `createdAt` - The timestamp at which the file or directory was created
|
||||
- `modifiedAt` - The timestamp at which the file or directory was last modified
|
||||
- `accessedAt` - The timestamp at which the file or directory was last accessed
|
||||
- `permissions` - Current permissions for the file or directory
|
||||
|
||||
Note that timestamps are relative to the unix epoch, and may not be accurate if the system clock is
|
||||
not accurate.
|
||||
|
||||
---
|
||||
|
||||
### WriteOptions
|
||||
|
||||
Options for filesystem APIs what write to files and/or directories.
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
- `overwrite` - If the target path should be overwritten or not, in the case that it already
|
||||
exists
|
||||
|
||||
---
|
270
pages/api-reference/net.md
Normal file
270
pages/api-reference/net.md
Normal file
|
@ -0,0 +1,270 @@
|
|||
# Net
|
||||
|
||||
Built-in library for network access
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local net = require("@lune/net")
|
||||
|
||||
-- Sending a web request
|
||||
local response = net.request("https://www.google.com")
|
||||
print(response.ok)
|
||||
print(response.statusCode, response.statusMessage)
|
||||
print(response.headers)
|
||||
|
||||
-- Using a JSON web API
|
||||
local response = net.request({
|
||||
url = "https://dummyjson.com/products/add",
|
||||
method = "POST",
|
||||
headers = { ["Content-Type"] = "application/json" },
|
||||
body = net.jsonEncode({
|
||||
title = "Cool Pencil",
|
||||
})
|
||||
})
|
||||
local product = net.jsonDecode(response.body)
|
||||
print(product.id, "-", product.title)
|
||||
|
||||
-- Starting up a webserver
|
||||
net.serve(8080, function(request)
|
||||
return {
|
||||
status = 200,
|
||||
body = "Echo:\n" .. request.body,
|
||||
}
|
||||
end)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### request
|
||||
|
||||
Sends an HTTP request using the given url and / or parameters, and returns a dictionary that
|
||||
describes the response received.
|
||||
|
||||
Only throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status
|
||||
codes.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `config` The URL or request config to use
|
||||
|
||||
#### Returns
|
||||
|
||||
- A dictionary representing the response for the request
|
||||
|
||||
---
|
||||
|
||||
### socket
|
||||
|
||||
Connects to a web socket at the given URL.
|
||||
|
||||
Throws an error if the server at the given URL does not support web sockets, or if a miscellaneous
|
||||
network or I/O error occurs.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `url` The URL to connect to
|
||||
|
||||
#### Returns
|
||||
|
||||
- A web socket handle
|
||||
|
||||
---
|
||||
|
||||
### serve
|
||||
|
||||
Creates an HTTP server that listens on the given `port`.
|
||||
|
||||
This will **_not_** block and will keep listening for requests on the given `port` until the `stop`
|
||||
function on the returned `ServeHandle` has been called.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `port` The port to use for the server
|
||||
|
||||
- `handlerOrConfig` The handler function or config to use for the server
|
||||
|
||||
#### Returns
|
||||
|
||||
- ServeHandle
|
||||
|
||||
---
|
||||
|
||||
### jsonEncode
|
||||
|
||||
Encodes the given value as JSON.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` The value to encode as JSON
|
||||
|
||||
- `pretty` If the encoded JSON string should include newlines and spaces. Defaults to false
|
||||
|
||||
#### Returns
|
||||
|
||||
- The encoded JSON string
|
||||
|
||||
---
|
||||
|
||||
### jsonDecode
|
||||
|
||||
Decodes the given JSON string into a lua value.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `encoded` The JSON string to decode
|
||||
|
||||
#### Returns
|
||||
|
||||
- The decoded lua value
|
||||
|
||||
---
|
||||
|
||||
### urlEncode
|
||||
|
||||
Encodes the given string using URL encoding.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `s` The string to encode
|
||||
|
||||
- `binary` If the string should be treated as binary data and/or is not valid utf-8. Defaults to
|
||||
false
|
||||
|
||||
#### Returns
|
||||
|
||||
- The encoded string
|
||||
|
||||
---
|
||||
|
||||
### urlDecode
|
||||
|
||||
Decodes the given string using URL decoding.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `s` The string to decode
|
||||
|
||||
- `binary` If the string should be treated as binary data and/or is not valid utf-8. Defaults to
|
||||
false
|
||||
|
||||
#### Returns
|
||||
|
||||
- The decoded string
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### FetchParamsOptions
|
||||
|
||||
Extra options for `FetchParams`.
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
- `decompress` - If the request body should be automatically decompressed when possible. Defaults
|
||||
to `true`
|
||||
|
||||
---
|
||||
|
||||
### FetchParams
|
||||
|
||||
Parameters for sending network requests with `net.request`.
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
- `url` - The URL to send a request to. This is always required
|
||||
- `method` - The HTTP method verb, such as `"GET"`, `"POST"`, `"PATCH"`, `"PUT"`, or `"DELETE"`.
|
||||
Defaults to `"GET"`
|
||||
- `body` - The request body
|
||||
- `query` - A table of key-value pairs representing query parameters in the request path
|
||||
- `headers` - A table of key-value pairs representing headers
|
||||
- `options` - Extra options for things such as automatic decompression of response bodies
|
||||
|
||||
---
|
||||
|
||||
### FetchResponse
|
||||
|
||||
Response type for sending network requests with `net.request`.
|
||||
|
||||
This is a dictionary containing the following values:
|
||||
|
||||
- `ok` - If the status code is a canonical success status code, meaning within the range 200 ->
|
||||
299
|
||||
- `statusCode` - The status code returned for the request
|
||||
- `statusMessage` - The canonical status message for the returned status code, such as
|
||||
`"Not Found"` for status code 404
|
||||
- `headers` - A table of key-value pairs representing headers
|
||||
- `body` - The request body, or an empty string if one was not given
|
||||
|
||||
---
|
||||
|
||||
### ServeRequest
|
||||
|
||||
Data type for requests in `net.serve`.
|
||||
|
||||
This is a dictionary containing the following values:
|
||||
|
||||
- `path` - The path being requested, relative to the root. Will be `/` if not specified
|
||||
- `query` - A table of key-value pairs representing query parameters in the request path
|
||||
- `method` - The HTTP method verb, such as `"GET"`, `"POST"`, `"PATCH"`, `"PUT"`, or `"DELETE"`.
|
||||
Will always be uppercase
|
||||
- `headers` - A table of key-value pairs representing headers
|
||||
- `body` - The request body, or an empty string if one was not given
|
||||
|
||||
---
|
||||
|
||||
### ServeResponse
|
||||
|
||||
Response type for requests in `net.serve`.
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
- `status` - The status code for the request, in the range `100` -> `599`
|
||||
- `headers` - A table of key-value pairs representing headers
|
||||
- `body` - The response body
|
||||
|
||||
---
|
||||
|
||||
### ServeConfig
|
||||
|
||||
Configuration for `net.serve`.
|
||||
|
||||
This may contain one of, or both of the following callbacks:
|
||||
|
||||
- `handleRequest` for handling normal http requests, equivalent to just passing a function to
|
||||
`net.serve`
|
||||
- `handleWebSocket` for handling web socket requests, which will receive a `WebSocket` object as
|
||||
its first and only parameter
|
||||
|
||||
---
|
||||
|
||||
### ServeHandle
|
||||
|
||||
A handle to a currently running web server, containing a single `stop` function to gracefully shut
|
||||
down the web server.
|
||||
|
||||
---
|
||||
|
||||
### WebSocket
|
||||
|
||||
A reference to a web socket connection.
|
||||
|
||||
The web socket may be in either an "open" or a "closed" state, changing its current behavior.
|
||||
|
||||
When open:
|
||||
|
||||
- Any function on the socket such as `send`, `next` or `close` can be called without erroring
|
||||
- `next` can be called to yield until the next message is received or the socket becomes closed
|
||||
|
||||
When closed:
|
||||
|
||||
- `next` will no longer return any message(s) and instead instantly return nil
|
||||
- `send` will throw an error stating that the socket has been closed
|
||||
|
||||
Once the websocket has been closed, `closeCode` will no longer be nil, and will be populated with a
|
||||
close code according to the
|
||||
[WebSocket specification](https://www.iana.org/assignments/websocket/websocket.xhtml). This will be
|
||||
an integer between 1000 and 4999, where 1000 is the canonical code for normal, error-free closure.
|
||||
|
||||
---
|
161
pages/api-reference/process.md
Normal file
161
pages/api-reference/process.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# Process
|
||||
|
||||
Built-in functions for the current process & child processes
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local process = require("@lune/process")
|
||||
|
||||
-- Getting the arguments passed to the Lune script
|
||||
for index, arg in process.args do
|
||||
print("Process argument #" .. tostring(index) .. ": " .. arg)
|
||||
end
|
||||
|
||||
-- Getting the currently available environment variables
|
||||
local PORT: string? = process.env.PORT
|
||||
local HOME: string? = process.env.HOME
|
||||
for name, value in process.env do
|
||||
print("Environment variable " .. name .. " is set to " .. value)
|
||||
end
|
||||
|
||||
-- Getting the current os and processor architecture
|
||||
print("Running " .. process.os .. " on " .. process.arch .. "!")
|
||||
|
||||
-- Spawning a child process
|
||||
local result = process.spawn("program", {
|
||||
"cli argument",
|
||||
"other cli argument"
|
||||
})
|
||||
if result.ok then
|
||||
print(result.stdout)
|
||||
else
|
||||
print(result.stderr)
|
||||
end
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
### os
|
||||
|
||||
`OS`
|
||||
|
||||
The current operating system being used.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `"linux"`
|
||||
- `"macos"`
|
||||
- `"windows"`
|
||||
|
||||
---
|
||||
|
||||
### arch
|
||||
|
||||
`Arch`
|
||||
|
||||
The architecture of the processor currently being used.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `"x86_64"`
|
||||
- `"aarch64"`
|
||||
|
||||
---
|
||||
|
||||
### args
|
||||
|
||||
`{ string }`
|
||||
|
||||
The arguments given when running the Lune script.
|
||||
|
||||
---
|
||||
|
||||
### cwd
|
||||
|
||||
`string`
|
||||
|
||||
The current working directory in which the Lune script is running.
|
||||
|
||||
---
|
||||
|
||||
### env
|
||||
|
||||
`{ [string]: string? }`
|
||||
|
||||
Current environment variables for this process.
|
||||
|
||||
Setting a value on this table will set the corresponding environment variable.
|
||||
|
||||
---
|
||||
|
||||
## Functions
|
||||
|
||||
### exit
|
||||
|
||||
Exits the currently running script as soon as possible with the given exit code.
|
||||
|
||||
Exit code 0 is treated as a successful exit, any other value is treated as an error.
|
||||
|
||||
Setting the exit code using this function will override any otherwise automatic exit code.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `code` The exit code to set
|
||||
|
||||
---
|
||||
|
||||
### spawn
|
||||
|
||||
Spawns a child process that will run the program `program`, and returns a dictionary that describes
|
||||
the final status and ouput of the child process.
|
||||
|
||||
The second argument, `params`, can be passed as a list of string parameters to give to the program.
|
||||
|
||||
The third argument, `options`, can be passed as a dictionary of options to give to the child
|
||||
process. Refer to the documentation for `SpawnOptions` for specific option keys and their values.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `program` The program to spawn as a child process
|
||||
|
||||
- `params` Additional parameters to pass to the program
|
||||
|
||||
- `options` A dictionary of options for the child process
|
||||
|
||||
#### Returns
|
||||
|
||||
- A dictionary representing the result of the child process
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### SpawnOptions
|
||||
|
||||
A dictionary of options for `process.spawn`, with the following available values:
|
||||
|
||||
- `cwd` - The current working directory for the process
|
||||
- `env` - Extra environment variables to give to the process
|
||||
- `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a
|
||||
string to run using a specific shell
|
||||
- `stdio` - How to treat output and error streams from the child process - set to "inherit" to
|
||||
pass output and error streams to the current process
|
||||
|
||||
---
|
||||
|
||||
### SpawnResult
|
||||
|
||||
Result type for child processes in `process.spawn`.
|
||||
|
||||
This is a dictionary containing the following values:
|
||||
|
||||
- `ok` - If the child process exited successfully or not, meaning the exit code was zero or not
|
||||
set
|
||||
- `code` - The exit code set by the child process, or 0 if one was not set
|
||||
- `stdout` - The full contents written to stdout by the child process, or an empty string if
|
||||
nothing was written
|
||||
- `stderr` - The full contents written to stderr by the child process, or an empty string if
|
||||
nothing was written
|
||||
|
||||
---
|
213
pages/api-reference/roblox.md
Normal file
213
pages/api-reference/roblox.md
Normal file
|
@ -0,0 +1,213 @@
|
|||
# Roblox
|
||||
|
||||
Built-in library for manipulating Roblox place & model files
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
-- Reading a place file
|
||||
local placeFile = fs.readFile("myPlaceFile.rbxl")
|
||||
local game = roblox.deserializePlace(placeFile)
|
||||
|
||||
-- Manipulating and reading instances - just like in Roblox!
|
||||
local workspace = game:GetService("Workspace")
|
||||
for _, child in workspace:GetChildren() do
|
||||
print("Found child " .. child.Name .. " of class " .. child.ClassName)
|
||||
end
|
||||
|
||||
-- Writing a place file
|
||||
local newPlaceFile = roblox.serializePlace(game)
|
||||
fs.writeFile("myPlaceFile.rbxl", newPlaceFile)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### deserializePlace
|
||||
|
||||
Deserializes a place into a DataModel instance.
|
||||
|
||||
This function accepts a string of contents, _not_ a file path. If reading a place file from a file
|
||||
path is desired, `fs.readFile` can be used and the resulting string may be passed to this function.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local placeFile = fs.readFile("filePath.rbxl")
|
||||
local game = roblox.deserializePlace(placeFile)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `contents` The contents of the place to read
|
||||
|
||||
#### Returns
|
||||
|
||||
- DataModel
|
||||
|
||||
---
|
||||
|
||||
### deserializeModel
|
||||
|
||||
Deserializes a model into an array of instances.
|
||||
|
||||
This function accepts a string of contents, _not_ a file path. If reading a model file from a file
|
||||
path is desired, `fs.readFile` can be used and the resulting string may be passed to this function.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local modelFile = fs.readFile("filePath.rbxm")
|
||||
local instances = roblox.deserializeModel(modelFile)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `contents` The contents of the model to read
|
||||
|
||||
#### Returns
|
||||
|
||||
- { Instance }
|
||||
|
||||
---
|
||||
|
||||
### serializePlace
|
||||
|
||||
Serializes a place from a DataModel instance.
|
||||
|
||||
This string can then be written to a file, or sent over the network.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local placeFile = roblox.serializePlace(game)
|
||||
fs.writeFile("filePath.rbxl", placeFile)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `dataModel` The DataModel for the place to serialize
|
||||
|
||||
- `xml` If the place should be serialized as xml or not. Defaults to `false`, meaning the place
|
||||
gets serialized using the binary format and not xml.
|
||||
|
||||
#### Returns
|
||||
|
||||
- string
|
||||
|
||||
---
|
||||
|
||||
### serializeModel
|
||||
|
||||
Serializes one or more instances as a model.
|
||||
|
||||
This string can then be written to a file, or sent over the network.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local modelFile = roblox.serializeModel({ instance1, instance2, ... })
|
||||
fs.writeFile("filePath.rbxm", modelFile)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `instances` The array of instances to serialize
|
||||
|
||||
- `xml` If the model should be serialized as xml or not. Defaults to `false`, meaning the model
|
||||
gets serialized using the binary format and not xml.
|
||||
|
||||
#### Returns
|
||||
|
||||
- string
|
||||
|
||||
---
|
||||
|
||||
### getAuthCookie
|
||||
|
||||
Gets the current auth cookie, for usage with Roblox web APIs.
|
||||
|
||||
Note that this auth cookie is formatted for use as a "Cookie" header, and that it contains
|
||||
restrictions so that it may only be used for official Roblox endpoints. To get the raw cookie value
|
||||
without any additional formatting, you can pass `true` as the first and only parameter.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
local net = require("@lune/net")
|
||||
|
||||
local cookie = roblox.getAuthCookie()
|
||||
assert(cookie ~= nil, "Failed to get roblox auth cookie")
|
||||
|
||||
local myPrivatePlaceId = 1234567890
|
||||
|
||||
local response = net.request({
|
||||
url = "https://assetdelivery.roblox.com/v2/assetId/" .. tostring(myPrivatePlaceId),
|
||||
headers = {
|
||||
Cookie = cookie,
|
||||
},
|
||||
})
|
||||
|
||||
local responseTable = net.jsonDecode(response.body)
|
||||
local responseLocation = responseTable.locations[1].location
|
||||
print("Download link to place: " .. responseLocation)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `raw` If the cookie should be returned as a pure value or not. Defaults to false
|
||||
|
||||
#### Returns
|
||||
|
||||
- string?
|
||||
|
||||
---
|
||||
|
||||
### getReflectionDatabase
|
||||
|
||||
Gets the bundled reflection database.
|
||||
|
||||
This database contains information about Roblox enums, classes, and their properties.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local db = roblox.getReflectionDatabase()
|
||||
|
||||
print("There are", #db:GetClassNames(), "classes in the reflection database")
|
||||
|
||||
print("All base instance properties:")
|
||||
|
||||
local class = db:GetClass("Instance")
|
||||
for name, prop in class.Properties do
|
||||
print(string.format(
|
||||
"- %s with datatype %s and default value %s",
|
||||
prop.Name,
|
||||
prop.Datatype,
|
||||
tostring(class.DefaultProperties[prop.Name])
|
||||
))
|
||||
end
|
||||
```
|
||||
|
||||
#### Returns
|
||||
|
||||
- Database
|
||||
|
||||
---
|
127
pages/api-reference/serde.md
Normal file
127
pages/api-reference/serde.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Serde
|
||||
|
||||
Built-in library for:
|
||||
|
||||
- serialization & deserialization
|
||||
- encoding & decoding
|
||||
- compression
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local serde = require("@lune/serde")
|
||||
|
||||
-- Parse different file formats into lua tables
|
||||
local someJson = serde.decode("json", fs.readFile("myFile.json"))
|
||||
local someToml = serde.decode("toml", fs.readFile("myFile.toml"))
|
||||
local someYaml = serde.decode("yaml", fs.readFile("myFile.yaml"))
|
||||
|
||||
-- Write lua tables to files in different formats
|
||||
fs.writeFile("myFile.json", serde.encode("json", someJson))
|
||||
fs.writeFile("myFile.toml", serde.encode("toml", someToml))
|
||||
fs.writeFile("myFile.yaml", serde.encode("yaml", someYaml))
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### encode
|
||||
|
||||
Encodes the given value using the given format.
|
||||
|
||||
Currently supported formats:
|
||||
|
||||
| Name | Learn More |
|
||||
| :----- | :------------------- |
|
||||
| `json` | https://www.json.org |
|
||||
| `yaml` | https://yaml.org |
|
||||
| `toml` | https://toml.io |
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `format` The format to use
|
||||
|
||||
- `value` The value to encode
|
||||
|
||||
- `pretty` If the encoded string should be human-readable, including things such as newlines and
|
||||
spaces. Only supported for json and toml formats, and defaults to false
|
||||
|
||||
#### Returns
|
||||
|
||||
- The encoded string
|
||||
|
||||
---
|
||||
|
||||
### decode
|
||||
|
||||
Decodes the given string using the given format into a lua value.
|
||||
|
||||
Currently supported formats:
|
||||
|
||||
| Name | Learn More |
|
||||
| :----- | :------------------- |
|
||||
| `json` | https://www.json.org |
|
||||
| `yaml` | https://yaml.org |
|
||||
| `toml` | https://toml.io |
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `format` The format to use
|
||||
|
||||
- `encoded` The string to decode
|
||||
|
||||
#### Returns
|
||||
|
||||
- The decoded lua value
|
||||
|
||||
---
|
||||
|
||||
### compress
|
||||
|
||||
Compresses the given string using the given format.
|
||||
|
||||
Currently supported formats:
|
||||
|
||||
| Name | Learn More |
|
||||
| :------- | :-------------------------------- |
|
||||
| `brotli` | https://github.com/google/brotli |
|
||||
| `gzip` | https://www.gnu.org/software/gzip |
|
||||
| `lz4` | https://github.com/lz4/lz4 |
|
||||
| `zlib` | https://www.zlib.net |
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `format` The format to use
|
||||
|
||||
- `s` The string to compress
|
||||
|
||||
#### Returns
|
||||
|
||||
- The compressed string
|
||||
|
||||
---
|
||||
|
||||
### decompress
|
||||
|
||||
Decompresses the given string using the given format.
|
||||
|
||||
Currently supported formats:
|
||||
|
||||
| Name | Learn More |
|
||||
| :------- | :-------------------------------- |
|
||||
| `brotli` | https://github.com/google/brotli |
|
||||
| `gzip` | https://www.gnu.org/software/gzip |
|
||||
| `lz4` | https://github.com/lz4/lz4 |
|
||||
| `zlib` | https://www.zlib.net |
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `format` The format to use
|
||||
|
||||
- `s` The string to decompress
|
||||
|
||||
#### Returns
|
||||
|
||||
- The decompressed string
|
||||
|
||||
---
|
126
pages/api-reference/stdio.md
Normal file
126
pages/api-reference/stdio.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
# Stdio
|
||||
|
||||
Built-in standard input / output & utility functions
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local stdio = require("@lune/stdio")
|
||||
|
||||
-- Prompting the user for basic input
|
||||
local text: string = stdio.prompt("text", "Please write some text")
|
||||
local confirmed: boolean = stdio.prompt("confirm", "Please confirm this action")
|
||||
|
||||
-- Writing directly to stdout or stderr, without the auto-formatting of print/warn/error
|
||||
stdio.write("Hello, ")
|
||||
stdio.write("World! ")
|
||||
stdio.write("All on the same line")
|
||||
stdio.ewrite("\nAnd some error text, too")
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### prompt
|
||||
|
||||
Prompts for user input using the wanted kind of prompt:
|
||||
|
||||
- `"text"` - Prompts for a plain text string from the user
|
||||
- `"confirm"` - Prompts the user to confirm with y / n (yes / no)
|
||||
- `"select"` - Prompts the user to select _one_ value from a list
|
||||
- `"multiselect"` - Prompts the user to select _one or more_ values from a list
|
||||
- `nil` - Equivalent to `"text"` with no extra arguments
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `kind` The kind of prompt to use
|
||||
|
||||
- `message` The message to show the user
|
||||
|
||||
- `defaultOrOptions` The default value for the prompt, or options to choose from for selection
|
||||
prompts
|
||||
|
||||
---
|
||||
|
||||
### color
|
||||
|
||||
Return an ANSI string that can be used to modify the persistent output color.
|
||||
|
||||
Pass `"reset"` to get a string that can reset the persistent output color.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
stdio.write(stdio.color("red"))
|
||||
print("This text will be red")
|
||||
stdio.write(stdio.color("reset"))
|
||||
print("This text will be normal")
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `color` The color to use
|
||||
|
||||
#### Returns
|
||||
|
||||
- A printable ANSI string
|
||||
|
||||
---
|
||||
|
||||
### style
|
||||
|
||||
Return an ANSI string that can be used to modify the persistent output style.
|
||||
|
||||
Pass `"reset"` to get a string that can reset the persistent output style.
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
stdio.write(stdio.style("bold"))
|
||||
print("This text will be bold")
|
||||
stdio.write(stdio.style("reset"))
|
||||
print("This text will be normal")
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `style` The style to use
|
||||
|
||||
#### Returns
|
||||
|
||||
- A printable ANSI string
|
||||
|
||||
---
|
||||
|
||||
### format
|
||||
|
||||
Formats arguments into a human-readable string with syntax highlighting for tables.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `...` The values to format
|
||||
|
||||
#### Returns
|
||||
|
||||
- The formatted string
|
||||
|
||||
---
|
||||
|
||||
### write
|
||||
|
||||
Writes a string directly to stdout, without any newline.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `s` The string to write to stdout
|
||||
|
||||
---
|
||||
|
||||
### ewrite
|
||||
|
||||
Writes a string directly to stderr, without any newline.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `s` The string to write to stderr
|
||||
|
||||
---
|
112
pages/api-reference/task.md
Normal file
112
pages/api-reference/task.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
# Task
|
||||
|
||||
Built-in task scheduler & thread spawning
|
||||
|
||||
#### Example usage
|
||||
|
||||
```lua
|
||||
local task = require("@lune/task")
|
||||
|
||||
-- Waiting for a certain amount of time
|
||||
task.wait(1)
|
||||
print("Waited for one second")
|
||||
|
||||
-- Running a task after a given amount of time
|
||||
task.delay(2, function()
|
||||
print("Ran after two seconds")
|
||||
end)
|
||||
|
||||
-- Spawning a new task that runs concurrently
|
||||
task.spawn(function()
|
||||
print("Running instantly")
|
||||
task.wait(1)
|
||||
print("One second passed inside the task")
|
||||
end)
|
||||
|
||||
print("Running after task.spawn yields")
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### cancel
|
||||
|
||||
Stops a currently scheduled thread from resuming.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `thread` The thread to cancel
|
||||
|
||||
---
|
||||
|
||||
### defer
|
||||
|
||||
Defers a thread or function to run at the end of the current task queue.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `functionOrThread` The function or thread to defer
|
||||
|
||||
- `...` T...
|
||||
|
||||
#### Returns
|
||||
|
||||
- The thread that will be deferred
|
||||
|
||||
---
|
||||
|
||||
### delay
|
||||
|
||||
Delays a thread or function to run after `duration` seconds.
|
||||
|
||||
If no `duration` is given, this will wait for the minimum amount of time possible.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `duration` number?
|
||||
|
||||
- `functionOrThread` The function or thread to delay
|
||||
|
||||
- `...` T...
|
||||
|
||||
#### Returns
|
||||
|
||||
- The thread that will be delayed
|
||||
|
||||
---
|
||||
|
||||
### spawn
|
||||
|
||||
Instantly runs a thread or function.
|
||||
|
||||
If the spawned task yields, the thread that spawned the task will resume, letting the spawned task
|
||||
run in the background.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `functionOrThread` The function or thread to spawn
|
||||
|
||||
- `...` T...
|
||||
|
||||
#### Returns
|
||||
|
||||
- The thread that was spawned
|
||||
|
||||
---
|
||||
|
||||
### wait
|
||||
|
||||
Waits for _at least_ the given amount of time.
|
||||
|
||||
The minimum wait time possible when using `task.wait` is limited by the underlying OS sleep
|
||||
implementation. For most systems this means `task.wait` is accurate down to about 5 milliseconds or
|
||||
less.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `duration` The amount of time to wait
|
||||
|
||||
#### Returns
|
||||
|
||||
- The exact amount of time waited
|
||||
|
||||
---
|
43
pages/getting-started/1-installation.md
Normal file
43
pages/getting-started/1-installation.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
# Installation
|
||||
|
||||
The preferred way of installing Lune is using [Aftman](https://github.com/lpghatguy/aftman).
|
||||
|
||||
Running this command in your terminal will add `lune` to an `aftman.toml` file in the current
|
||||
directory, or create one if it does not exist:
|
||||
|
||||
```sh copy
|
||||
aftman add filiptibell/lune
|
||||
```
|
||||
|
||||
## Other options
|
||||
|
||||
### Building from source
|
||||
|
||||
Building and installing from source requires the latest version of
|
||||
[Rust & Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) to be installed on
|
||||
your system. <br /> Once installed, run the following command in your terminal:
|
||||
|
||||
```sh copy
|
||||
cargo install lune --locked
|
||||
```
|
||||
|
||||
Note that Lune does not make any minimum supported rust version (MSRV) guarantees and you may need
|
||||
to upgrade your version of Rust to update Lune in the future.
|
||||
|
||||
### Using GitHub Releases
|
||||
|
||||
You can download pre-built binaries for most systems directly from the
|
||||
[GitHub Releases](https://github.com/filiptibell/lune/releases) page. <br /> There are many tools
|
||||
that can install binaries directly from releases, and it is up to you to choose what tool to use
|
||||
when installing here.
|
||||
|
||||
## Next steps
|
||||
|
||||
Congratulations! You've installed Lune and are now ready to write your first script.
|
||||
|
||||
- If you want to write standalone scripts, head over to the
|
||||
[Writing Scripts](./1-writing-scripts.md) page.
|
||||
- If you want to write Lune scripts specifically for Roblox, check out the
|
||||
[Roblox](../roblox/1-introduction.md) section.
|
297
pages/getting-started/2-writing-scripts.md
Normal file
297
pages/getting-started/2-writing-scripts.md
Normal file
|
@ -0,0 +1,297 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD026 -->
|
||||
|
||||
# Writing Lune Scripts
|
||||
|
||||
If you've already written some version of Lua (or Luau) scripts before, this walkthrough will make
|
||||
you feel right at home.
|
||||
|
||||
Once you have a script you want to run, head over to the [Running Scripts](./2-running-scripts.md)
|
||||
page.
|
||||
|
||||
## Hello, Lune!
|
||||
|
||||
```lua copy
|
||||
--[[
|
||||
EXAMPLE #1
|
||||
|
||||
Using arguments given to the program
|
||||
]]
|
||||
|
||||
if #process.args > 0 then
|
||||
print("Got arguments:")
|
||||
print(process.args)
|
||||
if #process.args > 3 then
|
||||
error("Too many arguments!")
|
||||
end
|
||||
else
|
||||
print("Got no arguments ☹️")
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #2
|
||||
|
||||
Using the stdio library to prompt for terminal input
|
||||
]]
|
||||
|
||||
local text = stdio.prompt("text", "Please write some text")
|
||||
|
||||
print("You wrote '" .. text .. "'!")
|
||||
|
||||
local confirmed = stdio.prompt("confirm", "Please confirm that you wrote some text")
|
||||
if confirmed == false then
|
||||
error("You didn't confirm!")
|
||||
else
|
||||
print("Confirmed!")
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #3
|
||||
|
||||
Get & set environment variables
|
||||
|
||||
Checks if environment variables are empty or not,
|
||||
prints out ❌ if empty and ✅ if they have a value
|
||||
]]
|
||||
|
||||
print("Reading current environment 🔎")
|
||||
|
||||
-- Environment variables can be read directly
|
||||
assert(process.env.PATH ~= nil, "Missing PATH")
|
||||
assert(process.env.PWD ~= nil, "Missing PWD")
|
||||
|
||||
-- And they can also be accessed using Luau's generalized iteration (but not pairs())
|
||||
for key, value in process.env do
|
||||
local box = if value and value ~= "" then "✅" else "❌"
|
||||
print(string.format("[%s] %s", box, key))
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #4
|
||||
|
||||
Writing a module
|
||||
|
||||
Modularizing and splitting up your code is Lune is very straight-forward,
|
||||
in contrast to other scripting languages and shells such as bash
|
||||
]]
|
||||
|
||||
local module = {}
|
||||
|
||||
function module.sayHello()
|
||||
print("Hello, Lune! 🌙")
|
||||
end
|
||||
|
||||
return module
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #5
|
||||
|
||||
Using a function from another module / script
|
||||
|
||||
Lune has path-relative imports, similar to other popular languages such as JavaScript
|
||||
]]
|
||||
|
||||
local module = require("../modules/module")
|
||||
module.sayHello()
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #6
|
||||
|
||||
Spawning concurrent tasks
|
||||
|
||||
These tasks will run at the same time as other Lua code which lets you do primitive multitasking
|
||||
]]
|
||||
|
||||
task.spawn(function()
|
||||
print("Spawned a task that will run instantly but not block")
|
||||
task.wait(5)
|
||||
end)
|
||||
|
||||
print("Spawning a delayed task that will run in 5 seconds")
|
||||
task.delay(5, function()
|
||||
print("...")
|
||||
task.wait(1)
|
||||
print("Hello again!")
|
||||
task.wait(1)
|
||||
print("Goodbye again! 🌙")
|
||||
end)
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #7
|
||||
|
||||
Read files in the current directory
|
||||
|
||||
This prints out directory & file names with some fancy icons
|
||||
]]
|
||||
|
||||
print("Reading current dir 🗂️")
|
||||
local entries = fs.readDir(".")
|
||||
|
||||
-- NOTE: We have to do this outside of the sort function
|
||||
-- to avoid yielding across the metamethod boundary, all
|
||||
-- of the filesystem APIs are asynchronous and yielding
|
||||
local entryIsDir = {}
|
||||
for _, entry in entries do
|
||||
entryIsDir[entry] = fs.isDir(entry)
|
||||
end
|
||||
|
||||
-- Sort prioritizing directories first, then alphabetically
|
||||
table.sort(entries, function(entry0, entry1)
|
||||
if entryIsDir[entry0] ~= entryIsDir[entry1] then
|
||||
return entryIsDir[entry0]
|
||||
end
|
||||
return entry0 < entry1
|
||||
end)
|
||||
|
||||
-- Make sure we got some known files that should always exist
|
||||
assert(table.find(entries, "Cargo.toml") ~= nil, "Missing Cargo.toml")
|
||||
assert(table.find(entries, "Cargo.lock") ~= nil, "Missing Cargo.lock")
|
||||
|
||||
-- Print the pretty stuff
|
||||
for _, entry in entries do
|
||||
if fs.isDir(entry) then
|
||||
print("📁 " .. entry)
|
||||
else
|
||||
print("📄 " .. entry)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #8
|
||||
|
||||
Call out to another program / executable
|
||||
|
||||
You can also get creative and combine this with example #6 to spawn several programs at the same time!
|
||||
]]
|
||||
|
||||
print("Sending 4 pings to google 🌏")
|
||||
local result = process.spawn("ping", {
|
||||
"google.com",
|
||||
"-c 4",
|
||||
})
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #9
|
||||
|
||||
Using the result of a spawned process, exiting the process
|
||||
|
||||
This looks scary with lots of weird symbols, but, it's just some Lua-style pattern matching
|
||||
to parse the lines of "min/avg/max/stddev = W/X/Y/Z ms" that the ping program outputs to us
|
||||
]]
|
||||
|
||||
if result.ok then
|
||||
assert(#result.stdout > 0, "Result output was empty")
|
||||
local min, avg, max, stddev = string.match(
|
||||
result.stdout,
|
||||
"min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms"
|
||||
)
|
||||
print(string.format("Minimum ping time: %.3fms", assert(tonumber(min))))
|
||||
print(string.format("Maximum ping time: %.3fms", assert(tonumber(max))))
|
||||
print(string.format("Average ping time: %.3fms", assert(tonumber(avg))))
|
||||
print(string.format("Standard deviation: %.3fms", assert(tonumber(stddev))))
|
||||
else
|
||||
print("Failed to send ping to google!")
|
||||
print(result.stderr)
|
||||
process.exit(result.code)
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #10
|
||||
|
||||
Using the built-in networking library, encoding & decoding json
|
||||
]]
|
||||
|
||||
print("Sending PATCH request to web API 📤")
|
||||
local apiResult = net.request({
|
||||
url = "https://jsonplaceholder.typicode.com/posts/1",
|
||||
method = "PATCH",
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
},
|
||||
body = net.jsonEncode({
|
||||
title = "foo",
|
||||
body = "bar",
|
||||
}),
|
||||
})
|
||||
|
||||
if not apiResult.ok then
|
||||
print("Failed to send network request!")
|
||||
print(string.format("%d (%s)", apiResult.statusCode, apiResult.statusMessage))
|
||||
print(apiResult.body)
|
||||
process.exit(1)
|
||||
end
|
||||
|
||||
type ApiResponse = {
|
||||
id: number,
|
||||
title: string,
|
||||
body: string,
|
||||
userId: number,
|
||||
}
|
||||
|
||||
local apiResponse: ApiResponse = net.jsonDecode(apiResult.body)
|
||||
assert(apiResponse.title == "foo", "Invalid json response")
|
||||
assert(apiResponse.body == "bar", "Invalid json response")
|
||||
print("Got valid JSON response with changes applied")
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #11
|
||||
|
||||
Using the stdio library to print pretty
|
||||
]]
|
||||
|
||||
print("Printing with pretty colors and auto-formatting 🎨")
|
||||
|
||||
print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset"))
|
||||
|
||||
print("API response:", apiResponse)
|
||||
warn({
|
||||
Oh = {
|
||||
No = {
|
||||
TooMuch = {
|
||||
Nesting = {
|
||||
"Will not print",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset"))
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
EXAMPLE #12
|
||||
|
||||
Saying goodbye 😔
|
||||
]]
|
||||
|
||||
print("Goodbye, lune! 🌙")
|
||||
|
||||
```
|
||||
|
||||
More real-world examples of how to write Lune scripts can be found in the
|
||||
[examples](https://github.com/filiptibell/lune/blob/main/.lune/examples/) folder.
|
||||
|
||||
Documentation for individual APIs and types can be found in the "API Reference" section in the
|
||||
sidebar.
|
58
pages/getting-started/3-running-scripts.md
Normal file
58
pages/getting-started/3-running-scripts.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
# Running Lune Scripts
|
||||
|
||||
After you've written a script file, for example `script-name.luau`, you can run it:
|
||||
|
||||
```sh copy
|
||||
lune script-name
|
||||
```
|
||||
|
||||
This will look for the file `script-name.luau`**_<sup>[1]</sup>_** in a few locations:
|
||||
|
||||
- The current directory
|
||||
- The folder `lune` in the current directory, if it exists
|
||||
- The folder `.lune` in the current directory, if it exists
|
||||
- The folder `lune` in the _home_ directory, if it exists
|
||||
- The folder `.lune` in the _home_ directory, if it exists
|
||||
|
||||
## Passing Command-Line Arguments
|
||||
|
||||
Arguments can be passed to a Lune script directory from the command line when running it:
|
||||
|
||||
```sh copy
|
||||
lune script-name arg1 arg2 "argument three"
|
||||
```
|
||||
|
||||
These arguments will then be available in your script using `process.args`:
|
||||
|
||||
```lua copy
|
||||
local process = require("@lune/process")
|
||||
|
||||
print(process.args)
|
||||
--> { "arg1", "arg2", "argument three" }
|
||||
```
|
||||
|
||||
## Additional Commands
|
||||
|
||||
```sh copy
|
||||
lune --list
|
||||
```
|
||||
|
||||
Lists all scripts found in `lune` or `.lune` directories, including any top-level description
|
||||
comments. <br /> Lune description comments are always written at the top of a file and start with a
|
||||
lua-style comment arrow (`-->`).
|
||||
|
||||
```sh copy
|
||||
lune -
|
||||
```
|
||||
|
||||
Runs a script passed to Lune using stdin. Occasionally useful for running scripts piped to Lune from
|
||||
external sources.
|
||||
|
||||
---
|
||||
|
||||
**_<sup>[1]</sup>_** _Lune also supports files with the `.lua` extension but using the `.luau`
|
||||
extension is highly recommended. Additionally, if you don't want Lune to look in sub-directories or
|
||||
try to find files with `.lua` / `.luau` extensions at all, you can provide an absolute file path.
|
||||
This will disable all file path parsing and checks, and just run the file directly._
|
60
pages/getting-started/4-editor-setup.mdx
Normal file
60
pages/getting-started/4-editor-setup.mdx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { Steps } from "nextra/components"
|
||||
|
||||
# Editor Setup
|
||||
|
||||
Lune prioritizes developer experience, and as such type definitions and documentation are provided
|
||||
for several editors and tools without any additional downloads. This guide will help you get set up
|
||||
with your editor environment.
|
||||
|
||||
## Luau Language Server
|
||||
|
||||
Lune provides type definitions and documentation using the open source Luau Language Server, also
|
||||
known as [`luau-lsp`](https://github.com/JohnnyMorganz/luau-lsp).
|
||||
|
||||
These steps assume you have access to a terminal, that you have already installed Lune, and that it
|
||||
is available to run in the current directory.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Step 1
|
||||
|
||||
Run the following command in your terminal to generate Luau type definitions for your installed
|
||||
version of Lune:
|
||||
|
||||
```sh
|
||||
lune --setup
|
||||
```
|
||||
|
||||
### Step 2
|
||||
|
||||
Verify that type definition files have been generated, these should be located in your home
|
||||
directory and you can list them using `ls`:
|
||||
|
||||
```sh
|
||||
ls ~/.lune/.typedefs/
|
||||
```
|
||||
|
||||
Your currently installed version of Lune should appear, along with any previously installed versions
|
||||
you have type definitions for, when running this command.
|
||||
|
||||
### Step 3
|
||||
|
||||
Modify your editor settings. If you are using Visual Studio Code you can do this either by using the
|
||||
settings menu or by manually pasting these settings in your `settings.json` file:
|
||||
|
||||
```json
|
||||
"luau-lsp.require.mode": "relativeToFile",
|
||||
"luau-lsp.require.directoryAliases": {
|
||||
"@lune/": "~/.lune/.typedefs/x.y.z/"
|
||||
}
|
||||
```
|
||||
|
||||
These settings will:
|
||||
|
||||
- Set the require mode to work with Lune
|
||||
- Add type definitions for Lune built-in libraries
|
||||
|
||||
_**NOTE:** If you already had a `.vscode/settings.json` file in your current directory the type
|
||||
definition files may have been added automatically!_
|
||||
|
||||
</Steps>
|
6
pages/getting-started/_meta.json
Normal file
6
pages/getting-started/_meta.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"1-installation": "Installation",
|
||||
"2-writing-scripts": "Writing Scripts",
|
||||
"3-running-scripts": "Running Scripts",
|
||||
"4-editor-setup": "Editor Setup"
|
||||
}
|
32
pages/index.md
Normal file
32
pages/index.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Lune
|
||||
|
||||
A standalone [Luau](https://luau-lang.org) script runtime.
|
||||
|
||||
Write and run scripts, similar to runtimes for other languages such as [Node](https://nodejs.org) /
|
||||
[Deno](https://deno.land), or [Luvit](https://luvit.io) for vanilla Lua.
|
||||
|
||||
Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for optimal safety
|
||||
and correctness.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌙 A strictly minimal but powerful interface that is easy to read and remember, just like Luau
|
||||
itself
|
||||
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~3mb)
|
||||
executable
|
||||
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection
|
||||
necessary
|
||||
- 🏡 A familiar scripting environment for Roblox developers, with an included 1-to-1 task
|
||||
scheduler port
|
||||
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Making scripts short and terse - proper autocomplete / intellisense make scripting using Lune
|
||||
just as quick, and readability is important
|
||||
- Running full Roblox game scripts outside of Roblox - there is some compatibility, but Lune is
|
||||
meant for different purposes
|
||||
|
||||
## Where do I start?
|
||||
|
||||
Head over to the [Installation](./getting-started/1-installation.md) page to get started using Lune!
|
15
pages/roblox/1-introduction.md
Normal file
15
pages/roblox/1-introduction.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD026 -->
|
||||
|
||||
# The `roblox` Library
|
||||
|
||||
Lune has a powerful built-in library and set of APIs for manipulating Roblox place files and model
|
||||
files. It contains APIs for reading & writing files, and gives you instances to use, just as if you
|
||||
were scripting inside of the Roblox engine, albeit with a more limited API.
|
||||
|
||||
- For examples on how to write Roblox-specific Lune scripts, check out the
|
||||
[Examples](./2-examples.md) page.
|
||||
- For a guide on how to migrate to Lune from [Remodel](https://github.com/rojo-rbx/remodel), check
|
||||
out the [Migrating from Remodel](./3-remodel-migration.mdx) page.
|
||||
- For a full list of the currently implemented Roblox APIs, check out the
|
||||
[API Status](./4-api-status.md) page.
|
74
pages/roblox/2-examples.md
Normal file
74
pages/roblox/2-examples.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD026 -->
|
||||
|
||||
# Example Scripts
|
||||
|
||||
These are a few short examples of things you can do using the built-in `roblox` library.
|
||||
|
||||
## Make all parts anchored in a place file
|
||||
|
||||
```lua copy
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
-- Read the place file called myPlaceFile.rbxl into a DataModel called "game"
|
||||
-- This works exactly the same as in Roblox, except "game" does not exist by default - you have to load it from a file!
|
||||
local game = roblox.readPlaceFile("myPlaceFile.rbxl")
|
||||
local workspace = game:GetService("Workspace")
|
||||
|
||||
-- Make all of the parts in the workspace anchored
|
||||
for _, descendant in workspace:GetDescendants() do
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.Anchored = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the DataModel (game) back to the file that we read it from
|
||||
roblox.writePlaceFile("myPlaceFile.rbxl")
|
||||
```
|
||||
|
||||
## Save instances in a place as individual model files
|
||||
|
||||
```lua copy
|
||||
local roblox = require("@lune/roblox")
|
||||
local fs = require("@lune/fs")
|
||||
|
||||
-- Here we load a file just like in the first example
|
||||
local game = roblox.readPlaceFile("myPlaceFile.rbxl")
|
||||
local workspace = game:GetService("Workspace")
|
||||
|
||||
-- We use a normal Lune API to make sure a directory exists to save our models in
|
||||
fs.writeDir("models")
|
||||
|
||||
-- Then we save all of our instances in Workspace as model files, in our new directory
|
||||
-- Note that a model file can actually contain several instances at once, so we pass a table here
|
||||
for _, child in workspace:GetChildren() do
|
||||
roblox.writeModelFile("models/" .. child.Name, { child })
|
||||
end
|
||||
```
|
||||
|
||||
## Make a new place from scratch
|
||||
|
||||
```lua copy
|
||||
local roblox = require("@lune/roblox")
|
||||
local Instance = roblox.Instance
|
||||
|
||||
-- You can even create a new DataModel using Instance.new, which is not normally possible in Roblox
|
||||
-- This is normal - most instances that are not normally accessible in Roblox can be manipulated using Lune!
|
||||
local game = Instance.new("DataModel")
|
||||
local workspace = game:GetService("Workspace")
|
||||
|
||||
-- Here we just make a bunch of models with parts in them for demonstration purposes
|
||||
for i = 1, 50 do
|
||||
local model = Instance.new("Model")
|
||||
model.Name = "Model #" .. tostring(i)
|
||||
model.Parent = workspace
|
||||
for j = 1, 4 do
|
||||
local part = Instance.new("Part")
|
||||
part.Name = "Part #" .. tostring(j)
|
||||
part.Parent = model
|
||||
end
|
||||
end
|
||||
|
||||
-- As always, we have to save the DataModel (game) to a file when we're done
|
||||
roblox.writePlaceFile("myPlaceWithLotsOfModels.rbxl")
|
||||
```
|
369
pages/roblox/3-remodel-migration.mdx
Normal file
369
pages/roblox/3-remodel-migration.mdx
Normal file
|
@ -0,0 +1,369 @@
|
|||
import { Steps } from "nextra/components"
|
||||
|
||||
# Migrating from Remodel
|
||||
|
||||
If you have used [Remodel](https://github.com/rojo-rbx/remodel) before to manipulate place and/or
|
||||
model files, this migration guide will help you get started with accomplishing the same tasks in
|
||||
Lune.
|
||||
|
||||
## Drop-in Compatibility
|
||||
|
||||
This guide provides a module which translates all of the relevant Lune APIs to their Remodel
|
||||
equivalents.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Step 1
|
||||
|
||||
Copy the source below and place it in a file named `remodel.luau`:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
```lua copy
|
||||
--!strict
|
||||
|
||||
local fs = require("@lune/fs")
|
||||
local net = require("@lune/net")
|
||||
local serde = require("@lune/serde")
|
||||
local process = require("@lune/process")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
export type LuneDataModel = roblox.DataModel
|
||||
export type LuneInstance = roblox.Instance
|
||||
|
||||
local function getAuthCookieWithFallbacks()
|
||||
local cookie = roblox.getAuthCookie()
|
||||
if cookie then
|
||||
return cookie
|
||||
end
|
||||
|
||||
local cookieFromEnv = process.env.REMODEL_AUTH
|
||||
if cookieFromEnv and #cookieFromEnv > 0 then
|
||||
return `.ROBLOSECURITY={cookieFromEnv}`
|
||||
end
|
||||
|
||||
for index, arg in process.args do
|
||||
if arg == "--auth" then
|
||||
local cookieFromArgs = process.args[index + 1]
|
||||
if cookieFromArgs and #cookieFromArgs > 0 then
|
||||
return `.ROBLOSECURITY={cookieFromArgs}`
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
error([[
|
||||
Failed to find ROBLOSECURITY cookie for authentication!
|
||||
Make sure you have logged into studio, or set the ROBLOSECURITY environment variable.
|
||||
]])
|
||||
end
|
||||
|
||||
local function downloadAssetId(assetId: number)
|
||||
-- 1. Try to find the auth cookie for the current user
|
||||
local cookie = getAuthCookieWithFallbacks()
|
||||
|
||||
-- 2. Send a request to the asset delivery API,
|
||||
-- which will respond with cdn download link(s)
|
||||
local assetApiResponse = net.request({
|
||||
url = `https://assetdelivery.roblox.com/v2/assetId/{assetId}`,
|
||||
headers = {
|
||||
Accept = "application/json",
|
||||
Cookie = cookie,
|
||||
},
|
||||
})
|
||||
if not assetApiResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to fetch asset download link for asset id %s!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(assetApiResponse.statusCode),
|
||||
tostring(assetApiResponse.statusMessage),
|
||||
tostring(assetApiResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 3. Make sure we got a valid response body
|
||||
local assetApiBody = serde.decode("json", assetApiResponse.body)
|
||||
if type(assetApiBody) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned an invalid response body!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
elseif type(assetApiBody.locations) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned an invalid response body!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 4. Grab the first asset download location - we only
|
||||
-- requested one in our query, so this will be correct
|
||||
local firstLocation = assetApiBody.locations[1]
|
||||
if type(firstLocation) ~= "table" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned no download locations!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
elseif type(firstLocation.location) ~= "string" then
|
||||
error(
|
||||
string.format(
|
||||
"Asset delivery API returned no valid download locations!\n%s",
|
||||
assetApiResponse.body
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 5. Fetch the place contents from the cdn
|
||||
local cdnResponse = net.request({
|
||||
url = firstLocation.location,
|
||||
headers = {
|
||||
Cookie = cookie,
|
||||
},
|
||||
})
|
||||
if not cdnResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to download asset with id %s from the Roblox cdn!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(cdnResponse.statusCode),
|
||||
tostring(cdnResponse.statusMessage),
|
||||
tostring(cdnResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
-- 6. The response body should now be the contents of the asset file
|
||||
return cdnResponse.body
|
||||
end
|
||||
|
||||
local function uploadAssetId(assetId: number, contents: string)
|
||||
-- 1. Try to find the auth cookie for the current user
|
||||
local cookie = getAuthCookieWithFallbacks()
|
||||
|
||||
-- 2. Create request headers in advance, we might re-use them for CSRF challenges
|
||||
local headers = {
|
||||
["User-Agent"] = "Roblox/WinInet",
|
||||
["Content-Type"] = "application/octet-stream",
|
||||
Accept = "application/json",
|
||||
Cookie = cookie,
|
||||
}
|
||||
|
||||
-- 3. Create and send a request to the upload url
|
||||
local uploadResponse = net.request({
|
||||
url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`,
|
||||
body = contents,
|
||||
method = "POST",
|
||||
headers = headers,
|
||||
})
|
||||
|
||||
-- 4. Check if we got a valid response, we might have gotten a CSRF
|
||||
-- challenge and need to send the request with a token included
|
||||
if
|
||||
not uploadResponse.ok
|
||||
and uploadResponse.statusCode == 403
|
||||
and uploadResponse.headers["x-csrf-token"] ~= nil
|
||||
then
|
||||
headers["X-CSRF-Token"] = uploadResponse.headers["x-csrf-token"]
|
||||
uploadResponse = net.request({
|
||||
url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`,
|
||||
body = contents,
|
||||
method = "POST",
|
||||
headers = headers,
|
||||
})
|
||||
end
|
||||
|
||||
if not uploadResponse.ok then
|
||||
error(
|
||||
string.format(
|
||||
"Failed to upload asset with id %s to Roblox!\n%s (%s)\n%s",
|
||||
tostring(assetId),
|
||||
tostring(uploadResponse.statusCode),
|
||||
tostring(uploadResponse.statusMessage),
|
||||
tostring(uploadResponse.body)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local remodel = {}
|
||||
|
||||
--[=[
|
||||
Load an `rbxl` or `rbxlx` file from the filesystem.
|
||||
|
||||
Returns a `DataModel` instance, equivalent to `game` from within Roblox.
|
||||
]=]
|
||||
function remodel.readPlaceFile(filePath: string)
|
||||
local placeFile = fs.readFile(filePath)
|
||||
local place = roblox.deserializePlace(placeFile)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Load an `rbxm` or `rbxmx` file from the filesystem.
|
||||
|
||||
Note that this function returns a **list of instances** instead of a single instance!
|
||||
This is because models can contain mutliple top-level instances.
|
||||
]=]
|
||||
function remodel.readModelFile(filePath: string)
|
||||
local modelFile = fs.readFile(filePath)
|
||||
local model = roblox.deserializeModel(modelFile)
|
||||
return model
|
||||
end
|
||||
|
||||
--[=[
|
||||
Reads a place asset from Roblox, equivalent to `remodel.readPlaceFile`.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.readPlaceAsset(assetId: number)
|
||||
local contents = downloadAssetId(assetId)
|
||||
local place = roblox.deserializePlace(contents)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Reads a model asset from Roblox, equivalent to `remodel.readModelFile`.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.readModelAsset(assetId: number)
|
||||
local contents = downloadAssetId(assetId)
|
||||
local place = roblox.deserializeModel(contents)
|
||||
return place
|
||||
end
|
||||
|
||||
--[=[
|
||||
Saves an `rbxl` or `rbxlx` file out of the given `DataModel` instance.
|
||||
|
||||
If the instance is not a `DataModel`, this function will throw.
|
||||
Models should be saved with `writeModelFile` instead.
|
||||
]=]
|
||||
function remodel.writePlaceFile(filePath: string, dataModel: LuneDataModel)
|
||||
local asBinary = string.sub(filePath, -5) == ".rbxl"
|
||||
local asXml = string.sub(filePath, -6) == ".rbxlx"
|
||||
assert(asBinary or asXml, "File path must have .rbxl or .rbxlx extension")
|
||||
local placeFile = roblox.serializePlace(dataModel, asXml)
|
||||
fs.writeFile(filePath, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Saves an `rbxm` or `rbxmx` file out of the given `Instance`.
|
||||
|
||||
If the instance is a `DataModel`, this function will throw.
|
||||
Places should be saved with `writePlaceFile` instead.
|
||||
]=]
|
||||
function remodel.writeModelFile(filePath: string, instance: LuneInstance)
|
||||
local asBinary = string.sub(filePath, -5) == ".rbxm"
|
||||
local asXml = string.sub(filePath, -6) == ".rbxmx"
|
||||
assert(asBinary or asXml, "File path must have .rbxm or .rbxmx extension")
|
||||
local placeFile = roblox.serializeModel({ instance }, asXml)
|
||||
fs.writeFile(filePath, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Uploads the given `DataModel` instance to Roblox, overwriting an existing place.
|
||||
|
||||
If the instance is not a `DataModel`, this function will throw.
|
||||
Models should be uploaded with `writeExistingModelAsset` instead.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.writeExistingPlaceAsset(dataModel: LuneDataModel, assetId: number)
|
||||
local placeFile = roblox.serializePlace(dataModel)
|
||||
uploadAssetId(assetId, placeFile)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Uploads the given instance to Roblox, overwriting an existing model.
|
||||
|
||||
If the instance is a `DataModel`, this function will throw.
|
||||
Places should be uploaded with `writeExistingPlaceAsset` instead.
|
||||
|
||||
***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!*
|
||||
]=]
|
||||
function remodel.writeExistingModelAsset(instance: LuneInstance, assetId: number)
|
||||
local modelFile = roblox.serializeModel({ instance })
|
||||
uploadAssetId(assetId, modelFile)
|
||||
end
|
||||
|
||||
remodel.readFile = fs.readFile
|
||||
remodel.readDir = fs.readDir
|
||||
remodel.writeFile = fs.writeFile
|
||||
remodel.createDirAll = fs.writeDir
|
||||
remodel.removeFile = fs.removeFile
|
||||
remodel.removeDir = fs.removeDir
|
||||
remodel.isFile = fs.isFile
|
||||
remodel.isDir = fs.isDir
|
||||
|
||||
return remodel
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
This module is quite large, but you will not need to read through it unless you want to know about
|
||||
the internal details of how Remodel used to work.
|
||||
|
||||
### Step 2
|
||||
|
||||
Next, create another script next to your `remodel.luau`. We will be naming it `example.luau`, but
|
||||
you can name it whatever you want. This example code is from one of the previous Remodel-native
|
||||
example scripts, with only the top line added:
|
||||
|
||||
```lua copy
|
||||
local remodel = require("./remodel")
|
||||
|
||||
-- One use for Remodel is to move the terrain of one place into another place.
|
||||
local inputGame = remodel.readPlaceFile("input-place.rbxlx")
|
||||
local outputGame = remodel.readPlaceFile("output-place.rbxlx")
|
||||
|
||||
-- This isn't possible inside Roblox, but works just fine in Remodel!
|
||||
outputGame.Workspace.Terrain:Destroy()
|
||||
inputGame.Workspace.Terrain.Parent = outputGame.Workspace
|
||||
|
||||
remodel.writePlaceFile("output-place-updated.rbxlx", outputGame)
|
||||
```
|
||||
|
||||
### Step 3
|
||||
|
||||
Finally, run the script you've created by providing the script name to Lune, in our case `example`,
|
||||
without the luau file extension. Everything should work the same way it did when running natively in
|
||||
Remodel, now running in Lune 🚀
|
||||
|
||||
```sh copy
|
||||
lune example
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## API Differences
|
||||
|
||||
Since Lune is meant to be a general-purpose Luau runtime, it takes a different approach from Remodel
|
||||
in certain areas:
|
||||
|
||||
- Lune uses Luau instead of Lua 5.3.
|
||||
- APIs are more loosely coupled, for example reading a Roblox place file is separated into two
|
||||
steps - reading the actual file using Lune's `fs` built-in library, and then deserializing that
|
||||
file using the `roblox` built-in library.
|
||||
- Lune tries to support many more formats and use cases - while Remodel has the `JSON` global for
|
||||
converting to/from JSON specifically, Lune has the `serde` built-in library which can convert
|
||||
to/from JSON, YAML, TOML, compress and decompress files, and more.
|
||||
- Built-in libraries are not accessible from global variables, you have to explicitly import them
|
||||
using `require("@lune/library-name")`.
|
||||
- Arguments given to the script are not available in `...`, you have to import them using the
|
||||
`process` library:
|
||||
|
||||
```lua copy
|
||||
local process = require("@lune/process")
|
||||
|
||||
print(process.args) -- Same as print(...) in Remodel
|
||||
```
|
||||
|
||||
There are many more subtle differences between the two, not all of which are listed here.
|
80
pages/roblox/4-api-status.md
Normal file
80
pages/roblox/4-api-status.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
<!-- markdownlint-disable MD041 -->
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
# API Status
|
||||
|
||||
This is a page indicating the current implementation status for instance methods and datatypes in
|
||||
the `roblox` library.
|
||||
|
||||
If an API on a class is not listed here it may not be within the scope for Lune and may not be
|
||||
implemented in the future. <br /> However, if a recently added datatype is missing, and it can be
|
||||
used as an instance property, it is likely that it will be implemented.
|
||||
|
||||
## Classes
|
||||
|
||||
### `Instance`
|
||||
|
||||
Currently implemented APIs:
|
||||
|
||||
- [`new`](https://create.roblox.com/docs/reference/engine/datatypes/Instance#new) - note that this
|
||||
does not include the second `parent` argument
|
||||
- [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
|
||||
- [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
|
||||
- [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
|
||||
- [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)
|
||||
- [`FindFirstAncestor`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestor)
|
||||
- [`FindFirstAncestorOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorOfClass)
|
||||
- [`FindFirstAncestorWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorWhichIsA)
|
||||
- [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild)
|
||||
- [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass)
|
||||
- [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA)
|
||||
- [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
|
||||
- [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
|
||||
- [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
|
||||
- [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
|
||||
- [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
|
||||
- [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
|
||||
- [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
|
||||
- [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
|
||||
- [`IsAncestorOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsAncestorOf)
|
||||
- [`IsDescendantOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsDescendantOf)
|
||||
- [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
|
||||
- [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
|
||||
|
||||
### `DataModel`
|
||||
|
||||
Currently implemented APIs:
|
||||
|
||||
- [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
|
||||
- [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
|
||||
|
||||
## Datatypes
|
||||
|
||||
Currently implemented datatypes:
|
||||
|
||||
- [`Axes`](https://create.roblox.com/docs/reference/engine/datatypes/Axes)
|
||||
- [`BrickColor`](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor)
|
||||
- [`CFrame`](https://create.roblox.com/docs/reference/engine/datatypes/CFrame)
|
||||
- [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3)
|
||||
- [`ColorSequence`](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence)
|
||||
- [`ColorSequenceKeypoint`](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint)
|
||||
- [`Enum`](https://create.roblox.com/docs/reference/engine/datatypes/Enum)
|
||||
- [`Faces`](https://create.roblox.com/docs/reference/engine/datatypes/Faces)
|
||||
- [`Font`](https://create.roblox.com/docs/reference/engine/datatypes/Font)
|
||||
- [`NumberRange`](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange)
|
||||
- [`NumberSequence`](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence)
|
||||
- [`NumberSequenceKeypoint`](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint)
|
||||
- [`PhysicalProperties`](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties)
|
||||
- [`Ray`](https://create.roblox.com/docs/reference/engine/datatypes/Ray)
|
||||
- [`Rect`](https://create.roblox.com/docs/reference/engine/datatypes/Rect)
|
||||
- [`Region3`](https://create.roblox.com/docs/reference/engine/datatypes/Region3)
|
||||
- [`Region3int16`](https://create.roblox.com/docs/reference/engine/datatypes/Region3int16)
|
||||
- [`UDim`](https://create.roblox.com/docs/reference/engine/datatypes/UDim)
|
||||
- [`UDim2`](https://create.roblox.com/docs/reference/engine/datatypes/UDim2)
|
||||
- [`Vector2`](https://create.roblox.com/docs/reference/engine/datatypes/Vector2)
|
||||
- [`Vector2int16`](https://create.roblox.com/docs/reference/engine/datatypes/Vector2int16)
|
||||
- [`Vector3`](https://create.roblox.com/docs/reference/engine/datatypes/Vector3)
|
||||
- [`Vector3int16`](https://create.roblox.com/docs/reference/engine/datatypes/Vector3int16)
|
||||
|
||||
Note that these datatypes are kept as up-to-date as possible, but recently added members & methods
|
||||
may be missing.
|
6
pages/roblox/_meta.json
Normal file
6
pages/roblox/_meta.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"1-introduction": "Introduction",
|
||||
"2-examples": "Examples",
|
||||
"3-remodel-migration": "Migrating from Remodel",
|
||||
"4-api-status": "API Status"
|
||||
}
|
9
stylua.toml
Normal file
9
stylua.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
column_width = 100
|
||||
line_endings = "Unix"
|
||||
indent_type = "Tabs"
|
||||
indent_width = 4
|
||||
quote_style = "AutoPreferDouble"
|
||||
call_parentheses = "Always"
|
||||
|
||||
[sort_requires]
|
||||
enabled = true
|
19
theme.config.jsx
Normal file
19
theme.config.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from "react"
|
||||
|
||||
export default {
|
||||
logo: <span>Lune Documentation</span>,
|
||||
docsRepositoryBase: "https://github.com/lune-org/docs/blob/main/pages",
|
||||
project: {
|
||||
link: "https://github.com/lune-org/lune",
|
||||
},
|
||||
footer: {
|
||||
text: (
|
||||
<span>
|
||||
MPL-2.0 {new Date().getFullYear()} ©{" "}
|
||||
<a href="https://github.com/lune-org/lune" target="_blank">
|
||||
Lune
|
||||
</a>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
}
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in a new issue