Created
January 25, 2026 12:15
-
-
Save Mark-Marks/8673723ad5290279bc78e2519c0fa2fd to your computer and use it in GitHub Desktop.
Smart sync
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --!strict | |
| --!optimize 2 | |
| local function Color256Fn(set: number): (code: number) -> string | |
| return function(code: number): string | |
| if code < 0 or code > 255 then | |
| error("Code must be between 0 to 255", 2) | |
| end | |
| return string.format(`\x1b[{set};5;%dm`, code) | |
| end | |
| end | |
| local function TrueColorFn(set: number): (r: number, g: number, b: number) -> string | |
| return function(r: number, g: number, b: number): string | |
| if r < 0 or r > 255 then | |
| error("R must be between 0 to 255", 2) | |
| end | |
| if g < 0 or g > 255 then | |
| error("G must be between 0 to 255", 2) | |
| end | |
| if b < 0 or b > 255 then | |
| error("B must be between 0 to 255", 2) | |
| end | |
| return string.format(`\x1b[{set};2;%d;%d;%dm`, r, g, b) | |
| end | |
| end | |
| local CursorMoveTemplates = { | |
| home = "\x1b[H", | |
| goto = "\x1b[%d;%dH", | |
| up = "\x1b[%dA", | |
| down = "\x1b[%dB", | |
| right = "\x1b[%dC", | |
| left = "\x1b[%dD", | |
| nextline = "\x1b[%dE", | |
| prevline = "\x1b[%dF", | |
| gotocol = "\x1b[%dG", | |
| } | |
| local function cursorMove(action: string, ...): string | |
| local template = CursorMoveTemplates[action] | |
| if not template then | |
| return error("UnknownKind", 2) | |
| end | |
| return string.format(template, ...) | |
| end | |
| local EraseTemplates = { | |
| endOf = "\x1b[0J", | |
| startOf = "\x1b[1J", | |
| entire = "\x1b[2J", | |
| savedLines = "\x1b[3J", | |
| endOfLine = "\x1b[0K", | |
| startOfLine = "\x1b[1K", | |
| entireLine = "\x1b[2K", | |
| } | |
| local function erase( | |
| erase: "endOf" | "startOf" | "entire" | "savedLines" | "endOfLine" | "startOfLine" | "entireLine" | |
| ): string | |
| return EraseTemplates[erase] or error("UnknownKind", 2) | |
| end | |
| return { | |
| black = "\x1b[30m", | |
| red = "\x1b[31m", | |
| green = "\x1b[32m", | |
| yellow = "\x1b[33m", | |
| blue = "\x1b[34m", | |
| magenta = "\x1b[35m", | |
| cyan = "\x1b[36m", | |
| white = "\x1b[37m", | |
| bright_black = "\x1b[90m", | |
| bright_red = "\x1b[91m", | |
| bright_green = "\x1b[92m", | |
| bright_yellow = "\x1b[93m", | |
| bright_blue = "\x1b[94m", | |
| bright_magenta = "\x1b[95m", | |
| bright_cyan = "\x1b[96m", | |
| bright_white = "\x1b[97m", | |
| color256 = Color256Fn(38), | |
| trueColor = TrueColorFn(38), | |
| bg = { | |
| black = "\x1b[40m", | |
| red = "\x1b[41m", | |
| green = "\x1b[42m", | |
| yellow = "\x1b[43m", | |
| blue = "\x1b[44m", | |
| magenta = "\x1b[45m", | |
| cyan = "\x1b[46m", | |
| white = "\x1b[47m", | |
| bright_black = "\x1b[100m", | |
| bright_red = "\x1b[101m", | |
| bright_green = "\x1b[102m", | |
| bright_yellow = "\x1b[103m", | |
| bright_blue = "\x1b[104m", | |
| bright_magenta = "\x1b[105m", | |
| bright_cyan = "\x1b[106m", | |
| bright_white = "\x1b[107m", | |
| color256 = Color256Fn(48), | |
| trueColor = TrueColorFn(48), | |
| }, | |
| dim = "\x1b[2m", | |
| bold = "\x1b[1m", | |
| italic = "\x1b[3m", | |
| underline = "\x1b[4m", | |
| blink = "\x1b[5m", | |
| inverse = "\x1b[7m", | |
| hidden = "\x1b[8m", | |
| strikethrough = "\x1b[9m", | |
| reset = "\x1b[0m", | |
| reset_bold = "\x1b[22m", | |
| reset_dim = "\x1b[22m", | |
| reset_italic = "\x1b[23m", | |
| reset_underline = "\x1b[24m", | |
| reset_blink = "\x1b[25m", | |
| reset_inverse = "\x1b[27m", | |
| reset_hidden = "\x1b[28m", | |
| reset_strikethrough = "\x1b[29m", | |
| reset_color = "\x1b[39m", | |
| reset_bgcolor = "\x1b[49m", | |
| cursorMove = (cursorMove :: any) :: ((action: "home") -> string) & ((action: "goto", line: number, column: number) -> string) & ( | |
| (action: "up" | "down" | "right" | "left", amount: number) -> string | |
| ) & ((action: "nextline", linesDown: number) -> string) & ((action: "prevline", linesUp: number) -> string) & ((action: "gotocol", column: number) -> string), | |
| erase = erase, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local fs = zune.fs | |
| local task = zune.task | |
| local process = zune.process | |
| local platform = zune.platform | |
| local stdpath = fs.path | |
| local function exists(path: string): boolean | |
| return fs.stat(path).kind ~= "none" | |
| end | |
| local function shell_remove(path: string, flags: { recursive: boolean?, force: boolean? }?) | |
| local args = table.create(3) | |
| if flags then | |
| if flags.recursive then | |
| table.insert(args, "-r") | |
| end | |
| if flags.force then | |
| table.insert(args, if platform.os == "windows" then "-Force" else "-f") | |
| end | |
| end | |
| table.insert(args, path) | |
| process.run("rm", args, { shell = if platform.os == "windows" then "pwsh" else "bash" }) | |
| end | |
| local function watch_file(path: string, fn: (metadata: Metadata) -> ()): () -> () | |
| local origin_metadata = fs.metadata(path) | |
| local last_modified = origin_metadata.modified_at | |
| local thread = task.spawn(function() | |
| while true do | |
| task.wait(1) | |
| local metadata = fs.metadata(path) | |
| if last_modified < metadata.modified_at then | |
| last_modified = metadata.modified_at | |
| fn(metadata) | |
| end | |
| end | |
| end) | |
| return function() | |
| task.cancel(thread) | |
| end | |
| end | |
| type WatchEvent = "created" | "modified" | "moved" | "renamed" | "deleted" | "metadata" | |
| local function deep_watch(path: string, fn: (path: string, event: WatchEvent) -> ()): () -> () | |
| local dtors = {} | |
| local last_modified: { path: string, at: number }? = nil | |
| local watcher = fs.watch(path, function(child_name, events) | |
| local event_path = stdpath.join(path, child_name) | |
| if table.find(events, "modified") then | |
| local metadata = fs.metadata(event_path) | |
| if | |
| last_modified | |
| and last_modified.at == metadata.modified_at | |
| and last_modified.path == event_path | |
| then | |
| return | |
| end | |
| last_modified = { path = event_path, at = metadata.modified_at } | |
| end | |
| if | |
| table.find(events, "deleted") | |
| or table.find(events, "moved") | |
| or table.find(events, "renamed") | |
| then | |
| local dtor = dtors[event_path] | |
| if dtor then | |
| dtor() | |
| dtors[event_path] = nil | |
| elseif fs.stat(event_path).kind == "directory" then | |
| dtors[event_path] = deep_watch(event_path, fn) | |
| end | |
| end | |
| if table.find(events, "created") and fs.stat(event_path).kind == "directory" then | |
| dtors[event_path] = deep_watch(event_path, fn) | |
| end | |
| for _, event in events do | |
| fn(event_path, event :: WatchEvent) | |
| end | |
| end) | |
| local function root_dtor() | |
| watcher:stop() | |
| end | |
| dtors[path] = root_dtor | |
| for _, entry in fs.entries(path) do | |
| if entry.kind ~= "directory" then | |
| continue | |
| end | |
| local child_path = stdpath.join(path, entry.name) | |
| dtors[child_path] = deep_watch(child_path, fn) | |
| end | |
| return function() | |
| for _, dtor in dtors do | |
| dtor() | |
| end | |
| dtors = {} | |
| end | |
| end | |
| return { | |
| exists = exists, | |
| shell_remove = shell_remove, | |
| watch_file = watch_file, | |
| deep_watch = deep_watch, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local process = zune.process | |
| local task = zune.task | |
| local Process = {} | |
| local mt = { __index = Process } | |
| type Properties = { | |
| command: string, | |
| args: { string }, | |
| options: ProcessOptions, | |
| } | |
| export type Identity = typeof(setmetatable({} :: Properties, {} :: typeof(mt))) | |
| local function constructor(command: string): Identity | |
| local self: Properties = { | |
| command = command, | |
| args = {}, | |
| options = {}, | |
| } | |
| return setmetatable(self, mt) | |
| end | |
| function Process.argument(self: Identity, argument: string): Identity | |
| table.insert(self.args, argument) | |
| return self | |
| end | |
| function Process.option( | |
| self: Identity, | |
| option: keyof<ProcessOptions>, | |
| value: index<ProcessOptions, keyof<ProcessOptions>> | |
| ): Identity | |
| (self.options :: any)[option] = value | |
| return self | |
| end | |
| function Process.inherit_stdout(self: Identity): Identity | |
| return self:option("stdout", "inherit"):option("stderr", "inherit") | |
| end | |
| function Process.create(self: Identity): ProcessChild | |
| return process.create(self.command, self.args, self.options) | |
| end | |
| function Process.run(self: Identity, carry_exit: boolean?): ProcessRunResult | |
| local result = process.run(self.command, self.args, self.options) | |
| if not result.ok and (carry_exit == nil or carry_exit == true) then | |
| error(result.code) | |
| end | |
| return result | |
| end | |
| function Process.spawn(self: Identity): thread | |
| return task.spawn(process.run, self.command, self.args, self.options) | |
| end | |
| function Process.clone(self: Identity): Identity | |
| local clone: Properties = { | |
| command = self.command, | |
| args = table.clone(self.args), | |
| options = table.clone(self.options), | |
| } | |
| return setmetatable(clone, mt) | |
| end | |
| return { | |
| new = constructor, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local Process = require("./util/Process") | |
| local fs_util = require("./util/fs") | |
| local ansi = require("./util/ansi") | |
| local fs = zune.fs | |
| local stdpath = fs.path | |
| local function smart_sync(options: { | |
| sourcemap_project: string, | |
| sync_project: string, | |
| source: string, | |
| out: string, | |
| }) | |
| local source_dir, out_dir = options.source, options.out | |
| if fs_util.exists(out_dir) then | |
| fs_util.shell_remove(out_dir, { recursive = true, force = true }) | |
| end | |
| local pesde = Process.new("pesde"):argument("install"):inherit_stdout() | |
| pesde:run() | |
| fs_util.watch_file("pesde.toml", function() | |
| pesde:spawn() | |
| end) | |
| local zap = Process.new("zap"):argument("net.zap"):inherit_stdout() | |
| zap:run() | |
| fs_util.watch_file("net.zap", function() | |
| zap:spawn() | |
| end) | |
| local regenerate_sourcemap = Process.new("rojo") | |
| :argument("sourcemap") | |
| :argument(options.sourcemap_project) | |
| :argument("-o") | |
| :argument("sourcemap.json") | |
| :inherit_stdout() | |
| regenerate_sourcemap:run() | |
| local darklua = Process.new("darklua") | |
| :argument("process") | |
| :argument("--config") | |
| :argument(".darklua.json") | |
| :option("env", { ROBLOX_DEV = "true" }) | |
| :inherit_stdout() | |
| local process_all = darklua:clone():argument(source_dir):argument(out_dir) | |
| process_all:run() | |
| fs_util.deep_watch(source_dir, function(path, event) | |
| if event == "metadata" then | |
| return | |
| end | |
| local local_path = string.sub(path, 4) -- Removes src/ from the beginning of the path | |
| local path_in_out = stdpath.join(out_dir, local_path) | |
| if event == "created" or event == "modified" then | |
| if event == "created" then | |
| regenerate_sourcemap:run() | |
| end | |
| local process_this = darklua:clone():argument(path):argument(path_in_out) | |
| process_this:run() | |
| print( | |
| `{ansi.green}{ansi.bold}[process]{ansi.reset} Synced {ansi.bg.black}{ansi.dim}{path}{ansi.reset} -> {ansi.bg.black}{ansi.dim}{path_in_out}{ansi.reset}` | |
| ) | |
| return | |
| end | |
| if event == "deleted" then | |
| fs_util.remove(path_in_out) | |
| print( | |
| `{ansi.green}{ansi.bold}[process]{ansi.reset} Removed {ansi.bg.black}{ansi.dim}{path_in_out}{ansi.reset}` | |
| ) | |
| return | |
| end | |
| regenerate_sourcemap:run() | |
| process_all:run() | |
| print( | |
| `{ansi.green}{ansi.bold}[process]{ansi.reset} Triggered reprocess because of {event} @ {path}` | |
| ) | |
| end) | |
| Process.new("rojo"):argument("serve"):argument(options.sync_project):inherit_stdout():spawn() | |
| print( | |
| `{ansi.green}{ansi.bold}[process]{ansi.reset} Syncing {ansi.bg.black}{ansi.dim}{source_dir}{ansi.reset} -> {ansi.bg.black}{ansi.dim}{out_dir}{ansi.reset} -> {ansi.bg.black}{ansi.dim}127.0.0.1:34872{ansi.reset}` | |
| ) | |
| end | |
| return smart_sync |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment