# Charm

    Atomic state management for Roblox.

    npm package →

**Charm** is an atomic and immutable state management library, inspired by [Jotai](https://jotai.org) and [Nanostores](https://github.com/nanostores/nanostores). Store your state in atoms, and write your own functions to read, write, and observe state.

See an example of Charm's features in [this example repository](https://github.com/littensy/charm-example).

## 🍀 Features

-   ⚛ **Manage state with _atoms_.** Decompose state into independent containers called _atoms_, as opposed to combining them into a single store.

-   💪 **Minimal, yet powerful.** Less boilerplate — write simple functions to read from and write to state.

-   🔬 **Immediate updates.** Listeners run asynchronously by default, avoiding the cascading effects of deferred updates and improving responsiveness.

-   🦄 **Like magic.** Selector functions can be subscribed to as-is — with implicit dependency tracking, atoms are captured and memoized for you.

## 📦 Setup

Install Charm for roblox-ts using your package manager of choice.

```sh
npm install @rbxts/charm
yarn add @rbxts/charm
pnpm add @rbxts/charm

Alternatively, add `littensy/charm` to your `wally.toml` file.

```toml
[dependencies]
Charm = "littensy/charm@VERSION"

## 🐛 Debugging

Charm provides a debug mode to help you identify potential bugs in your project. To enable debug mode, set the global `_G.__DEV__` flag to `true` before importing Charm.

Enabling `__DEV__` adds a few helpful features:

-   Better error handling for selectors, subscriptions, and batched functions:

    -   Errors provide the function's name and line number.
    -   Yielding in certain functions will throw an error.

-   Server state is validated for [remote event limitations](https://create.roblox.com/docs/scripting/events/remote#argument-limitations) before being passed to the client.

Enabling debug mode in unit tests, storybooks, and other development environments can help you catch potential issues early. However, remember to turn off debug mode in production to avoid the performance overhead.

## 📚 Reference

### `atom(state, options?)`

Atoms are the building blocks of Charm. They are functions that hold a single value, and calling them can read or write to that value. Atoms, or any function that reads from atoms, can also be [subscribed](#subscribecallback-listener) to.

Call `atom` to create a state container initialized with the value `state`.

```luau
local nameAtom = atom("John")
local todosAtom: Atom<{ string }> = atom({})

#### Parameters

-   `state`: The value to assign to the atom initially.

-   **optional** `options`: An object that configures the behavior of this atom.

    -   **optional** `equals`: An equality function to determine whether the state has changed. By default, strict equality (`==`) is used.

#### Returns

The `atom` constructor returns an atom function with two possible operations:

1. **Read the state.** Call the atom without arguments to get the current state.
2. **Set the state.** Pass a new value or an updater function to change the state.

```luau
local function newTodo()
    nameAtom("Jane")

    todosAtom(function(todos)
        todos = table.clone(todos)
        table.insert(todos, "Buy milk")
        return todos
    end)

    print(nameAtom()) --> "Jane"
end

### `subscribe(callback, listener)`

Call `subscribe` to listen for changes in an atom or selector function. When the function's result changes, subscriptions are immediately called with the new state and the previous state.

```luau
local nameAtom = atom("John")

local cleanup = subscribe(nameAtom, function(name, prevName)
    print(name)
end)

nameAtom("Jane") --> "Jane"

You may also pass a selector function that calls other atoms. The function will be memoized and only runs when its atom dependencies update.

```luau
local function getUppercase()
    return string.upper(nameAtom())
end

local cleanup = subscribe(getUppercase, function(name)
    print(name)
end)

nameAtom("Jane") --> "JANE"

#### Parameters

-   `callback`: The function to subscribe to. This may be an atom or a selector function that depends on an atom.

-   `listener`: The listener is called when the result of the callback changes. It receives the new state and the previous state as arguments.

#### Returns

`subscribe` returns a cleanup function.

### `effect(callback)`

Call `effect` to track state changes in all atoms read within the callback. The callback will run once to retrieve its dependencies, and then again whenever they change. Your callback may return a cleanup function to run when the effect is removed or about to re-run.

```luau
local nameAtom = atom("John")

local cleanup = effect(function()
    print(nameAtom())
    return function()
        print("Cleanup function called!")
    end
end)

Because `effect` implicitly tracks all atoms read within the callback, it might be useful to exclude atoms that should not trigger a re-run. You can use [`peek`](#peekvalue) to read from atoms without tracking them as dependencies.

#### Parameters

-   `callback`: The function to track for state changes. The callback will run once to retrieve its dependencies, and then again whenever they change.

#### Returns

`effect` returns a cleanup function.

#### Caveats

-   **If your effect should disconnect itself, use the `cleanup` argument.** Because effects run immediately, your effect may run before a `cleanup` function is returned. To disconnect an effect from the inside, use the argument passed to your effect instead:
    ```lua
    effect(function(cleanup)
        if condition() then
            cleanup()
        end
    end)

### `computed(callback, options?)`

Call `computed` when you want to derive a new atom from one or more atoms. The callback will be memoized, meaning that subsequent calls to the atom return a cached value that is only re-calculated when the dependencies change.

```luau
local todosAtom: Atom<{ string }> = atom({})
local mapToUppercase = computed(function()
    local result = table.clone(todosAtom())
    for key, todo in result do
        result[key] = string.upper(todo)
    end
    return result
end)

Because `computed` implicitly tracks all atoms read within the callback, it might be useful to exclude atoms that should not trigger a re-run. You can use [`peek`](#peekvalue) to read from atoms without tracking them as dependencies.

This function is also useful for optimizing `effect` calls that depend on multiple atoms. For instance, if an effect derives some value from two atoms, it will run twice if both atoms change at the same time. Using `computed` can group these dependencies together and avoid re-running side effects.

#### Parameters

-   `callback`: A function that returns a new value depending on one or more atoms.

-   **optional** [`options`](#parameters): An object that configures the behavior of this atom.

#### Returns

`computed` returns a read-only atom.

### `observe(callback, factory)`

Call `observe` to create an instance of `factory` for each key present in a dictionary or array. Your factory can return a cleanup function to run when the key is removed or the observer is cleaned up.

> [!NOTE]
> Because `observe` tracks the lifetime of each key in your data, your keys must be unique and unchanging. If your data is not keyed by unique and stable identifiers, consider using [`mapped`](#mappedcallback-mapper) to transform it into a keyed object before passing it to `observe`.

```luau
local todosAtom: Atom<{ [string]: Todo }> = atom({})

local cleanup = observe(todosAtom, function(todo, key)
    print(`Added {key}: {todo.name}`)
    return function()
        print(`Removed {key}`)
    end
end)

#### Parameters

-   `callback`: An atom or selector function that returns a dictionary or an array of values. When a key is added to the state, the factory will be called with the new key and its initial value.

-   `factory`: A function that will be called whenever a key is added or removed from the atom's state. The callback will receive the key and the entry's initial value as arguments, and may return a cleanup function.

#### Returns

`observe` returns a cleanup function.

### `mapped(callback, mapper)`

Call `mapped` to transform the keys and values of your state. The `mapper` function will be called for each key-value pair in the atom's state, and the new keys and atoms will be stored in a new atom.

```luau
local todosAtom: Atom<{ Todo }> = atom({})
local todosById = mapped(todosAtom, function(todo, index)
    return todo, todo.id
end)

#### Parameters

-   `callback`: The function whose result you want to map over. This can be an atom or a selector function that reads from atoms.

-   `mapper`: The mapper is called for each key in your state. Given the current value and key, it should return a new corresponding value and key:

    1. Return a single value to map the table's original key to a new value.
    2. Return two values, the first being the value and the second being the key, to update both keys and values.
    3. Return `nil` for the value to remove the key from the resulting table.

#### Returns

`mapped` returns a read-only atom.

### `peek(value, ...)`

Call `peek` to call a function without tracking it as the dependency of an effect or a selector function.

```luau
local nameAtom = atom("John")
local ageAtom = atom(25)

effect(function()
    local name = nameAtom()
    local age = peek(ageAtom)
end)

#### Parameters

-   `value`: Any value. If the value is a function, `peek` will call it without tracking dependencies and return the result.

-   **optional** `...args`: Additional arguments to pass to the value if it is a function.

#### Returns

`peek` returns the result of the given function. If the value is not a function, it will return the value as-is.

### `batch(callback)`

Call `batch` to defer state changes until after the callback has run. This is useful when you need to make multiple changes to the state and only want listeners to be notified once.

```luau
local nameAtom = atom("John")
local ageAtom = atom(25)

batch(function()
    nameAtom("Jane")
    ageAtom(26)
end)

#### Parameters

-   `callback`: A function that updates atoms. Listeners will only be notified once all changes have been applied.

#### Returns

`batch` does not return anything.

## 📦 React

### Setup

Install the React bindings for Charm using your package manager of choice.

```sh
npm install @rbxts/react-charm
yarn add @rbxts/react-charm
pnpm add @rbxts/react-charm

```toml
[dependencies]
ReactCharm = "littensy/react-charm@VERSION"

### `useAtom(callback, dependencies?)`

Call `useAtom` at the top-level of a React component to read from an atom or selector. The component will re-render when the value changes.

```luau
local todosAtom = require(script.Parent.todosAtom)

local function Todos()
    local todos = useAtom(todosAtom)
    -- ...
end

If your selector depends on the component's state or props, remember to pass them in a dependency array. This prevents skipped updates when an untracked parameter of the selector changes.

```luau
local todos = useAtom(function()
    return searchTodos(props.filter)
end, { props.filter })

#### Parameters

-   `callback`: An atom or selector function that depends on an atom.

-   **optional** `dependencies`: An array of outside values that the selector depends on. If the dependencies change, the subscription is re-created and the component re-renders with the new state.

#### Returns

`useAtom` returns the current state of the atom.

## 📦 Vide

### Setup

Install the Vide bindings for Charm using your package manager of choice.

```sh
npm install @rbxts/vide-charm
yarn add @rbxts/vide-charm
pnpm add @rbxts/vide-charm

```toml
[dependencies]
VideCharm = "littensy/vide-charm@VERSION"

### `useAtom(callback)`

Call `useAtom` in any scope to create a Vide source that returns the current state of an atom or selector.

```luau
local todosAtom = require(script.Parent.todosAtom)

local function Todos()
    local todos = useAtom(todosAtom)
    -- ...
end

#### Parameters

-   `callback`: An atom or selector function that depends on an atom.

#### Returns

`useAtom` returns a Vide source.

## 📗 Charm Sync

### Setup

The Charm Sync package provides server-client synchronization for your Charm atoms. Install it using your package manager of choice.

```sh
npm install @rbxts/charm-sync
yarn add @rbxts/charm-sync
pnpm add @rbxts/charm-sync

```toml
[dependencies]
CharmSync = "littensy/charm-sync@VERSION"

### `server(options)`

Call `server` to create a server sync object. This synchronizes every client's atoms with the server's state by sending partial patches that the client merges into its state.

```luau
local syncer = CharmSync.server({
    -- A dictionary of the atoms to sync, matching the client's
    atoms = atomsToSync,
    -- The minimum interval between state updates
    interval = 0,
    -- Whether to send a full history of changes made to the atoms (slower)
    preserveHistory = false,
    -- Whether to apply fixes for remote event limitations. Disable this option
    -- when using a network library with custom ser/des, like ByteNet or Zap.
    autoSerialize = true,
})

-- Sends state updates to clients when a synced atom changes.
-- Omitting sensitive information and data serialization can be done here.
syncer:connect(function(player, ...)
    remotes.syncState:fire(player, ...)
end)

-- Sends the initial state to a player upon request. This should fire when a
-- player joins the game.
remotes.requestState:connect(function(player)
    syncer:hydrate(player)
end)

#### Parameters

-   `options`: An object to configure sync behavior.

    -   `atoms`: A dictionary of the atoms to sync. The keys should match the keys on the client.

    -   **optional** `interval`: The interval at which to batch state updates to clients. Defaults to `0`, meaning updates are batched every frame.

    -   **optional** `preserveHistory`: Whether to sync an exhaustive history of changes made to the atoms since the last sync event. If `true`, the server sends multiple payloads instead of one. Defaults to `false` for performance.

    -   **optional** `autoSerialize`: Whether to apply validation and workarounds to certain [remote argument limitations](https://create.roblox.com/docs/scripting/events/remote#table-indexing). Defaults to `true`, but you should set it to `false` if you serialize remote arguments (i.e. if you use [ByteNet](https://github.com/ffrostfall/ByteNet) or [Zap](https://github.com/red-blox/zap)).

> [!NOTE]
> Charm sends table updates in the form of partial tables, so arrays will contain `nil` values, which has undefined behavior in remotes without serialization.
> Charm's default `autoSerialize` behavior fixes this, but it can interfere with custom serialization. Disable this option if you use a network library that serializes remote event arguments.

#### Returns

`server` returns an object with the following methods:

-   `syncer:connect(callback)`: Registers a callback to send state updates to clients. The callback will receive the player and the payload(s) to send, and should fire a remote event. The payload is read-only, so any changes should be applied to a copy of the payload.

-   `syncer:hydrate(player)`: Sends the player a full state update for all synced atoms.

#### Caveats

-   **Do not use values that cannot be sent over remotes** in your shared atoms. This includes functions, threads, and non-string keys in dictionaries. [Read more about argument limitations in remotes.](https://create.roblox.com/docs/scripting/events/remote#argument-limitations)

-   **By default, Charm omits the individual changes made to atoms** between sync events (i.e. a `counterAtom` set to `1` and then `2` will only send the final state of `2`). If you need to preserve a history of changes, set `preserveHistory` to `true`.

-   **Charm does not handle network communication.** Use remote events or a network library to send sync payloads - and remember to set `autoSerialize` accordingly!

### `client(options)`

Call `client` to create a client sync object. This synchronizes the client's atoms with the server's state by merging partial patches sent by the server into each atom.

```luau
local syncer = CharmSync.client({
    atoms = atomsToSync, -- A dictionary of the atoms to sync, matching the server's
    ignoreUnhydrated = true, -- Whether to ignore state updates before the initial update
})

-- Applies state updates from the server to the client's atoms.
-- Data deserialization can be done here.
remotes.syncState:connect(function(...)
    syncer:sync(...)
end)

-- Requests the initial state from the server when the client joins the game.
-- Before this runs, the client uses the atoms' default values.
remotes.requestState:fire()

#### Parameters

-   `options`: An object to configure sync behavior.

    -   `atoms`: A dictionary of the atoms to sync. The keys should match the keys on the server.

    -   **optional** `ignoreUnhydrated`: Whether to ignore state updates before setting the initial state. Defaults to `true`.

#### Returns

`client` returns an object with the following methods:

-   `syncer:sync(...payloads)` applies a state update from the server.

#### Caveats

-   **Charm does not handle network communication.** Use remote events or a network library to receive sync payloads. This includes requesting the initial state, which is implemented via `requestState` in the example above.

### `isNone(value)`

Call `isNone` to check if a value is `None`. Charm's partial state patches omit values that did not change between sync events, so to mark keys for deletion, Charm uses the `None` marker.

This function can be used to check whether a value is about to be removed from an atom.

```luau
local syncer = CharmSync.server({ atoms = atomsToSync })

syncer:connect(function(player, payload)
    if
        payload.type === "patch"
        and payload.data.todosAtom
        and CharmSync.isNone(payload.data.todosAtom.eggs)
    then
        -- 'eggs' will be removed from the client's todo list
    end
    remotes.syncState.fire(player, payload)
end)

#### Parameters

-   `value`: Any value. If the value is `None`, `isNone` will return `true`.

#### Returns

`isNone` returns a boolean.

## 🚀 Examples

### Counter atom

```luau
local counterAtom = atom(0)

-- Create a selector that returns double the counter value
local function doubleCounter()
    return counterAtom() * 2
end

-- Runs after counterAtom is updated and prints double the new value
subscribe(doubleCounter, function(value)
    print(value)
end)

counterAtom(1) --> 2
counterAtom(function(count)
    return count + 1
end) --> 4

### React component

```luau
local counter = require(script.Parent.counter)
local counterAtom = counter.counterAtom
local incrementCounter = counter.incrementCounter

local function Counter()
    local count = useAtom(counterAtom)

    return React.createElement("TextButton", {
        [React.Event.Activated] = incrementCounter,
        Text = `Count: {count}`,
        Size = UDim2.new(0, 100, 0, 50),
    })
end

### Vide component

```luau
local counter = require(script.Parent.counter)
local counterAtom = counter.counterAtom
local incrementCounter = counter.incrementCounter

local function Counter()
    local count = useAtom(counterAtom)

    return create "TextButton" {
        Activated = incrementCounter,
        Text = function()
            return `Count: {count()}`
        end,
        Size = UDim2.new(0, 100, 0, 50),
end

### Server-client sync

Charm is designed for both client and server use, but there are often cases where the client needs to reference state that lives on the server. The CharmSync package provides a way to synchronize atoms between the server and its clients using remote events.

Start by creating a set of atoms to sync between the server and clients. Export these atoms from a module to be shared between the server and client:

```luau
-- atoms.luau
local counter = require(script.Parent.counter)
local todos = require(script.Parent.todos)

return {
    counterAtom = counter.counterAtom,
    todosAtom = todos.todosAtom,

Then, on the server, create a server sync object and pass in the atoms to sync. Use remote events to broadcast state updates and send initial state to clients upon request.

> [!NOTE]
> If `preserveHistory` is `true`, the server will send multiple payloads to the client, so the callback passed to `connect` should accept a variadic `...payloads` parameter. Otherwise, you only need to handle a single `payload` parameter.

```luau
-- sync.server.luau
local atoms = require(script.Parent.atoms)

local syncer = CharmSync.server({ atoms = atoms })

-- Sends state updates to clients when a synced atom changes.
-- Omitting sensitive information and data serialization can be done here.
syncer:connect(function(player, payload)
    remotes.syncState.fire(player, payload)
end)

-- Sends the initial state to a player upon request. This should fire when a
-- player joins the game.
remotes.requestState:connect(function(player)
    syncer:hydrate(player)
end)

Finally, on the client, create a client sync object and use it to apply incoming state changes.

```luau
-- sync.client.luau
local atoms = require(script.Parent.atoms)

local syncer = CharmSync.client({ atoms = atoms })

-- Applies state updates from the server to the client's atoms.
remotes.syncState:connect(function(payload)
    syncer:sync(payload)
end)

-- Requests the initial state from the server when the client joins the game.
-- Before this runs, the client uses the atoms' default values.
remotes.requestState:fire()

Charm is released under the MIT License.


Rblx-to-Local-Files is a software to export your **.rbxl** file to separate local files.

Tips:
1. Separate the service names with a space
2. If you type "all" in the services list, it will export all services visible in the Roblox Studio explorer

Example video (Youtube): [Exporting .rbxl to local files and default.project.json](https://www.youtube.com/watch?v=16LU1snWPOQ)

##### Download [here](https://github.com/sayderkonkest/Rbxl-to-Local-Files/releases).
##### Made with [Lune](https://lune-org.github.io/docs/).


# Lune

A standalone [Luau](https://luau-lang.org) runtime.

Write and run programs, similar to runtimes for other languages such as [Node](https://nodejs.org), [Deno](https://deno.land), [Bun](https://bun.sh), or [Luvit](https://luvit.io) for vanilla Lua.

Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for speed, safety and correctness.

## Features

Section titled “Features”

  * 🌙 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 (~5mb zipped) executable
  * 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
  * 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
  * ✏ Optional standard library for manipulating Roblox place & model files, and their instances

## Non-goals

Section titled “Non-goals”

  * Making programs short and terse - proper autocomplete / intellisense make using Lune just as quick, and readability is important
  * Running full Roblox games outside of Roblox - there is some compatibility, but Lune is meant for different purposes

## Where do I start?

Section titled “Where do I start?”

Head over to the [Installation](https://lune-org.github.io/docs/getting-started/1-installation) page to get started using Lune!


# Hello, Lune!

Welcome to The Lune Book! Now that you have Lune installed, you’re ready to start writing scripts and exploring what makes Lune special.

If you’ve written Lua or Luau scripts before, you’ll feel right at home with the examples in this book. Even if you haven’t, don’t worry - this guide will take you through everything step by step.

The chapters in this book are organized to build on each other, starting with the basics, and gradually introducing more powerful features. Here’s a quick overview:

  1. [Hello, Lune!](https://lune-org.github.io/docs/the-book/1-hello-lune) _(you are here)_
  2. [The Standard Library](https://lune-org.github.io/docs/the-book/2-standard-library)
  3. [Input & Output](https://lune-org.github.io/docs/the-book/3-input-output)
  4. [Arguments](https://lune-org.github.io/docs/the-book/4-arguments)
  5. [Networking](https://lune-org.github.io/docs/the-book/5-networking)
  6. [Working with Files](https://lune-org.github.io/docs/the-book/6-working-with-files)
  7. [Modules](https://lune-org.github.io/docs/the-book/7-modules)
  8. [Spawning Programs](https://lune-org.github.io/docs/the-book/8-spawning-programs)
  9. [Task Scheduler](https://lune-org.github.io/docs/the-book/9-task-scheduler)

By the end of this book, you’ll understand how to use Lune for everything from simple automation scripts to complex networking applications. Let’s get started!


# Security

When running Lune scripts, it’s important to know that any scripts you execute have full access to your device - this means access to your files, programs, and more. It is generally good to be cautious when running scripts from sources you don’t trust.

Here are some ways to run untrusted scripts more safely:

  1. Running Lune scripts in a custom sandboxed environment
  2. Using a containerized environment such as Docker
  3. Using a virtual machine

## Sandboxing

Section titled “Sandboxing”

Lune provides a basic but functional sandboxing example. We’ll show you how to use it here, but for production use and proper security, using a container or virtual machine is highly recommended.

  1. Copy the [sandbox module](https://github.com/lune-org/docs/blob/main/modules/sandbox.luau) and save it as `sandbox.luau`.

  2. Place the untrusted script you want to run next to the `sandbox.luau` file.

Terminal

         lune run sandbox.luau script.luau -- [ARGUMENTS_HERE]

Replace `script.luau` and `[ARGUMENTS_HERE]` with the path to your script and any arguments you want to pass to it.

  3. As the script runs, any attempts to access potentially dangerous modules will require your approval before continuing. Any method calls within approved modules will be logged.

The output from the sandbox script and the script being run will be clearly separated, so you can see what’s happening.


# Task

Built-in task scheduler & thread spawning

#### Example usage

Section titled “Example usage”

    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

Section titled “Functions”

### cancel

Section titled “cancel”

Stops a currently scheduled thread from resuming.

#### Parameters

Section titled “Parameters”

  * `thread` The thread to cancel

### defer

Section titled “defer”

Defers a thread or function to run at the end of the current task queue.

#### Parameters

Section titled “Parameters”

  * `functionOrThread` The function or thread to defer

  * `...` T…

#### Returns

Section titled “Returns”

  * The thread that will be deferred

### delay

Section titled “delay”

Delays a thread or function to run after `duration` seconds.

#### Parameters

Section titled “Parameters”

  * `duration` number

  * `functionOrThread` The function or thread to delay

  * `...` T…

#### Returns

Section titled “Returns”

  * The thread that will be delayed

### spawn

Section titled “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

Section titled “Parameters”

  * `functionOrThread` The function or thread to spawn

  * `...` T…

#### Returns

Section titled “Returns”

  * The thread that was spawned

### wait

Section titled “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

Section titled “Parameters”

  * `duration` The amount of time to wait

#### Returns

Section titled “Returns”

  * The exact amount of time waited


# FS

Built-in library for filesystem access

#### Example usage

Section titled “Example usage”

    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

Section titled “Functions”

### readFile

Section titled “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.
  * Some other I/O error occurred.

#### Parameters

Section titled “Parameters”

  * `path` The path to the file to read

#### Returns

Section titled “Returns”

  * The contents of the file

### readDir

Section titled “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

Section titled “Parameters”

  * `path` The directory path to search in

#### Returns

Section titled “Returns”

  * A list of files & directories found

### writeFile

Section titled “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

Section titled “Parameters”

  * `path` The path of the file

  * `contents` The contents of the file

### writeDir

Section titled “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

Section titled “Parameters”

  * `path` The directory to create

### removeFile

Section titled “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

Section titled “Parameters”

  * `path` The file to remove

### removeDir

Section titled “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

Section titled “Parameters”

  * `path` The directory to remove

### metadata

Section titled “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

Section titled “Parameters”

  * `path` The path to get metadata for

#### Returns

Section titled “Returns”

  * Metadata for the path

### isFile

Section titled “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

Section titled “Parameters”

  * `path` The file path to check

#### Returns

Section titled “Returns”

  * If the path is a file or not

### isDir

Section titled “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

Section titled “Parameters”

  * `path` The directory path to check

#### Returns

Section titled “Returns”

  * If the path is a directory or not

### move

Section titled “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

Section titled “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

Section titled “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

Section titled “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

Section titled “Types”

### MetadataPermissions

Section titled “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

Section titled “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 represented as a `DateTime` object at which the file or directory was created
  * `modifiedAt` \- The timestamp represented as a `DateTime` object at which the file or directory was last modified
  * `accessedAt` \- The timestamp represented as a `DateTime` object 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

Section titled “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


# Input & Output

Let’s start exploring Lune’s capabilities with the `stdio` library, which handles standard input and output. This is one of the most useful libraries for creating interactive scripts.

## Getting User Input

Section titled “Getting User Input”

The `stdio` library makes it easy to interact with users. Let’s create our first interactive script called `hello.luau`:

hello.luau

    local stdio = require("@lune/stdio")

    local name = stdio.prompt("text", "What's your name?")

    print(`Hello, {name}!`)

Save this file in your current directory, then run it using the Lune CLI:

Terminal

    lune run hello

When you run this script, it will wait for you to type your name and press Enter, then greet you!

## Different Types of Prompts

Section titled “Different Types of Prompts”

The `stdio` library can prompt for more than just text. Let’s expand our script to ask a yes/no question:

hello.luau

    local stdio = require("@lune/stdio")

    local name = stdio.prompt("text", "What's your name?")

    print(`Hello, {name}!`)

    local confirmed = stdio.prompt("confirm", "Is that really your name?")

    if confirmed then

      print(`Nice to meet you, {name}!`)

      print("Have a great day!")

    else

      print("Hmm, well whoever you are, welcome!")

    end

The `confirm` prompt type shows a yes/no question and returns `true` or `false` based on the user’s choice.

These two prompt types - `text` and `confirm` \- will cover most of your interactive script needs. There are more advanced options available in the [stdio API reference](https://lune-org.github.io/docs/api-reference/stdio) when you need them.

Extra: Number Guessing Game

## Building a Simple Game

Section titled “Building a Simple Game”

Here’s a fun example that combines what we’ve learned with some basic Luau programming. Don’t worry if you don’t understand every line yet - focus on how `stdio.prompt` is used to create an interactive experience:

guessing-game.luau

    local stdio = require("@lune/stdio")

    print("Welcome to the number guessing game!")

    print("I'm thinking of a number between 1 and 10.")

    print()

    local answer = tostring(math.random(1, 10))

    local attempts = 0

    local guess = stdio.prompt("text", "What's your guess?")

    attempts += 1

    while guess ~= answer do

      if tonumber(guess) and tonumber(guess) < tonumber(answer) then

        print("Too low! Try again.")

      elseif tonumber(guess) and tonumber(guess) > tonumber(answer) then

        print("Too high! Try again.")

      else

        print("That's not a valid number.")

      end

      guess = stdio.prompt("text", "What's your guess?")

      attempts += 1

    end

    print()

    print(`Correct! You got it in {attempts} attempts!`)

Try running this game and see if you can guess the number! This example shows how `stdio.prompt` can be used in a loop to create ongoing interaction.

Terminal

    lune run guessing-game

## What’s Next?

Section titled “What’s Next?”

Now that you know how to get input from users, let’s learn about handling input that comes from the command line when someone runs your script. This is covered in the next chapter on [Arguments](https://lune-org.github.io/docs/the-book/3-input-output/4-arguments).


# DateTime

Built-in library for date & time

#### Example usage

Section titled “Example usage”

    local DateTime = require("@lune/datetime")

    -- Creates a DateTime for the current exact moment in time

    local now = DateTime.now()

    -- Formats the current moment in time as an RFC 3339 string

    print(now:toRfc3339())

    -- Formats the current moment in time as an RFC 2822 string

    print(now:toRfc2822())

    -- Formats the current moment in time, using the local

    -- time, the French locale, and the specified time string

    print(now:formatLocalTime("%A, %d %B %Y", "fr"))

    -- Returns a specific moment in time as a DateTime instance

    local someDayInTheFuture = DateTime.fromLocalTime({

      year = 3033,

      month = 8,

      day = 26,

      hour = 16,

      minute = 56,

      second = 28,

      millisecond = 892,

    })

    -- Extracts the current local date & time as separate values (same values as above table)

    print(now:toLocalTime())

    -- Returns a DateTime instance from a given float, where the whole

    -- denotes the seconds and the fraction denotes the milliseconds

    -- Note that the fraction for millis here is completely optional

    DateTime.fromUnixTimestamp(871978212313.321)

    -- Extracts the current universal (UTC) date & time as separate values

    print(now:toUniversalTime())

## Properties

Section titled “Properties”

### unixTimestamp

Section titled “unixTimestamp”

`number`

Number of seconds passed since the UNIX epoch.

### unixTimestampMillis

Section titled “unixTimestampMillis”

`number`

Number of milliseconds passed since the UNIX epoch.

## Constructors

Section titled “Constructors”

### now

Section titled “now”

Returns a `DateTime` representing the current moment in time.

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromUnixTimestamp

Section titled “fromUnixTimestamp”

Creates a new `DateTime` from the given UNIX timestamp.

This timestamp may contain both a whole and fractional part - where the fractional part denotes milliseconds / nanoseconds.

Example usage of fractions:

  * `DateTime.fromUnixTimestamp(123456789.001)` \- one millisecond
  * `DateTime.fromUnixTimestamp(123456789.000000001)` \- one nanosecond

Note that the fractional part has limited precision down to exactly one nanosecond, any fraction that is more precise will get truncated.

#### Parameters

Section titled “Parameters”

  * `unixTimestamp` `number` Seconds passed since the UNIX epoch

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromUniversalTime

Section titled “fromUniversalTime”

Creates a new `DateTime` from the given date & time values table, in universal (UTC) time.

The given table must contain the following values:

Key| Type| Range
---|---|---
`year`| `number`| `1400 -> 9999`
`month`| `number`| `1 -> 12`
`day`| `number`| `1 -> 31`
`hour`| `number`| `0 -> 23`
`minute`| `number`| `0 -> 59`
`second`| `number`| `0 -> 60`

An additional `millisecond` value may also be included, and should be within the range `0 -> 999`, but is optional.

Any non-integer values in the given table will be rounded down.

#### Errors

Section titled “Errors”

This constructor is fallible and may throw an error in the following situations:

  * Date units (year, month, day) were given that produce an invalid date. For example, January 32nd or February 29th on a non-leap year.

#### Parameters

Section titled “Parameters”

  * `values` `DateTimeValueArguments` Table containing date & time values

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromLocalTime

Section titled “fromLocalTime”

Creates a new `DateTime` from the given date & time values table, in local time.

The given table must contain the following values:

Key| Type| Range
---|---|---
`year`| `number`| `1400 -> 9999`
`month`| `number`| `1 -> 12`
`day`| `number`| `1 -> 31`
`hour`| `number`| `0 -> 23`
`minute`| `number`| `0 -> 59`
`second`| `number`| `0 -> 60`

An additional `millisecond` value may also be included, and should be within the range `0 -> 999`, but is optional.

Any non-integer values in the given table will be rounded down.

#### Errors

Section titled “Errors”

This constructor is fallible and may throw an error in the following situations:

  * Date units (year, month, day) were given that produce an invalid date. For example, January 32nd or February 29th on a non-leap year.

#### Parameters

Section titled “Parameters”

  * `values` `DateTimeValueArguments` Table containing date & time values

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromIsoDate

Section titled “fromIsoDate”

**DEPRECATED**: Use `DateTime.fromRfc3339` instead.

Creates a new `DateTime` from an ISO 8601 date-time string.

#### Errors

Section titled “Errors”

This constructor is fallible and may throw an error if the given string does not strictly follow the ISO 8601 date-time string format.

Some examples of valid ISO 8601 date-time strings are:

  * `2020-02-22T18:12:08Z`
  * `2000-01-31T12:34:56+05:00`
  * `1970-01-01T00:00:00.055Z`

#### Parameters

Section titled “Parameters”

  * `isoDate` `string` An ISO 8601 formatted string

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromRfc3339

Section titled “fromRfc3339”

Creates a new `DateTime` from an RFC 3339 date-time string.

#### Errors

Section titled “Errors”

This constructor is fallible and may throw an error if the given string does not strictly follow the RFC 3339 date-time string format.

Some examples of valid RFC 3339 date-time strings are:

  * `2020-02-22T18:12:08Z`
  * `2000-01-31T12:34:56+05:00`
  * `1970-01-01T00:00:00.055Z`

#### Parameters

Section titled “Parameters”

  * `rfc3339Date` `string` An RFC 3339 formatted string

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

### fromRfc2822

Section titled “fromRfc2822”

Creates a new `DateTime` from an RFC 2822 date-time string.

#### Errors

Section titled “Errors”

This constructor is fallible and may throw an error if the given string does not strictly follow the RFC 2822 date-time string format.

Some examples of valid RFC 2822 date-time strings are:

  * `Fri, 21 Nov 1997 09:55:06 -0600`
  * `Tue, 1 Jul 2003 10:52:37 +0200`
  * `Mon, 23 Dec 2024 01:58:48 GMT`

#### Parameters

Section titled “Parameters”

  * `rfc2822Date` `string` An RFC 2822 formatted string

#### Returns

Section titled “Returns”

  * `DateTime` The new DateTime object

## Methods

Section titled “Methods”

### formatLocalTime

Section titled “formatLocalTime”

Formats this `DateTime` using the given `formatString` and `locale`, as local time.

The given `formatString` is parsed using a `strftime`/`strptime`-inspired date and time formatting syntax, allowing tokens such as the following:

Token| Example| Description
---|---|---
`%Y`| `1998`| Year number
`%m`| `04`| Month number
`%d`| `29`| Day number
`%A`| `Monday`| Weekday name
`%M`| `59`| Minute number
`%S`| `10`| Second number

For a full reference of all available tokens, see the [chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).

If not provided, `formatString` and `locale` will default to `"%Y-%m-%d %H:%M:%S"` and `"en"` (english) respectively.

#### Parameters

Section titled “Parameters”

  * `self` DateTime

  * `formatString` `string?` A string containing formatting tokens

  * `locale` `Locale?` The locale the time should be formatted in

#### Returns

Section titled “Returns”

  * `string` The formatting string

### formatUniversalTime

Section titled “formatUniversalTime”

Formats this `DateTime` using the given `formatString` and `locale`, as UTC (universal) time.

The given `formatString` is parsed using a `strftime`/`strptime`-inspired date and time formatting syntax, allowing tokens such as the following:

Token| Example| Description
---|---|---
`%Y`| `1998`| Year number
`%m`| `04`| Month number
`%d`| `29`| Day number
`%A`| `Monday`| Weekday name
`%M`| `59`| Minute number
`%S`| `10`| Second number

For a full reference of all available tokens, see the [chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).

If not provided, `formatString` and `locale` will default to `"%Y-%m-%d %H:%M:%S"` and `"en"` (english) respectively.

#### Parameters

Section titled “Parameters”

  * `self` DateTime

  * `formatString` `string?` A string containing formatting tokens

  * `locale` `Locale?` The locale the time should be formatted in

#### Returns

Section titled “Returns”

  * `string` The formatting string

### toIsoDate

Section titled “toIsoDate”

**DEPRECATED**: Use `DateTime.toRfc3339` instead.

Formats this `DateTime` as an ISO 8601 date-time string.

Some examples of ISO 8601 date-time strings are:

  * `2020-02-22T18:12:08Z`
  * `2000-01-31T12:34:56+05:00`
  * `1970-01-01T00:00:00.055Z`

#### Parameters

Section titled “Parameters”

  * `self` DateTime

#### Returns

Section titled “Returns”

  * `string` The ISO 8601 formatted string

### toRfc2822

Section titled “toRfc2822”

Formats this `DateTime` as an RFC 2822 date-time string.

Some examples of RFC 2822 date-time strings are:

  * `Fri, 21 Nov 1997 09:55:06 -0600`
  * `Tue, 1 Jul 2003 10:52:37 +0200`
  * `Mon, 23 Dec 2024 01:58:48 GMT`

#### Parameters

Section titled “Parameters”

  * `self` DateTime

#### Returns

Section titled “Returns”

  * `string` The RFC 2822 formatted string

### toRfc3339

Section titled “toRfc3339”

Formats this `DateTime` as an RFC 3339 date-time string.

Some examples of RFC 3339 date-time strings are:

  * `2020-02-22T18:12:08Z`
  * `2000-01-31T12:34:56+05:00`
  * `1970-01-01T00:00:00.055Z`

#### Parameters

Section titled “Parameters”

  * `self` DateTime

#### Returns

Section titled “Returns”

  * `string` The RFC 3339 formatted string

### toLocalTime

Section titled “toLocalTime”

Extracts separated local date & time values from this `DateTime`.

The returned table contains the following values:

Key| Type| Range
---|---|---
`year`| `number`| `1400 -> 9999`
`month`| `number`| `1 -> 12`
`day`| `number`| `1 -> 31`
`hour`| `number`| `0 -> 23`
`minute`| `number`| `0 -> 59`
`second`| `number`| `0 -> 60`
`millisecond`| `number`| `0 -> 999`

#### Parameters

Section titled “Parameters”

  * `self` DateTime

#### Returns

Section titled “Returns”

  * `DateTimeValueReturns` A table of DateTime values

### toUniversalTime

Section titled “toUniversalTime”

Extracts separated UTC (universal) date & time values from this `DateTime`.

The returned table contains the following values:

Key| Type| Range
---|---|---
`year`| `number`| `1400 -> 9999`
`month`| `number`| `1 -> 12`
`day`| `number`| `1 -> 31`
`hour`| `number`| `0 -> 23`
`minute`| `number`| `0 -> 59`
`second`| `number`| `0 -> 60`
`millisecond`| `number`| `0 -> 999`

#### Parameters

Section titled “Parameters”

  * `self` DateTime

#### Returns

Section titled “Returns”

  * `DateTimeValueReturns` A table of DateTime values

## Types

Section titled “Types”

### Locale

Section titled “Locale”

Enum type representing supported DateTime locales.

Currently supported locales are:

  * `en` \- English
  * `de` \- German
  * `es` \- Spanish
  * `fr` \- French
  * `it` \- Italian
  * `ja` \- Japanese
  * `pl` \- Polish
  * `pt-br` \- Brazilian Portuguese
  * `pt` \- Portuguese
  * `tr` \- Turkish

### DateTimeValues

Section titled “DateTimeValues”

Individual date & time values, representing the primitives that make up a `DateTime`.

This is a dictionary that will contain the following values:

  * `year` \- Year(s), in the range 1400 -> 9999
  * `month` \- Month(s), in the range 1 -> 12
  * `day` \- Day(s), in the range 1 -> 31
  * `hour` \- Hour(s), in the range 0 -> 23
  * `minute` \- Minute(s), in the range 0 -> 59
  * `second` \- Second(s), in the range 0 -> 60, where 60 is a leap second

An additional `millisecond` value may also be included, and should be within the range `0 -> 999`, but is optional.

However, any method returning this type should be guaranteed to include milliseconds - see individual methods to verify.

### DateTimeValueArguments

Section titled “DateTimeValueArguments”

Alias for `DateTimeValues` with an optional `millisecond` value.

Refer to the `DateTimeValues` documentation for additional information.

### DateTimeValueReturns

Section titled “DateTimeValueReturns”

Alias for `DateTimeValues` with a mandatory `millisecond` value.

Refer to the `DateTimeValues` documentation for additional information.


# Working with Files

The `fs` library lets you work with files and directories in Lune. You can read, write, copy, and move files around with no extra boilerplate.

## Example File Tree

Section titled “Example File Tree”

Let’s use this directory and file structure for our examples:

  * files.luau
  * dirs.luau
  * hello-world.json
  * Directoryfiles
    * coolstuff.toml
    * super.secret.txt

Show file contents

  * hello-world.json
  * coolstuff.toml
  * super.secret.txt

      "Hello": "World"

    [you]

    cool = true

    awesome = "yep"

    Hey, you're not supposed to be in here!

## Files

Section titled “Files”

Reading and writing files using the `fs` library is simple and only works with strings:

files.luau

    local fs = require("@lune/fs")

    -- Print out the contents of all of the files

    print(fs.readFile("hello-world.json"))

    print(fs.readFile("files/coolstuff.toml"))

    print(fs.readFile("files/super.secret.txt"))

    -- Create a new file in our "files" directory

    fs.writeFile("files/My Favorite Numbers.txt", "2 4 6 8 0")

    -- Write to one of our files, overwriting any previous contents

    fs.writeFile("files/super.secret.txt", "Super secret message")

    -- Remove the new file we created in our "files" directory

    fs.removeFile("files/My Favorite Numbers.txt")

Note that the filesystem library works with raw strings for file contents and doesn’t differentiate between binary, UTF-8, or other encodings. It’s up to you to know how your files are structured and handle them appropriately.

## Directories

Section titled “Directories”

Reading and creating directories has a similar API, but with slightly different parameters and return values:

dirs.luau

    local fs = require("@lune/fs")

    -- Print out the entries found in our directory

    -- The "." here means the current directory

    print("Contents of current directory:")

    for _, entry in fs.readDir(".") do

      if fs.isDir(entry) then

        print(`📁 {entry}`)

      elseif fs.isFile(entry) then

        print(`📄 {entry}`)

      end

    end

    -- Create a new directory next to the above entries

    fs.writeDir("myCoolDir")

    -- Create a new directory in our "files" directory

    fs.writeDir("files/myCoolSecondDir")

    -- Remove the entire files directory

    fs.removeDir("files")

In the above example:

  * `fs.readDir` returns a table (array) of strings with file and directory names
  * `fs.writeDir` takes only the directory name (path) to create a directory
  * `fs.removeDir` removes the directory **and everything inside it** \- use with caution!

## Resulting File Tree

Section titled “Resulting File Tree”

This is what our directory structure would look like after running the above examples:

  * files.luau
  * dirs.luau
  * hello-world.json
  * DirectorymyCoolDir/
    * …

## What’s Next?

Section titled “What’s Next?”

Now that you know how to work with files and directories, let’s learn about organizing your code with [Modules](https://lune-org.github.io/docs/the-book/6-working-with-files/7-modules).


# Command-Line Usage

## Running Scripts

Section titled “Running Scripts”

Once you’ve written a script file, for example `script-name.luau`, you can run it as follows:

Terminal

    lune run script-name

Lune will look for the file `script-name.luau`**_[1]_ ** 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 your home directory, if it exists
  * The folder `.lune` in your home directory, if it exists

## Listing Scripts

Section titled “Listing Scripts”

Terminal

    lune list

This command lists all scripts found in `lune` or `.lune` directories, including any top-level description comments. Lune description comments are written at the top of a file and start with a Lua-style comment arrow (`-->`).

## Advanced Usage

Section titled “Advanced Usage”

Terminal

    lune run -

This runs a script passed to Lune using stdin, which is useful for running scripts piped from external sources. Here’s an example:

Terminal

    echo "print 'Hello, terminal!'" | lune run -

**_[1]_ ** _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 subdirectories 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._


# Serde

Built-in library for:

  * serialization & deserialization
  * encoding & decoding
  * compression

#### Example usage

Section titled “Example usage”

    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

Section titled “Functions”

### encode

Section titled “encode”

Encodes the given value using the given format.

See [`EncodeDecodeFormat`] for a list of supported formats.

#### Parameters

Section titled “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

Section titled “Returns”

  * The encoded string

### decode

Section titled “decode”

Decodes the given string using the given format into a lua value.

See [`EncodeDecodeFormat`] for a list of supported formats.

#### Parameters

Section titled “Parameters”

  * `format` The format to use

  * `encoded` The string to decode

#### Returns

Section titled “Returns”

  * The decoded lua value

### compress

Section titled “compress”

Compresses the given string using the given format.

See [`CompressDecompressFormat`] for a list of supported formats.

#### Parameters

Section titled “Parameters”

  * `format` The format to use

  * `s` The string to compress

  * `level` The compression level to use, clamped to the format’s limits. The best compression level is used by default

#### Returns

Section titled “Returns”

  * The compressed string

### decompress

Section titled “decompress”

Decompresses the given string using the given format.

See [`CompressDecompressFormat`] for a list of supported formats.

#### Parameters

Section titled “Parameters”

  * `format` The format to use

  * `s` The string to decompress

#### Returns

Section titled “Returns”

  * The decompressed string

### hash

Section titled “hash”

Hashes the given message using the given algorithm and returns the hash as a hex string.

See [`HashAlgorithm`] for a list of supported algorithms.

#### Parameters

Section titled “Parameters”

  * `algorithm` The algorithm to use

  * `message` The message to hash

#### Returns

Section titled “Returns”

  * The hash as a hex string

### hmac

Section titled “hmac”

Hashes the given message using HMAC with the given secret and algorithm, returning the hash as a base64 string.

See [`HashAlgorithm`] for a list of supported algorithms.

#### Parameters

Section titled “Parameters”

  * `algorithm` The algorithm to use

  * `message` The message to hash

  * `secret` string | buffer

#### Returns

Section titled “Returns”

  * The hash as a base64 string

## Types

Section titled “Types”

### EncodeDecodeFormat

Section titled “EncodeDecodeFormat”

A serialization/deserialization format supported by the Serde library.

Currently supported formats:

Name| Learn More
`json`| <https://www.json.org>
`yaml`| <https://yaml.org>
`toml`| <https://toml.io>

### CompressDecompressFormat

Section titled “CompressDecompressFormat”

A compression/decompression format supported by the Serde library.

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>
`zstd`| <https://github.com/facebook/zstd>

### HashAlgorithm

Section titled “HashAlgorithm”

A hash algorithm supported by the Serde library.

Currently supported algorithms:

Name| Learn More
`md5`| <https://en.wikipedia.org/wiki/MD5>
`sha1`| <https://en.wikipedia.org/wiki/SHA-1>
`sha224`| <https://en.wikipedia.org/wiki/SHA-2>
`sha256`| <https://en.wikipedia.org/wiki/SHA-2>
`sha384`| <https://en.wikipedia.org/wiki/SHA-2>
`sha512`| <https://en.wikipedia.org/wiki/SHA-2>
`sha3-224`| <https://en.wikipedia.org/wiki/SHA-3>
`sha3-256`| <https://en.wikipedia.org/wiki/SHA-3>
`sha3-384`| <https://en.wikipedia.org/wiki/SHA-3>
`sha3-512`| <https://en.wikipedia.org/wiki/SHA-3>
`blake3`| <https://en.wikipedia.org/wiki/BLAKE3>


# Stdio

Built-in standard input / output & utility functions

#### Example usage

Section titled “Example usage”

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

    -- Reading a single line from stdin

    local line = stdio.readLine()

    -- Reading the entire input from stdin

    local input = stdio.readToEnd()

## Functions

Section titled “Functions”

### prompt

Section titled “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

Section titled “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

Section titled “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

Section titled “Example usage”

    stdio.write(stdio.color("red"))

    print("This text will be red")

    stdio.write(stdio.color("reset"))

    print("This text will be normal")

#### Parameters

Section titled “Parameters”

  * `color` The color to use

#### Returns

Section titled “Returns”

  * A printable ANSI string

### style

Section titled “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

Section titled “Example usage”

    stdio.write(stdio.style("bold"))

    print("This text will be bold")

    stdio.write(stdio.style("reset"))

    print("This text will be normal")

#### Parameters

Section titled “Parameters”

  * `style` The style to use

#### Returns

Section titled “Returns”

  * A printable ANSI string

### format

Section titled “format”

Formats arguments into a human-readable string with syntax highlighting for tables.

#### Parameters

Section titled “Parameters”

  * `...` The values to format

#### Returns

Section titled “Returns”

  * The formatted string

### write

Section titled “write”

Writes a string directly to stdout, without any newline.

#### Parameters

Section titled “Parameters”

  * `s` The string to write to stdout

### ewrite

Section titled “ewrite”

Writes a string directly to stderr, without any newline.

#### Parameters

Section titled “Parameters”

  * `s` The string to write to stderr

### readLine

Section titled “readLine”

Reads a single line from stdin.

If stdin is closed, returns all input up until its closure.

#### Returns

Section titled “Returns”

  * The input from stdin

### readToEnd

Section titled “readToEnd”

Reads the entire input from stdin.

#### Returns

Section titled “Returns”

  * The input from stdin


# Roblox

Built-in library for manipulating Roblox place & model files

#### Example usage

Section titled “Example usage”

    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

Section titled “Functions”

### deserializePlace

Section titled “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

Section titled “Example usage”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    local placeFile = fs.readFile("filePath.rbxl")

    local game = roblox.deserializePlace(placeFile)

#### Parameters

Section titled “Parameters”

  * `contents` The contents of the place to read

#### Returns

Section titled “Returns”

  * DataModel

### deserializeModel

Section titled “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

Section titled “Example usage”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    local modelFile = fs.readFile("filePath.rbxm")

    local instances = roblox.deserializeModel(modelFile)

#### Parameters

Section titled “Parameters”

  * `contents` The contents of the model to read

#### Returns

Section titled “Returns”

  * { Instance }

### serializePlace

Section titled “serializePlace”

Serializes a place from a DataModel instance.

This string can then be written to a file, or sent over the network.

#### Example usage

Section titled “Example usage”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    local placeFile = roblox.serializePlace(game)

    fs.writeFile("filePath.rbxl", placeFile)

#### Parameters

Section titled “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

Section titled “Returns”

  * string

### serializeModel

Section titled “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

Section titled “Example usage”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    local modelFile = roblox.serializeModel({ instance1, instance2, ... })

    fs.writeFile("filePath.rbxm", modelFile)

#### Parameters

Section titled “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

Section titled “Returns”

  * string

### getAuthCookie

Section titled “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

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local serde = require("@lune/serde")

    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 = serde.decode("json", response.body)

    local responseLocation = responseTable.locations[1].location

    print("Download link to place: " .. responseLocation)

#### Parameters

Section titled “Parameters”

  * `raw` If the cookie should be returned as a pure value or not. Defaults to false

#### Returns

Section titled “Returns”

  * string?

### getReflectionDatabase

Section titled “getReflectionDatabase”

Gets the bundled reflection database.

This database contains information about Roblox enums, classes, and their properties.

#### Example usage

Section titled “Example usage”

    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

Section titled “Returns”

  * Database

### implementProperty

Section titled “implementProperty”

Implements a property for all instances of the given `className`.

This takes into account class hierarchies, so implementing a property for the `BasePart` class will also implement it for `Part` and others, unless a more specific implementation is added to the `Part` class directly.

#### Behavior

Section titled “Behavior”

The given `getter` callback will be called each time the property is indexed, with the instance as its one and only argument. The `setter` callback, if given, will be called each time the property should be set, with the instance as the first argument and the property value as second.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local part = roblox.Instance.new("Part")

    local propertyValues = {}

    roblox.implementProperty(

      "BasePart",

      "CoolProp",

      function(instance)

        if propertyValues[instance] == nil then

          propertyValues[instance] = 0

        end

        propertyValues[instance] += 1

        return propertyValues[instance]

      end,

      function(instance, value)

        propertyValues[instance] = value

      end

    )

    print(part.CoolProp) --> 1

    print(part.CoolProp) --> 2

    print(part.CoolProp) --> 3

    part.CoolProp = 10

    print(part.CoolProp) --> 11

    print(part.CoolProp) --> 12

    print(part.CoolProp) --> 13

#### Parameters

Section titled “Parameters”

  * `className` The class to implement the property for.

  * `propertyName` The name of the property to implement.

  * `getter` The function which will be called to get the property value when indexed.

  * `setter` The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only.

### implementMethod

Section titled “implementMethod”

Implements a method for all instances of the given `className`.

This takes into account class hierarchies, so implementing a method for the `BasePart` class will also implement it for `Part` and others, unless a more specific implementation is added to the `Part` class directly.

#### Behavior

Section titled “Behavior”

The given `callback` will be called every time the method is called, and will receive the instance it was called on as its first argument. The remaining arguments will be what the caller passed to the method, and all values returned from the callback will then be returned to the caller.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local part = roblox.Instance.new("Part")

    roblox.implementMethod("BasePart", "TestMethod", function(instance, ...)

        print("Called TestMethod on instance", instance, "with", ...)

    end)

    part:TestMethod("Hello", "world!")

    --> Called TestMethod on instance Part with Hello, world!

#### Parameters

Section titled “Parameters”

  * `className` The class to implement the method for.

  * `methodName` The name of the method to implement.

  * `callback` The function which will be called when the method is called.

### studioApplicationPath

Section titled “studioApplicationPath”

Returns the path to the system’s Roblox Studio executable.

There is no guarantee that this will exist, but if Studio is installed this is where it will be.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local pathToStudio = roblox.studioApplicationPath()

    print("Studio is located at:", pathToStudio)

#### Returns

Section titled “Returns”

  * string

### studioContentPath

Section titled “studioContentPath”

Returns the path to the `Content` folder of the system’s current Studio install.

This folder will always exist if Studio is installed.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local pathToContent = roblox.studioContentPath()

    print("Studio's content folder is located at:", pathToContent)

#### Returns

Section titled “Returns”

  * string

### studioPluginPath

Section titled “studioPluginPath”

Returns the path to the `plugin` folder of the system’s current Studio install. This is the path where local plugins are installed.

This folder may not exist if the user has never installed a local plugin. It will also not necessarily take into account custom plugin directories set from Studio.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local pathToPluginFolder = roblox.studioPluginPath()

    print("Studio's plugin folder is located at:", pathToPluginFolder)

#### Returns

Section titled “Returns”

  * string

### studioBuiltinPluginPath

Section titled “studioBuiltinPluginPath”

Returns the path to the `BuiltInPlugin` folder of the system’s current Studio install. This is the path where built-in plugins like the ToolBox are installed.

This folder will always exist if Studio is installed.

#### Example usage

Section titled “Example usage”

    local roblox = require("@lune/roblox")

    local pathToPluginFolder = roblox.studioBuiltinPluginPath()

    print("Studio's built-in plugin folder is located at:", pathToPluginFolder)

#### Returns

Section titled “Returns”

  * string


# The Standard Library

Lune has a comprehensive standard library that gives your scripts powerful capabilities. These libraries let you do everything from reading files, to making web requests, to running other programs.

Here are some of the most commonly used libraries:

  * [`fs`](https://lune-org.github.io/docs/api-reference/fs) \- Work with files and directories
  * [`net`](https://lune-org.github.io/docs/api-reference/net) \- Make HTTP requests and create servers
  * [`process`](https://lune-org.github.io/docs/api-reference/process) \- Run external programs and access system information
  * [`stdio`](https://lune-org.github.io/docs/api-reference/stdio) \- Get input from users and display output
  * [`task`](https://lune-org.github.io/docs/api-reference/task) \- Schedule and manage concurrent tasks

## Importing Libraries

Section titled “Importing Libraries”

Unlike Luau’s globals like [`math`](https://luau-lang.org/library#math-library) or [`table`](https://luau-lang.org/library#table-library), Lune’s libraries need to be imported before you can use them. You do this with a special `require` statement:

    local fs = require("@lune/fs")

    local net = require("@lune/net")

    local process = require("@lune/process")

The `@lune/` prefix tells Lune that you want to use one of its standard libraries rather than looking for a file in your project.

Throughout the rest of this book, we’ll explore these libraries in detail and see how they work together to make Lune scripts powerful and flexible.


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

Section titled “Classes”

### `Instance`

Section titled “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`

Section titled “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

Section titled “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.


# Editor Setup

Lune prioritizes developer experience, providing type definitions and documentation for many editors and tools without any additional downloads. This guide will help you set up your editor environment.

## Luau Language Server

Section titled “Luau Language Server”

The open source Luau Language Server, also known as [`luau-lsp`](https://github.com/JohnnyMorganz/luau-lsp), is currently the main language server providing editor support for Luau. It supports a wide range of editors.

Once you’ve installed both the language server and Lune, you can run the following command to generate type definition files and create or update a standardized `.luaurc` configuration file:

Terminal

    lune setup

This should be all you need to get up and running. You may, however, need to restart your editor for the changes to take effect.


# Installation

The preferred way of installing Lune is using [Rokit](https://github.com/rojo-rbx/rokit), a toolchain manager for Roblox projects. Rokit can manage your installed version of Lune and other ecosystem tools, and allows you to easily upgrade to newer versions as they become available.

  1. ### Installing Rokit

Section titled “Installing Rokit”

Follow the installation instructions on the [Rokit](https://github.com/rojo-rbx/rokit) page.

  2. ### Installing Lune

Section titled “Installing Lune”

Navigate to your project directory using your terminal, and run the following command:

Terminal

         rokit add lune

  3. ### Upgrading Lune

Section titled “Upgrading Lune”

When a new version of Lune becomes available, Rokit makes it easy to upgrade. Navigate to your project directory using your terminal again, and run the following command:

Terminal

         rokit update lune

If you prefer to install Lune globally and have it accessible on your entire system, instead of only in a specific project, you can do this with Rokit as well. Just add the `--global` option to the end of the commands above.

## Other Installation Options

Section titled “Other Installation Options”

Using GitHub Releases

You can download pre-built binaries for most systems directly from the [GitHub Releases](https://github.com/lune-org/lune/releases) page. There are many tools that can install binaries directly from releases, and it’s up to you to choose what tool to use. Lune is compatible with both [Foreman](https://github.com/Roblox/foreman) and [Aftman](https://github.com/LPGhatguy/aftman).

Community-maintained

### Scoop

Section titled “Scoop”

Windows users can use [Scoop](https://scoop.sh) to install Lune.

Terminal window

    # Add the bucket

    scoop bucket add lune https://github.com/CompeyDev/lune-packaging.git

    # Install the package

    scoop install lune

### Homebrew

Section titled “Homebrew”

macOS and Linux users can use [Homebrew](https://brew.sh) to install Lune.

Terminal

    # Installs latest stable precompiled binary

    brew install lune

**_or_ **

Terminal

    # Builds from latest stable source

    brew install lune --build-from-source

### APT

Section titled “APT”

APT is a package manager for Debian-based Linux distributions that uses `dpkg` to install packages. Follow the instructions at the unofficial [lune-packaging](https://github.com/CompeyDev/lune-packaging#apt) repository to install Lune using APT.

### AppImage

Section titled “AppImage”

AppImages are platform-independent sandboxed binaries that work out of the box. Go to the [GitHub Actions Page](https://github.com/CompeyDev/lune-packaging/actions/workflows/appimage.yaml), and download the artifact suitable for your architecture from the build artifacts.

### AUR (Arch User Repository)

Section titled “AUR (Arch User Repository)”

There are a number of packages available on the AUR:

  * `lune` \- Builds from the latest stable release source
  * `lune-git` \- Builds from the latest commit in the repository (unstable)
  * `lune-bin` \- Installs a precompiled binary from GitHub Release artifacts

These can be installed with your preferred AUR package manager:

Terminal

    paru -S [PACKAGE_NAME]

**_or_ **

Terminal

    yay -S [PACKAGE_NAME]

### Nix

Section titled “Nix”

macOS* and Linux users can use [Nix](https://nixos.org/) to install Lune.

Imperatively

**NixOS**

Terminal

    nix-env -iA nixos.lune

**Non-NixOS**

Terminal

    nix-env -iA nixpkgs.lune

    # If you are using flakes

    nix profile install nixpkgs#lune

Declaratively

**With [home-manager](https://github.com/nix-community/home-manager)**

    home.packages = with pkgs; [

      lune

    ];

**System-wide NixOS configuration**

    environment.systemPackages = with pkgs; [

      lune

    ];

Temporarily

You can temporarily use Lune in your shell. This is useful to try out Lune before deciding to permanently install it.

Terminal

    nix-shell -p lune

Using crates.io

### Building from source

Section titled “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. Once installed, run the following command in your terminal:

Terminal

    cargo install lune --locked

### Binstall

Section titled “Binstall”

[`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) provides a simple mechanism for installing Rust binaries from crates.io without compiling from source (unlike `cargo install`). Lune is packaged in a `binstall`-compatible way.

With `binstall` installed and in your path, run:

Terminal

    cargo binstall lune

## Next Steps

Section titled “Next Steps”

Congratulations! You’ve installed Lune and are now ready to write your first script.

A great place to continue reading is the [Lune Book](https://lune-org.github.io/docs/the-book/1-hello-lune), which is also part of the official Lune documentation, and will give you a guided and comprehensive introduction to Lune.

Or, if you want to dive right into specific resources, check out the API reference in the sidebar.


# 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

Section titled “Drop-in Compatibility”

This guide provides a module which translates all of the relevant Lune APIs to their Remodel equivalents. For more details or manual migration steps, check out Differences Between Lune & Remodel below.

  1. Copy the [remodel module](https://github.com/lune-org/docs/blob/main/modules/remodel.luau) and place it in a file named `remodel.luau`.

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.

  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 legacy Remodel-native example scripts, with only the top line added:

remodel.luau

         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)

  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 🚀

Terminal

         lune run example

## Differences Between Lune & Remodel

Section titled “Differences Between Lune & Remodel”

Most APIs previously found in Remodel have direct equivalents in Lune, below are some direct links to APIs that are equivalent or very similar.

Places & Models

  * `remodel.readPlaceFile` ➡ [`fs.readFile`](https://lune-org.github.io/docs/api-reference/fs#readfile) & [`roblox.deserializePlace`](https://lune-org.github.io/docs/api-reference/roblox#deserializeplace)
  * `remodel.readModelFile` ➡ [`fs.readFile`](https://lune-org.github.io/docs/api-reference/fs#readfile) & [`roblox.deserializeModel`](https://lune-org.github.io/docs/api-reference/roblox#deserializemodel)
  * `remodel.readPlaceAsset` ➡ [`net.request`](https://lune-org.github.io/docs/api-reference/net#request) & [`roblox.deserializePlace`](https://lune-org.github.io/docs/api-reference/roblox#deserializeplace)
  * `remodel.readModelAsset` ➡ [`net.request`](https://lune-org.github.io/docs/api-reference/net#request) & [`roblox.deserializeModel`](https://lune-org.github.io/docs/api-reference/roblox#deserializemodel)
  * `remodel.writePlaceFile` ➡ [`roblox.serializePlace`](https://lune-org.github.io/docs/api-reference/roblox#serializeplace) & [`fs.writeFile`](https://lune-org.github.io/docs/api-reference/fs#writefile)
  * `remodel.writeModelFile` ➡ [`roblox.serializeModel`](https://lune-org.github.io/docs/api-reference/roblox#serializemodel) & [`fs.writeFile`](https://lune-org.github.io/docs/api-reference/fs#writefile)
  * `remodel.writeExistingPlaceAsset` ➡ [`roblox.serializePlace`](https://lune-org.github.io/docs/api-reference/roblox#serializeplace) & [`net.request`](https://lune-org.github.io/docs/api-reference/net#request)
  * `remodel.writeExistingModelAsset` ➡ [`roblox.serializeModel`](https://lune-org.github.io/docs/api-reference/roblox#serializemodel) & [`net.request`](https://lune-org.github.io/docs/api-reference/net#request)
  * `remodel.getRawProperty` ➡ no equivalent, you can get properties directly by indexing
  * `remodel.setRawProperty` ➡ no equivalent, you can set properties directly by indexing

Files & Directories

  * `remodel.readFile` ➡ [`fs.readFile`](https://lune-org.github.io/docs/api-reference/fs#readfile)
  * `remodel.readDir` ➡ [`fs.readDir`](https://lune-org.github.io/docs/api-reference/fs#readdir)
  * `remodel.writeFile` ➡ [`fs.writeFile`](https://lune-org.github.io/docs/api-reference/fs#writefile)
  * `remodel.createDirAll` ➡ [`fs.writeDir`](https://lune-org.github.io/docs/api-reference/fs#writedir)
  * `remodel.removeFile` ➡ [`fs.removeFile`](https://lune-org.github.io/docs/api-reference/fs#removefile)
  * `remodel.removeDir` ➡ [`fs.removeDir`](https://lune-org.github.io/docs/api-reference/fs#removedir)
  * `remodel.isFile` ➡ [`fs.isFile`](https://lune-org.github.io/docs/api-reference/fs#isfile)
  * `remodel.isDir` ➡ [`fs.isDir`](https://lune-org.github.io/docs/api-reference/fs#isdir)

JSON

  * `json.fromString` ➡ [`serde.decode`](https://lune-org.github.io/docs/api-reference/serde#decode)
  * `json.toString` ➡ [`serde.encode`](https://lune-org.github.io/docs/api-reference/serde#encode)
  * `json.toStringPretty` ➡ [`serde.encode`](https://lune-org.github.io/docs/api-reference/serde#encode)

Since Lune is meant to be a general-purpose Luau runtime, there are also some more general differences, and Lune takes a different approach from Remodel in certain areas:

  * Lune runs Luau instead of Lua 5.3.
  * APIs are more loosely coupled, meaning that a task may require more steps using Lune. This also means that Lune is more flexible and supports more use cases.
  * Standard libraries are not accessible from global variables, you have to explicitly import them using `require("@lune/library-name")`.
  * Arguments given to scripts are not available in `...`, you have to use [`process.args`](https://lune-org.github.io/docs/api-reference/process#args) instead.
  * Lune generally supports all of the Roblox datatypes that are gettable/settable on instance properties. For a full list of available datatypes, check out the [API Status](https://lune-org.github.io/docs/roblox/4-api-status) page.

There may be more differences than are listed here, and the Lune-specific guides and examples may provide more info, but this should be all you need to know to migrate from Remodel. Good luck!


# Spawning Processes

Whenever Lune doesn’t have the API you need as part of its standard libraries, or when you want to use a program that already exists, but interact with it from within Lune, you can spawn a subprocess using [`process.exec`](https://lune-org.github.io/docs/api-reference/process#exec).

## Example

Section titled “Example”

This example calls out to the native “ping” program found on many operating systems, and parses its output into something more usable.

This may look a bit intimidating with all the pattern matching symbols, but it’s just parsing the line that says `"min/avg/max/stddev = W/X/Y/Z ms"` from ping’s output:

ping-example.luau

    local process = require("@lune/process")

    print("Sending 4 pings to google.com...")

    local result = process.exec("ping", {

      "google.com",

      "-c", "4",

    })

    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", tonumber(min)))

      print(string.format("Maximum ping time: %.3fms", tonumber(max)))

      print(string.format("Average ping time: %.3fms", tonumber(avg)))

      print(string.format("Standard deviation: %.3fms", tonumber(stddev)))

    else

      print("Failed to send ping to google.com")

      print(result.stderr)

      process.exit(result.code)

    end

Note that if the subprocess returns a non-zero exit code (meaning it errored and `ok` was set to `false`), we propagate that exit code using [`process.exit`](https://lune-org.github.io/docs/api-reference/process#exit). This ensures that if our subprocess fails, our script fails too, letting the user know something went wrong.

## The Result Table

Section titled “The Result Table”

When you call `process.exec`, you get back a table with these fields:

  * `ok` \- true if the exit code was 0 (success)
  * `code` \- the actual exit code
  * `stdout` \- what the program printed to standard output
  * `stderr` \- what the program printed to standard error

This should give you everything you need to work with external programs the same way you would when using your terminal - all the text you see outputted when using your terminal, for example, is always part of either `stdout` or `stderr`.

Generally, program output will be in `stdout`, and error messages, warnings, and other miscellaneous information will be in `stderr`.

## Common Use Cases

Section titled “Common Use Cases”

Beyond the ping example, here are some other ways you might use `process.exec`:

    -- Run git commands

    local gitStatus = process.exec("git", { "status", "--short" })

    -- Compress files

    local zipResult = process.exec("zip", { "-r", "archive.zip", "dir/" })

    -- Manipulate images

    local result = process.exec("convert", { "input.png", "-resize", "800x600", "output.jpg" })

Extra: Real-time Processing

As we’ve seen throughout this chapter, the `process.exec` function only returns a result table, and does not let you interact with the output streams while the process is running. But sometimes, you don’t want that simplicity, and you need more granular and real-time processing capabilities for process output.

That’s where `process.create` comes in - here’s an example for monitoring a log file in real-time and alert when errors occur:

log-monitor.luau

    local process = require("@lune/process")

    -- Start watching a log file

    local tail = process.create("tail", { "-f", "/var/log/app.log" })

    print("Monitoring log file for errors...")

    -- Read new log lines as they appear

    while true do

      local line = tail.stdout:read()

      if not line then

        break

      end

      if string.find(line, "ERROR") or string.find(line, "FATAL") then

          print(`🚨 ALERT: {line}`)

          -- Could send notification, write to file, etc.

      end

    end

## What’s Next?

Section titled “What’s Next?”

You can now extend Lune’s capabilities by running any program on your system. This opens up endless possibilities - from using git in your scripts to leveraging specialized tools for tasks Lune doesn’t handle natively.

But what if you need to run multiple operations at once? Or schedule work to happen later? Let’s explore Lune’s powerful concurrency features in our last chapter - [The Task Scheduler](https://lune-org.github.io/docs/the-book/8-spawning-processes/9-task-scheduler).