Skip to content

Instantly share code, notes, and snippets.

@cxmeel
Last active May 9, 2024 11:58
Show Gist options
  • Save cxmeel/d24eb096ebfb563f6e4267beb349c907 to your computer and use it in GitHub Desktop.
Save cxmeel/d24eb096ebfb563f6e4267beb349c907 to your computer and use it in GitHub Desktop.
A Lune script for building plugins and watching for file changes
--!strict
--[[
Usage:
Build production release:
> `lune run build`
Build dev release to Roblox/Plugins directory:
> `lune run build -- --dev`
Build dev release to plugins dir AND watch for changes:
> `lune run build -- --dev --watch`
Modify the `PROJECT_JSON`, `WATCHED_DIRS` and `COMMAND_SEQUENCE`
variables as required.
]]
local DateTime = require("@lune/datetime")
local fs = require("@lune/fs")
local process = require("@lune/process")
local serde = require("@lune/serde")
local task = require("@lune/task")
local ARGS = process.args
local isDev = table.find(ARGS, "--dev") ~= nil
local isWatch = table.find(ARGS, "--watch") ~= nil
type Command = {
label: string?,
exec: string | ({ string }) -> (),
args: { string }?,
when: (() -> boolean)?,
}
local PROJECT_JSON = serde.decode("json", fs.readFile("build.project.json"))
local POLL_INTERVAL = 2
local WATCHED_DIRS: { string } = {
"src",
"packages",
}
local COMMAND_SEQUENCE: { Command } = {
{
exec = "lune",
args = { "run", "wally-install" },
when = function()
return not isDev and not isWatch
end,
},
{
exec = "rojo",
args = { "sourcemap", "-o", "sourcemap.json" },
},
{
label = "remove build directory",
exec = function()
if fs.isDir("build") then
fs.removeDir("build")
end
end,
},
{
exec = "darklua",
args = { "process", "src", "build", "-c", "darklua.json" },
},
{
exec = "rojo",
args = { "build", "build.project.json", "--plugin", `{PROJECT_JSON.name}.rbxm` },
when = function()
return isDev
end,
},
{
exec = "rojo",
args = { "build", "build.project.json", "-o", `{PROJECT_JSON.name}.rbxm` },
when = function()
return not isDev
end,
},
}
local SESSION_START = DateTime.now()
local function generateUuid()
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
return string.gsub(template, "[xy]", function(c)
local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)
return string.format("%x", v)
end)
end
local function shortenUuid(uuid: string)
return uuid:sub(1, 7)
end
local function runCommandSequence()
local RUN_SESSION_ID = generateUuid()
local now = DateTime.now()
local formattedTime = now:formatLocalTime("%X")
print(`Running build sequence: {shortenUuid(RUN_SESSION_ID)} ({formattedTime})`)
for _, command in COMMAND_SEQUENCE do
if not command.when or command.when() then
local label = command.label or tostring(command.exec)
local isFunction = typeof(command.exec) == "function"
print(
`> Running {isFunction and "function" or "command"}: {label} {table.concat(
command.args or {},
" "
)}`
)
local result: any
if isFunction then
local ok, err = pcall(command.exec :: any, command.args)
result = { ok = ok, stderr = err }
else
result = process.spawn(command.exec :: string, command.args)
end
if not result.ok then
error(`Failed to run command: {command.exec}\n> {result.stderr}`)
end
end
end
if isWatch then
print("\nWaiting for changes. Press CTRL+C to stop.\n")
end
end
local function powershellGetLastModified(dir: string)
local dirModifiedAt = (fs.metadata(dir).modifiedAt or SESSION_START) :: any
local lastModified = dirModifiedAt.unixTimestamp
local result = process.spawn("powershell", {
"-Command",
`Get-ChildItem -Recurse -Path {dir} | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Get-Date -UFormat %s -Millisecond 0`,
})
if result.ok then
local psLastModified = tonumber(result.stdout)
if psLastModified and psLastModified > lastModified then
lastModified = psLastModified
end
end
return lastModified
end
local function watch(dir: string, callback: () -> ())
local lastModified = powershellGetLastModified(dir)
local thread
thread = task.spawn(function()
while task.wait(POLL_INTERVAL) do
if not fs.isDir(dir) then
if thread then
task.cancel(thread)
end
return
end
local modifiedAt = powershellGetLastModified(dir)
if modifiedAt ~= lastModified then
lastModified = modifiedAt
callback()
end
end
end)
return function()
if thread then
task.cancel(thread)
end
end
end
local function setReactDevMode()
if not isDev then
return
end
local reactPath = "Packages/React.lua"
local hasReact = fs.isFile(reactPath)
if not hasReact then
return
end
local reactContent = fs.readFile(reactPath)
if reactContent:find("_G.__DEV__") then
print("> React is running in dev mode\n")
return
end
reactContent = `-- selene: allow(global_usage)\n_G.__DEV__ = true\n\n{reactContent}`
fs.writeFile(reactPath, reactContent)
print("> Enabled React's dev mode due to `--dev` flag\n")
end
local function main()
setReactDevMode()
runCommandSequence()
if not isWatch then
return
end
local _watchThreads = {}
local thread: thread?
for _, dir in WATCHED_DIRS do
if not fs.isDir(dir) then
continue
end
local watchThread = watch(dir, function()
if thread then
task.cancel(thread)
end
thread = task.spawn(runCommandSequence)
end)
table.insert(_watchThreads, watchThread)
end
end
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment