Initial commit

This commit is contained in:
Filip Tibell 2023-07-22 14:25:44 +02:00
commit 4a9aa952ca
No known key found for this signature in database
40 changed files with 12196 additions and 0 deletions

16
.editorconfig Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

30
package.json Normal file
View 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
View file

@ -0,0 +1,6 @@
{
"index": "Home",
"getting-started": "Getting Started",
"roblox": "Roblox",
"api-reference": "API Reference"
}

View 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
View 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
View 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.
---

View 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
---

View 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
---

View 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
---

View 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
View 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
---

View 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.

View 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.

View 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._

View 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>

View 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
View 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!

View 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.

View 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")
```

View 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.

View 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
View 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
View 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
View 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
View 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"]
}