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