diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 412c418..a341a36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -19,13 +19,13 @@ jobs: allow-external-github-orgs: true - name: Install dependencies - run: lune run install + run: lute scripts/install.luau - name: Run unit tests - run: lune run test + run: lute scripts/test.luau code-quality: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -36,13 +36,13 @@ jobs: allow-external-github-orgs: true - name: Install dependencies - run: lune run install + run: lute scripts/install.luau - name: Lint - run: lune run lint + run: lute scripts/lint.luau analyze: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -53,12 +53,9 @@ jobs: allow-external-github-orgs: true - name: Install dependencies - run: lune run install - - - name: Setup Lune typedefs - run: lune setup + run: lute scripts/install.luau # MUS-2103 TODO: This will error in CI until Foreman is updated to install # the correct linux binary for luau-lsp - name: Analyze - run: lune run analyze + run: lute scripts/analyze.luau diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 136fa19..280bf73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: jobs: build-standalone-zip: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 10 env: BUILD_ARTIFACT_PATH: build/rbxasset.zip @@ -23,10 +23,10 @@ jobs: allow-external-github-orgs: true - name: Install dependencies - run: lune run install + run: lute scripts/install.luau - name: Build - run: lune run build + run: lute scripts/build.luau - uses: actions/upload-artifact@v4 with: diff --git a/.luaurc b/.luaurc index 7779ac6..ba6efda 100644 --- a/.luaurc +++ b/.luaurc @@ -3,6 +3,7 @@ "aliases": { "root": "src", "pkg": "pkg", - "lune": "~/.lune/.typedefs/0.9.3" + "lute": "~/.lute/typedefs/0.1.0/lute/", + "std": "~/.lute/typedefs/0.1.0/std/" } } diff --git a/.lune/analyze.luau b/.lune/analyze.luau deleted file mode 100644 index 630bb10..0000000 --- a/.lune/analyze.luau +++ /dev/null @@ -1,9 +0,0 @@ -local run = require("@root/lib/run") - -local foldersToAnalyze = { - ".lune", - "src", - "examples", -} - -run("luau-lsp", { "analyze", `--ignore="**/pkg/**"`, "--platform=standard", table.unpack(foldersToAnalyze) }) diff --git a/.lune/install.luau b/.lune/install.luau deleted file mode 100644 index 99f3afd..0000000 --- a/.lune/install.luau +++ /dev/null @@ -1,32 +0,0 @@ -local fs = require("@lune/fs") - -local run = require("@root/lib/run") - -local packages = "pkg" -local packageIndex = "_Index" - -local repos = { - { git = "itsfrank/frktest@31770327", main = "src" }, - { git = "osyrisrblx/t@1dbfccc1", main = "lib" }, -} - -run("rm", { "-rf", packages }) - -for _, repo in repos do - local parts = repo.git:split("@") - local repoPath = parts[1] - local rev = parts[2] - - local repoParts = repoPath:split("/") - local projectName = repoParts[2] - local dest = `{packages}/{packageIndex}/{projectName}` - - run("git", { "clone", `https://github.com/{repoPath}`, dest }) - - run("git", { "reset", "--hard", rev }, { - cwd = dest, - }) - - local linkerPath = `{packages}/{projectName}.luau` - fs.writeFile(linkerPath, `return require("./{packageIndex}/{projectName}/{repo.main}")`) -end diff --git a/.lune/test.luau b/.lune/test.luau deleted file mode 100644 index 1de1c0e..0000000 --- a/.lune/test.luau +++ /dev/null @@ -1,28 +0,0 @@ -local process = require("@lune/process") - -local frktest = require("@pkg/frktest") - -local logging = require("@root/logging") -local run = require("@root/lib/run") - -local reporter = frktest._reporters.lune_console_reporter - -local function loadTestCases() - local output = run("find", { "src", "-iname", "*.spec.luau" }) - output = output:gsub("src/", "@root/") - - local files = output:split("\n") - - logging.debug("files", files) - - for _, file in files do - (require :: any)(file) - end -end - -loadTestCases() - -reporter.init() -local success = frktest.run() - -process.exit(if success then 0 else 1) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ea902fc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "luau-lsp.fflags.override": { + "LuauSolverV2": "true" + } +} diff --git a/README.md b/README.md index c913a36..bc11efc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Deploy rbxm files from GitHub to the Creator Store. # Installation > [!NOTE] -> [Lune](https://github.com/lune-org/lune) v0.9.3+ is required. +> [Lute](https://github.com/luau-lang/lute) v0.1.0-nightly.20251023+ is required. ## Prebuilt zip (recommended) @@ -24,8 +24,8 @@ Run the following commands to clone the repo, install dependencies, and build rb git clone https://github.com/Roblox/rbxasset.git cd rbxasset foreman install -lune run install -lune run build +lute scripts/install.luau +lute scripts/build.luau ``` From there, drag and drop `build/rbxasset` to a place where you will require it via a Luau script. @@ -54,25 +54,27 @@ This defines a `default` asset and a `production` environment to deploy to. Then create a Luau script to handle the deployment: ```luau --- .lune/publish.luau -local process = require("@lune/process") +-- scripts/publish.luau +local process = require("@lute/process") local rbxasset = require("./path/to/rbxasset") -local apiKey = process.args[1] +local args = { ... } + +local apiKey = args[1] assert(apiKey, "argument #1 must be a valid Open Cloud API key") -- The rbxm file needs to be built manually. rbxasset makes no assumptions about -- how your project is setup, it only cares about having a file to upload. Note -- the filename `build.rbxm` matches the `model` field in rbxasset.toml -process.exec("rojo", { "build", "-o", "build.rbxm" }) +process.run("rojo build -o build.rbxm") -- Publish the `default` asset defined in rbxasset.toml -rbxasset.publishPackageAsync(process.cwd, "default", apiKey) +rbxasset.publishPackageAsync(process.cwd(), "default", apiKey) ``` ```sh -$ lune run publish +$ lute scripts/publish.luau ``` Where `` represents an Open Cloud API key. See below for the exact setup for the key. diff --git a/examples/package/deploy.luau b/examples/package/deploy.luau index c730c26..97165db 100644 --- a/examples/package/deploy.luau +++ b/examples/package/deploy.luau @@ -1,12 +1,14 @@ local rbxasset = require("@root/") -local process = require("@lune/process") +local process = require("@lute/process") -local apiKey = process.args[1] +local args = { ... } + +local apiKey = args[1] assert(apiKey, "argument #1 must be a valid Open Cloud API key") -assert(process.cwd:match("examples/package"), "you must be in the `examples/package` folder when running this script") +assert(process.cwd():match("examples/package"), "you must be in the `examples/package` folder when running this script") -process.exec("rojo", { "build", "-o", "asset.rbxm" }) +process.run("rojo build -o asset.rbxm") -rbxasset.publishPackageAsync(process.cwd, "package", apiKey) +rbxasset.publishPackageAsync(process.cwd(), "package", apiKey) diff --git a/examples/plugin/deploy.luau b/examples/plugin/deploy.luau index fa5fcd0..8526c6a 100644 --- a/examples/plugin/deploy.luau +++ b/examples/plugin/deploy.luau @@ -1,12 +1,14 @@ local rbxasset = require("@root/") -local process = require("@lune/process") +local process = require("@lute/process") -local apiKey = process.args[1] +local args = { ... } + +local apiKey = args[1] assert(apiKey, "argument #1 must be a valid Open Cloud API key") -assert(process.cwd:match("examples/plugin"), "you must be in the `examples/plugin` folder when running this script") +assert(process.cwd():match("examples/plugin"), "you must be in the `examples/plugin` folder when running this script") -process.exec("rojo", { "build", "-o", "plugin.rbxm" }) +process.run("rojo build -o plugin.rbxm") -rbxasset.publishPackageAsync(process.cwd, "plugin", apiKey) +rbxasset.publishPackageAsync(process.cwd(), "plugin", apiKey) diff --git a/examples/workspace/deploy.luau b/examples/workspace/deploy.luau index 2ee01a2..545c1b1 100644 --- a/examples/workspace/deploy.luau +++ b/examples/workspace/deploy.luau @@ -1,21 +1,23 @@ local rbxasset = require("@root/") -local process = require("@lune/process") +local process = require("@lute/process") -local apiKey = process.args[1] +local args = { ... } + +local apiKey = args[1] assert(apiKey, "argument #1 must be a valid Open Cloud API key") assert( - process.cwd:match("examples/workspace"), + process.cwd():match("examples/workspace"), "you must be in the `examples/workspace` folder when running this script" ) -process.exec("rojo", { "build", "-o", "../package.rbxm" }, { +process.run("rojo build -o ../package.rbxm", { cwd = "package", }) -process.exec("rojo", { "build", "-o", "../plugin.rbxm" }, { +process.run("rojo build -o ../plugin.rbxm", { cwd = "plugin", }) -rbxasset.publishWorkspaceAsync(process.cwd, apiKey) +rbxasset.publishWorkspaceAsync(process.cwd(), apiKey) diff --git a/foreman.toml b/foreman.toml index 538f0f3..102a1ac 100644 --- a/foreman.toml +++ b/foreman.toml @@ -1,6 +1,6 @@ [tools] luau-lsp = { source = "JohnnyMorganz/luau-lsp", version = "=1.53.0" } -lune = { source = "lune-org/lune", version = "=0.9.4" } +lute = { source = "luau-lang/lute", version = "=0.1.0-nightly.20251106" } rojo = { source = "rojo-rbx/rojo", version = "=7.5.0" } selene = { source = "kampfkarren/selene", version = "=0.28.0" } stylua = { source = "johnnymorganz/stylua", version = "=2.1.0" } diff --git a/scripts/analyze.luau b/scripts/analyze.luau new file mode 100644 index 0000000..d8ff4b3 --- /dev/null +++ b/scripts/analyze.luau @@ -0,0 +1,15 @@ +local run = require("@root/lib/run") + +run( + "luau-lsp", + { + "analyze", + "--settings=.vscode/settings.json", + `--ignore="**/pkg/**"`, + "--platform=standard", + + "examples", + "scripts", + "src", + } :: { string } +) diff --git a/.lune/build.luau b/scripts/build.luau similarity index 89% rename from .lune/build.luau rename to scripts/build.luau index ec5f171..27dead9 100644 --- a/.lune/build.luau +++ b/scripts/build.luau @@ -1,4 +1,4 @@ -local fs = require("@lune/fs") +local fs = require("@lute/fs") local run = require("@root/lib/run") @@ -21,7 +21,7 @@ run("rm", { "-rf", BUILD_PATH }) run("mkdir", { "-p", ARTIFACT_PATH }) for _, includeName in INCLUDES do - if fs.isDir(includeName) then + if fs.type(includeName) == "dir" then run("cp", { "-R", includeName, `{ARTIFACT_PATH}/{includeName}` }) else run("cp", { includeName, ARTIFACT_PATH }) diff --git a/scripts/install.luau b/scripts/install.luau new file mode 100644 index 0000000..1c3669c --- /dev/null +++ b/scripts/install.luau @@ -0,0 +1,54 @@ +local fs = require("@lute/fs") + +local run = require("@root/lib/run") + +local PACKAGES_PATH = "pkg" +local PACKAGE_INDEX_NAME = "_Index" + +local function installGitDependencies() + local repos = { + { git = "osyrisrblx/t@1dbfccc1", main = "lib" }, + } + + for _, repo in repos do + local parts = repo.git:split("@") + local repoPath = parts[1] + local rev = parts[2] + + local repoParts = repoPath:split("/") + local projectName = repoParts[2] + local dest = `{PACKAGES_PATH}/{PACKAGE_INDEX_NAME}/{projectName}` + + run("git", { "clone", `https://github.com/{repoPath}`, dest }) + + run("git", { "reset", "--hard", rev }, { + cwd = dest, + }) + + local linkerPath = `{PACKAGES_PATH}/{projectName}.luau` + fs.writestringtofile(linkerPath, `return require("./{PACKAGE_INDEX_NAME}/{projectName}/{repo.main}")`) + end +end + +local function installLuteBatteries() + local commit = "7b4e269" + local batteries = { + "cli.luau", + "toml.luau", + } + + run("mkdir", { "-p", PACKAGES_PATH }) + for _, battery in batteries do + run("curl", { "-LO", `https://raw.githubusercontent.com/luau-lang/lute/{commit}/batteries/{battery}` }, { + cwd = PACKAGES_PATH, + }) + end +end + +run("rm", { "-rf", PACKAGES_PATH }) + +-- Setup typedefs for the current Lute version +run("lute", { "setup" }) + +installGitDependencies() +installLuteBatteries() diff --git a/scripts/lib/find.luau b/scripts/lib/find.luau new file mode 100644 index 0000000..e6b8404 --- /dev/null +++ b/scripts/lib/find.luau @@ -0,0 +1,31 @@ +local fs = require("@lute/fs") + +local function find(root: string, filenamePattern: string): { string } + local matches: { string } = {} + + if not fs.exists(root) or fs.type(root) == "file" then + return matches + end + + local function search(path: string) + if fs.type(path) == "dir" then + for _, child in fs.listdir(path) do + local childPath = `{path}/{child.name}` + search(childPath) + end + else + local parts = path:split("/") + local filename = parts[#parts] + + if filename:match(filenamePattern) then + table.insert(matches, path) + end + end + end + + search(root) + + return matches +end + +return find diff --git a/.lune/lint.luau b/scripts/lint.luau similarity index 100% rename from .lune/lint.luau rename to scripts/lint.luau diff --git a/scripts/test.luau b/scripts/test.luau new file mode 100644 index 0000000..70b6e05 --- /dev/null +++ b/scripts/test.luau @@ -0,0 +1,27 @@ +local process = require("@lute/process") +local test = require("@std/test") + +local find = require("./lib/find") +local logging = require("@root/logging") + +local function loadTestCases() + local files = find("src", "%.spec%.luau") + + logging.info("spec files:") + for _, file in files do + logging.info(` {file}`) + end + + for _, file in files do + local aliasedPath = file:gsub("^src", "@root"); + (require :: any)(aliasedPath) + end +end + +loadTestCases() + +test.setreporter(function(result) + process.exit(if result.failed == 0 then 0 else 1) +end) + +test.run() diff --git a/src/cloud/createLuauTask.luau b/src/cloud/createLuauTask.luau index bfe17b2..bcc701f 100644 --- a/src/cloud/createLuauTask.luau +++ b/src/cloud/createLuauTask.luau @@ -1,32 +1,16 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local fetch = require("@root/requests/fetch") local types = require("@root/types") type AssetId = types.AssetId -type LuauTaskResponse = { - binaryInput: string, - binaryOutputUri: string, - createTime: string, - enableBinaryOutput: boolean, - output: { - results: { any }, - }, - path: string, - script: string, - state: string, - updateTime: string, - user: string, - error: { [string]: any }?, -} - local function createLuauTask( scriptContent: string, universeId: AssetId, placeId: AssetId, apiKey: string -): LuauTaskResponse +): types.LuauTaskResponse local res = fetch(`https://apis.roblox.com/cloud/v2/universes/{universeId}/places/{placeId}/luau-execution-session-tasks`, { method = "POST", @@ -34,12 +18,15 @@ local function createLuauTask( ["Content-Type"] = "application/json", ["x-api-key"] = apiKey, }, - body = serde.encode("json", { + body = json.serialize({ ["script"] = scriptContent, }), }) - return serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: any = json.deserialize(res.body) + + return body end return createLuauTask diff --git a/src/cloud/executeLuauTask.luau b/src/cloud/executeLuauTask.luau index da76c02..10c6349 100644 --- a/src/cloud/executeLuauTask.luau +++ b/src/cloud/executeLuauTask.luau @@ -1,4 +1,4 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local logging = require("@root/logging") local types = require("@root/types") @@ -28,10 +28,10 @@ local function executeLuauTask( logging.debug(`task created: {luauTask.path}`) logging.info("waiting for task to complete...") - luauTask = waitForTaskCompletion(luauTask.path, apiKey) - logging.debug(`task is now in {luauTask.state} state`) + local completedLuauTask = waitForTaskCompletion(luauTask.path, apiKey) + logging.debug(`task is now in {completedLuauTask.state} state`) - local logs = fetchLuauTaskLogs(luauTask.path, apiKey) + local logs = fetchLuauTaskLogs(completedLuauTask.path, apiKey) if #logs == 0 then logging.info("the task did not produce any logs") return {} @@ -51,17 +51,15 @@ local function executeLuauTask( end end - if luauTask.state == "COMPLETE" then - local output = luauTask.output - - if output["results"] then + if completedLuauTask.state == "COMPLETE" then + if completedLuauTask.output.results then logging.info("task output:") - logging.info(serde.encode("json", output["results"], true)) + logging.info(json.serialize(completedLuauTask.output.results, true)) else logging.info("the task did not return any results") end else - logging.err(`Task failed, error:\n{serde.encode("json", luauTask.error)}`) + logging.err(`Task failed, error:\n{json.serialize(completedLuauTask.error)}`) end return variables diff --git a/src/cloud/fetchLuauTaskLogs.luau b/src/cloud/fetchLuauTaskLogs.luau index ede06e7..ad15574 100644 --- a/src/cloud/fetchLuauTaskLogs.luau +++ b/src/cloud/fetchLuauTaskLogs.luau @@ -1,4 +1,4 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local fetch = require("@root/requests/fetch") @@ -9,7 +9,8 @@ local function fetchLuauTaskLogs(taskPath: string, apiKey: string): { string } }, }) - local logs = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local logs: any = json.deserialize(res.body) if #logs["luauExecutionSessionTaskLogs"] == 0 then return {} -- no logs found diff --git a/src/cloud/publishPlaceAsync.luau b/src/cloud/publishPlaceAsync.luau index ec2bfec..6df2a16 100644 --- a/src/cloud/publishPlaceAsync.luau +++ b/src/cloud/publishPlaceAsync.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local json = require("@std/json") local fetch = require("@root/requests/fetch") local logging = require("@root/logging") @@ -21,7 +21,7 @@ local function publishPlaceAsync( local res = fetch(`https://apis.roblox.com/universes/v1/{universeId}/places/{placeId}/versions?versionType={versionType}`, { method = "POST", - body = fs.readFile(placePath), + body = fs.readfiletostring(placePath), headers = { ["x-api-key"] = apiKey, ["Content-Type"] = "application/xml", @@ -30,8 +30,11 @@ local function publishPlaceAsync( }) if res.ok then - local body = serde.decode("json", res.body) - return body.versionNumber :: number + local body = json.deserialize(res.body) + + if typeof(body) == "table" and next(body) ~= nil then + return (body :: any).versionNumber :: number + end end return -1 end diff --git a/src/cloud/waitForTaskCompletion.luau b/src/cloud/waitForTaskCompletion.luau index 5d8725b..83652be 100644 --- a/src/cloud/waitForTaskCompletion.luau +++ b/src/cloud/waitForTaskCompletion.luau @@ -1,9 +1,10 @@ -local serde = require("@lune/serde") -local task = require("@lune/task") +local json = require("@std/json") +local task = require("@lute/task") local fetch = require("@root/requests/fetch") +local types = require("@root/types") -local function waitForTaskCompletion(operationPath: string, apiKey: string) +local function waitForTaskCompletion(operationPath: string, apiKey: string): types.LuauTaskResponse while true do local res = fetch(`https://apis.roblox.com/cloud/v2/{operationPath}`, { headers = { @@ -11,7 +12,8 @@ local function waitForTaskCompletion(operationPath: string, apiKey: string) }, }) - local luauTask = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local luauTask: any = json.deserialize(res.body) if luauTask.state ~= "PROCESSING" then return luauTask diff --git a/src/lib/join.luau b/src/lib/join.luau index 5c63601..428a8f5 100644 --- a/src/lib/join.luau +++ b/src/lib/join.luau @@ -10,7 +10,7 @@ local function join(...: any): T continue end - for key, value in pairs(dictionary) do + for key, value in dictionary do result[key] = value end end diff --git a/src/lib/run.luau b/src/lib/run.luau index 5b85a10..67247be 100644 --- a/src/lib/run.luau +++ b/src/lib/run.luau @@ -1,26 +1,32 @@ -local process = require("@lune/process") - -local logging = require("@root/logging") +local process = require("@lute/process") type Options = { cwd: string?, - env: { [string]: any }?, + env: { [string]: string }?, + stdio: any, } local function run(program: string, params: { string }, options: Options?): string - logging.debug(`RUN > {program} {table.concat(params, " ")}`) + local command = `{program} {table.concat(params, " ")}` + -- stdio.write(stdio.style("bold")) + print(`> {command}`) + -- stdio.write(stdio.style("reset")) - local result = process.exec(program, params, { - stdio = "inherit", + local result = process.run({ + program, + table.unpack(params), + }, { shell = true, + stdio = if options and options.stdio then options.stdio else nil, cwd = if options then options.cwd else nil, env = if options then options.env else nil, }) - if result.code == 0 then - return result.stdout:gsub("\n$", "") + if result.ok then + local trimmed = result.stdout:gsub("^\n", ""):gsub("\n$", "") + return trimmed else - error(result.stderr) + error(result.stderr, 2) end end diff --git a/src/lib/runLuauTask.luau b/src/lib/runLuauTask.luau index 252cba1..33f945a 100644 --- a/src/lib/runLuauTask.luau +++ b/src/lib/runLuauTask.luau @@ -1,6 +1,5 @@ -local fs = require("@lune/fs") -local roblox = require("@lune/roblox") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local json = require("@std/json") local executeLuauTask = require("@root/cloud/executeLuauTask") local logging = require("@root/logging") @@ -20,7 +19,7 @@ local function buildLuauExecutionRobloxPlace(outputPath: string): string }, } - fs.writeFile(rojoProjectPath, serde.encode("json", rojoProject)) + fs.writestringtofile(rojoProjectPath, json.serialize(rojoProject)) run("rojo", { "build", rojoProjectPath, "-o", placePath }) @@ -41,20 +40,20 @@ local function runLuauTask( ) run("rm", { "-rf", TEMP_DIR }) - if not fs.isDir(TEMP_DIR) then - fs.writeDir(TEMP_DIR) + if not fs.exists(TEMP_DIR) then + fs.mkdir(TEMP_DIR) end local placePath = buildLuauExecutionRobloxPlace(TEMP_DIR) if runnerContext.modelPath then - local game = roblox.deserializePlace(fs.readFile(placePath)) - local root = roblox.deserializeModel(fs.readFile(runnerContext.modelPath)) + local game = roblox.deserializePlace(fs.readfiletostring(placePath)) + local root = roblox.deserializeModel(fs.readfiletostring(runnerContext.modelPath)) for _, child in root do child.Parent = game:GetService("ReplicatedStorage") logging.debug(`parented {child} to ReplicatedStorage`) end - fs.writeFile(placePath, roblox.serializePlace(game)) + fs.writestringtofile(placePath, roblox.serializePlace(game)) end logging.info("substituting globals", globals) diff --git a/src/logging.luau b/src/logging.luau index 8979bea..8b2b52a 100644 --- a/src/logging.luau +++ b/src/logging.luau @@ -1,10 +1,8 @@ -local process = require("@lune/process") -local stdio = require("@lune/stdio") +local process = require("@lute/process") +-- local stdio = require("@lute/stdio") type LogLevel = "info" | "warn" | "err" | "debug" -local LOG_LEVEL = if process.env.LOG_LEVEL then process.env.LOG_LEVEL:lower() else "info" - local LOG_LEVEL_ORDER = { "err", "info", @@ -12,8 +10,17 @@ local LOG_LEVEL_ORDER = { "debug", } +local function getLogLevel(): LogLevel + local logLevel = process.env.LOG_LEVEL + if logLevel and logLevel ~= "" then + return logLevel:lower() :: LogLevel + else + return "info" + end +end + local function canLog(logLevelToCheck: LogLevel): boolean - local maxPriority = table.find(LOG_LEVEL_ORDER, LOG_LEVEL) + local maxPriority = table.find(LOG_LEVEL_ORDER, getLogLevel()) local priorityToCheck = table.find(LOG_LEVEL_ORDER, logLevelToCheck) if maxPriority and priorityToCheck then @@ -33,25 +40,25 @@ end function logging.warn(...) if canLog("warn") then - stdio.write(stdio.color("yellow")) + -- stdio.write(stdio.color("yellow")) print(`[warn]`, ...) - stdio.write(stdio.color("reset")) + -- stdio.write(stdio.color("reset")) end end function logging.err(...) if canLog("err") then - stdio.write(stdio.color("red")) + -- stdio.write(stdio.color("red")) print(`[err]`, ...) - stdio.write(stdio.color("reset")) + -- stdio.write(stdio.color("reset")) end end function logging.debug(...) if canLog("debug") then - stdio.write(stdio.color("black")) + -- stdio.write(stdio.color("black")) print(`[debug]`, ...) - stdio.write(stdio.color("reset")) + -- stdio.write(stdio.color("reset")) end end diff --git a/src/manifest/getOrCreateAssetLockfile.luau b/src/manifest/getOrCreateAssetLockfile.luau index 1494ab7..b61f262 100644 --- a/src/manifest/getOrCreateAssetLockfile.luau +++ b/src/manifest/getOrCreateAssetLockfile.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local toml = require("@pkg/toml") local constants = require("@root/constants") local join = require("@root/lib/join") @@ -15,14 +15,14 @@ local function getOrCreateAssetLockfile(packagePath: string): Lockfile -- on the first upload of the asset. So we just catch any error here local content: string local success = pcall(function() - content = fs.readFile(lockfilePath) + content = fs.readfiletostring(lockfilePath) end) - local parsedLockfile = if success then serde.decode("toml", content) else nil - local lockfile: Lockfile = join({ + local parsedLockfile = if success then toml.deserialize(content) else nil + local lockfile = join({ assets = {}, images = {}, - }, parsedLockfile) + }, parsedLockfile) :: Lockfile local isLockfileValid, message = types.validateLockfile(lockfile) assert(isLockfileValid, `failed to parse asset lockfile at {lockfilePath}: {message}`) diff --git a/src/manifest/getOrCreateAssetLockfile.spec.luau b/src/manifest/getOrCreateAssetLockfile.spec.luau index a7d5fcd..0ffadbf 100644 --- a/src/manifest/getOrCreateAssetLockfile.spec.luau +++ b/src/manifest/getOrCreateAssetLockfile.spec.luau @@ -1,17 +1,16 @@ -local frktest = require("@pkg/frktest") -local test = frktest.test -local check = frktest.assert.check +local assert = require("@std/assert") +local test = require("@std/test") local getOrCreateAssetLockfile = require("./getOrCreateAssetLockfile") test.case("reads an asset lockfile from disk", function() local lockfile = getOrCreateAssetLockfile("examples/package") - check.equal(lockfile.assets["package"].assetId, "120786139379662") + assert.eq(lockfile.assets["package"].assetId, "120786139379662") end) test.case("creates a blank asset lockfile when not found on disk", function() local lockfile = getOrCreateAssetLockfile("/nowhere") - check.table.equal(lockfile, { + assert.tableeq(lockfile, { assets = {}, images = {}, }) diff --git a/src/manifest/readManifest.luau b/src/manifest/readManifest.luau index acdf801..37da2ae 100644 --- a/src/manifest/readManifest.luau +++ b/src/manifest/readManifest.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local toml = require("@pkg/toml") local constants = require("@root/constants") local logging = require("@root/logging") @@ -9,13 +9,12 @@ type Manifest = types.Manifest local function readManifest(packagePath: string): Manifest local manifestPath = `{packagePath}/{constants.ASSET_MANIFEST_FILENAME}` - local content: string - local success, err = pcall(function() - content = fs.readFile(manifestPath) + local success, result = pcall(function() + return fs.readfiletostring(manifestPath) end) - assert(success, `failed to read asset manifest at {manifestPath}: {err}`) + assert(success, `failed to read asset manifest at {manifestPath}: {result}`) - local parsedContent = serde.decode("toml", content) + local parsedContent = toml.deserialize(result) local isManifestValid, message = types.validateManifest(parsedContent) assert(isManifestValid, `failed to parse asset manifest at {manifestPath}: {message}`) diff --git a/src/manifest/writeLockfile.luau b/src/manifest/writeLockfile.luau index e15c6c9..81508da 100644 --- a/src/manifest/writeLockfile.luau +++ b/src/manifest/writeLockfile.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local toml = require("@pkg/toml") local constants = require("@root/constants") local logging = require("@root/logging") @@ -9,7 +9,7 @@ type Lockfile = types.Lockfile local function writeLockfile(packagePath: string, lockfile: Lockfile) local lockfilePath = `{packagePath}/{constants.ASSET_LOCKFILE_FILENAME}` - fs.writeFile(lockfilePath, serde.encode("toml", lockfile)) + fs.writestringtofile(lockfilePath, toml.serialize(lockfile)) logging.debug(`wrote asset lockfile {lockfilePath}`) end diff --git a/src/requests/createImageAsync.luau b/src/requests/createImageAsync.luau index 09ac949..1820fa3 100644 --- a/src/requests/createImageAsync.luau +++ b/src/requests/createImageAsync.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local fs = require("@lute/fs") +local json = require("@std/json") local createFormData = require("@root/requests/forms/createFormData") local fetch = require("@root/requests/fetch") @@ -29,7 +29,7 @@ local function createImageAsync( }, fileContent = { filename = imagePath:match("[/\\](.+)$"), - value = fs.readFile(imagePath), + value = fs.readfiletostring(imagePath), contentType = "image/png", }, }) @@ -43,7 +43,8 @@ local function createImageAsync( body = formData.body, }) - local body = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: any = json.deserialize(res.body) return waitForAssetOperationAsync(body.operationId, apiKey) end diff --git a/src/requests/fetch.luau b/src/requests/fetch.luau index 193d972..fb06a50 100644 --- a/src/requests/fetch.luau +++ b/src/requests/fetch.luau @@ -1,5 +1,5 @@ -local net = require("@lune/net") -local serde = require("@lune/serde") +local json = require("@std/json") +local net = require("@lute/net") local logging = require("@root/logging") @@ -16,9 +16,10 @@ local function fetch(url: string, options: FetchOptions) logging.debug(`{method} {url}`) - local res = net.request({ - url = url, - method = method, + net.request(url) + + local res = net.request(url, { + method = method :: string, headers = options.headers, body = options.body, }) @@ -26,9 +27,12 @@ local function fetch(url: string, options: FetchOptions) if res.ok then return res else - local body = serde.decode("json", res.body) + -- TODO: This block might need to be revised to work with Lute + + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: any = json.deserialize(res.body) - local problems = {} + local problems: { string } = {} if body.errors then for _, err in body.errors do table.insert(problems, err.message) @@ -36,7 +40,7 @@ local function fetch(url: string, options: FetchOptions) end error( - `{res.statusCode} {res.statusMessage} when attempting to {method} {url}` + `{res.status} {res.body} when attempting to {method} {url}` .. if #problems > 0 then `: {table.concat(problems, ", ")}` else "", 2 ) diff --git a/src/requests/forms/createFormData.luau b/src/requests/forms/createFormData.luau index 7d0e163..bbf6b55 100644 --- a/src/requests/forms/createFormData.luau +++ b/src/requests/forms/createFormData.luau @@ -1,4 +1,5 @@ -local serde = require("@lune/serde") +local crypto = require("@lute/crypto") +local json = require("@std/json") type FormField = { value: any, @@ -10,7 +11,7 @@ local function createFormDataBody(boundary: string, data: { [string]: FormField local body = {} for key, field in data do - local value = if field.contentType == "application/json" then serde.encode("json", field.value) else field.value + local value = if field.contentType == "application/json" then json.serialize(field.value) else field.value local lines = { `--{boundary}`, @@ -30,7 +31,9 @@ local function createFormDataBody(boundary: string, data: { [string]: FormField end local function createFormData(data: { [string]: FormField }) - local boundary = "LuauFormBoundary" .. serde.hash("md5", tostring(math.random())) + local hash = buffer.tostring(crypto.digest(crypto.hash.md5, tostring(math.random()))) + + local boundary = "LuauFormBoundary" .. hash local body = createFormDataBody(boundary, data) return { diff --git a/src/requests/forms/createFormData.spec.luau b/src/requests/forms/createFormData.spec.luau index 3224f36..1a85ae2 100644 --- a/src/requests/forms/createFormData.spec.luau +++ b/src/requests/forms/createFormData.spec.luau @@ -1,6 +1,5 @@ -local frktest = require("@pkg/frktest") -local test = frktest.test -local check = frktest.assert.check +local assert = require("@std/assert") +local test = require("@std/test") local createFormData = require("./createFormData") @@ -10,7 +9,7 @@ local function assertStringsMatch(base: string, expected: string) table.insert(baseLines, line) end - local expectedLines = {} + local expectedLines: { string } = {} for line in expected:gmatch("[^\r\n]+") do table.insert(expectedLines, line) end @@ -36,7 +35,7 @@ test.case("automatically converts objects to json", function() local match = formData.body:match('{"bar":true,"baz":"str"}') - check.not_nil(match) + assert.neq(match, nil) end) test.case("body supports multiple parts", function() @@ -85,5 +84,5 @@ test.case("the boundary contains LuauFormBoundary", function() }, }) local match = formData.boundary:match("LuauFormBoundary") - check.not_nil(match) + assert.neq(match, nil) end) diff --git a/src/requests/getAssetDetailsAsync.luau b/src/requests/getAssetDetailsAsync.luau index ad57cec..f31cc42 100644 --- a/src/requests/getAssetDetailsAsync.luau +++ b/src/requests/getAssetDetailsAsync.luau @@ -1,4 +1,4 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local fetch = require("@root/requests/fetch") @@ -11,7 +11,10 @@ local function getAssetDetailsAsync(assetId: string, apiKey: string) }, }) - return serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local assetDetails: any = json.deserialize(res.body) + + return assetDetails end return getAssetDetailsAsync diff --git a/src/requests/publishAssetAsync.luau b/src/requests/publishAssetAsync.luau index 46a86f2..0b9e317 100644 --- a/src/requests/publishAssetAsync.luau +++ b/src/requests/publishAssetAsync.luau @@ -52,7 +52,7 @@ local function publishAssetAsync( description = asset.description, }) - local newLockfile = join(lockfile, { + local newLockfile: types.Lockfile = join(lockfile, { assets = join(lockfile.assets, { [assetName] = join(lockfile.assets[assetName], { assetId = assetId, diff --git a/src/requests/publishImagesAsync.luau b/src/requests/publishImagesAsync.luau index 0a84286..d4b45d1 100644 --- a/src/requests/publishImagesAsync.luau +++ b/src/requests/publishImagesAsync.luau @@ -1,5 +1,5 @@ -local fs = require("@lune/fs") -local serde = require("@lune/serde") +local crypto = require("@lute/crypto") +local fs = require("@lute/fs") local createImageAsync = require("@root/requests/createImageAsync") local getOrCreateAssetLockfile = require("@root/manifest/getOrCreateAssetLockfile") @@ -16,6 +16,10 @@ type PendingImage = { assetId: string, } +local function toSha256String(str: string): string + return buffer.tostring(crypto.digest(crypto.hash.sha256, str)) +end + local function publishImagesAsync( projectPath: string, assetConfig: AssetConfig, @@ -33,7 +37,7 @@ local function publishImagesAsync( logging.info(`syncing package icon at {iconPath}`) - local iconHash = serde.hash("sha256", fs.readFile(iconPath)) + local iconHash = toSha256String(fs.readfiletostring(iconPath)) if existingImages and existingImages[icon] and existingImages[icon].hash == iconHash then logging.debug(`hashes match for {iconPath}, skipping...`) @@ -51,11 +55,12 @@ local function publishImagesAsync( local assetId = if operation.response then operation.response.assetId else nil if assetId then - table.insert(pendingImages, { + local pendingImage: PendingImage = { path = iconPath, filename = icon, assetId = assetId, - }) + } + table.insert(pendingImages, pendingImage) logging.debug(`image uploaded successfully`) else @@ -70,7 +75,7 @@ local function publishImagesAsync( for index, thumbnailFilename in thumbnails do local thumbnailPath = `{projectPath}/{thumbnailFilename}` - local thumbnailHash = serde.hash("sha256", fs.readFile(thumbnailPath)) + local thumbnailHash = toSha256String(fs.readfiletostring(thumbnailPath)) local existingThumbnail = existingImages and existingImages[thumbnailFilename] if existingThumbnail and existingThumbnail.hash == thumbnailHash then @@ -92,11 +97,12 @@ local function publishImagesAsync( local assetId = operation.response.assetId if assetId then - table.insert(pendingImages, { + local pendingImage: PendingImage = { path = thumbnailPath, filename = thumbnailFilename, assetId = assetId, - }) + } + table.insert(pendingImages, pendingImage) logging.debug(`image uploaded successfully`) else @@ -107,13 +113,13 @@ local function publishImagesAsync( end if #pendingImages > 0 then - local newAssetLockfile = table.clone(assetLockfile or {}) + local newAssetLockfile = table.clone(assetLockfile or {} :: types.Lockfile) newAssetLockfile.images = newAssetLockfile.images or {} for _, pendingImage in pendingImages do newAssetLockfile.images[pendingImage.filename] = { assetId = pendingImage.assetId, - hash = serde.hash("sha256", fs.readFile(pendingImage.path)), + hash = toSha256String(fs.readfiletostring(pendingImage.path)), } logging.debug( diff --git a/src/requests/publishPackageAsync.luau b/src/requests/publishPackageAsync.luau index 07f727c..50d9fae 100644 --- a/src/requests/publishPackageAsync.luau +++ b/src/requests/publishPackageAsync.luau @@ -1,4 +1,4 @@ -local process = require("@lune/process") +-- local process = require("@lute/process") local logging = require("@root/logging") local publishAssetAsync = require("@root/requests/publishAssetAsync") @@ -12,7 +12,8 @@ local function publishPackageAsync(projectPath: string, assetName: string, apiKe local asset = manifest.assets[assetName] if not asset then logging.err(`publishing failed, could not find an asset named {assetName} in the manifest`) - process.exit(1) + -- process.exit(1) + error(1) end local environment = manifest.environments[asset.environment] diff --git a/src/requests/setAssetDetailsAsync.luau b/src/requests/setAssetDetailsAsync.luau index d57aab8..2dc0225 100644 --- a/src/requests/setAssetDetailsAsync.luau +++ b/src/requests/setAssetDetailsAsync.luau @@ -1,4 +1,4 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local createFormData = require("@root/requests/forms/createFormData") local fetch = require("@root/requests/fetch") @@ -33,7 +33,8 @@ local function setAssetDetailsAsync(assetId: string, apiKey: string, details: As body = formData.body, }) - local body: OperationResponse = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: OperationResponse = json.deserialize(res.body) :: any if body.message then error(body.message) diff --git a/src/requests/setAssetIconAsync.luau b/src/requests/setAssetIconAsync.luau index 68152b2..8e47c7f 100644 --- a/src/requests/setAssetIconAsync.luau +++ b/src/requests/setAssetIconAsync.luau @@ -1,4 +1,4 @@ -local serde = require("@lune/serde") +local json = require("@std/json") local createFormData = require("@root/requests/forms/createFormData") local fetch = require("@root/requests/fetch") @@ -55,7 +55,8 @@ local function setAssetIconAsync(projectPath: string, assetName: string, assetCo body = formData.body, }) - local body = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: any = json.deserialize(res.body) if not body.operationId then logging.err(`failed to set asset icon`) diff --git a/src/requests/waitForAssetOperationAsync.luau b/src/requests/waitForAssetOperationAsync.luau index ca6dd25..e4ea090 100644 --- a/src/requests/waitForAssetOperationAsync.luau +++ b/src/requests/waitForAssetOperationAsync.luau @@ -1,5 +1,5 @@ -local serde = require("@lune/serde") -local task = require("@lune/task") +local json = require("@std/json") +local task = require("@lute/task") local fetch = require("@root/requests/fetch") local types = require("@root/types") @@ -21,7 +21,8 @@ local function waitForAssetOperationAsync(operationId: string, apiKey: string): }, }) - local body = serde.decode("json", res.body) + -- FIXME: Narrow the return value of `json.deserialize` instead of casting to `any` + local body: any = json.deserialize(res.body) if res.ok and body.done then return body diff --git a/src/types.luau b/src/types.luau index c5bba7b..2a25b27 100644 --- a/src/types.luau +++ b/src/types.luau @@ -1,4 +1,4 @@ -local t = require("@pkg/t") +local t: any = require("@pkg/t") local types = {} @@ -79,11 +79,11 @@ types.validateManifest = function(value: any): (boolean, string?) return success, message end - if typeof(value.assets) == "table" and countObject(value.assets) == 0 then + if typeof(value.assets) == "table" and countObject(value.assets :: { [any]: any }) == 0 then return false, "at least one asset must be defined in the manifest" end - if typeof(value.environments) == "table" and countObject(value.environments) == 0 then + if typeof(value.environments) == "table" and countObject(value.environments :: { [any]: any }) == 0 then return false, "at least one environment must be defined in the manifest" end @@ -156,4 +156,20 @@ export type OperationResponse = { }?, } & ErrorStatusResponse +export type LuauTaskResponse = { + binaryInput: string, + binaryOutputUri: string, + createTime: string, + enableBinaryOutput: boolean, + output: { + results: { any }, + }, + path: string, + script: string, + state: string, + updateTime: string, + user: string, + error: { [string]: any }?, +} + return types diff --git a/src/types.spec.luau b/src/types.spec.luau index fda0417..ce328a3 100644 --- a/src/types.spec.luau +++ b/src/types.spec.luau @@ -1,11 +1,13 @@ -local frktest = require("@pkg/frktest") -local test = frktest.test -local check = frktest.assert.check +local assert = require("@std/assert") +local test = require("@std/test") local types = require("./types") -test.suite("validateManifest", function() - test.case("accepts minimal manifest", function() +-- FIXME: The `assert` arg to `case` resolves to an error type right now so +-- we're requiring `@std/assert` manually + +test.suite("validateManifest", function(suite) + suite:case("accepts minimal manifest", function(_assert) local success = types.validateManifest({ assets = { main = { @@ -24,10 +26,10 @@ test.suite("validateManifest", function() }, }) - check.is_true(success) + assert.eq(success, true) end) - test.case("accepts optional manifest values", function() + suite:case("accepts optional manifest values", function() local success = types.validateManifest({ assets = { main = { @@ -49,10 +51,10 @@ test.suite("validateManifest", function() }, }) - check.is_true(success) + assert.eq(success, true) end) - test.case("denies empty assets object", function() + suite:case("denies empty assets object", function() local success, message = types.validateManifest({ assets = {}, environments = { @@ -65,11 +67,11 @@ test.suite("validateManifest", function() }, }) - check.is_false(success) - check.equal(message, "at least one asset must be defined in the manifest") + assert.eq(success, false) + assert.eq(message, "at least one asset must be defined in the manifest") end) - test.case("denies empty environments object", function() + suite:case("denies empty environments object", function() local success, message = types.validateManifest({ assets = { main = { @@ -84,11 +86,11 @@ test.suite("validateManifest", function() environments = {}, }) - check.is_false(success) - check.equal(message, "at least one environment must be defined in the manifest") + assert.eq(success, false) + assert.eq(message, "at least one environment must be defined in the manifest") end) - test.case("assets must map back to a valid environment", function() + suite:case("assets must map back to a valid environment", function() local success, message = types.validateManifest({ assets = { main = { @@ -110,11 +112,11 @@ test.suite("validateManifest", function() }, }) - check.is_false(success) - check.equal(message, `asset "main" attempts to use the environment "does-not-exist" which does not exist`) + assert.eq(success, false) + assert.eq(message, `asset "main" attempts to use the environment "does-not-exist" which does not exist`) end) - test.case("denies optional manifest values of the wrong type", function() + suite:case("denies optional manifest values of the wrong type", function() local success = types.validateManifest({ assets = { main = { @@ -135,16 +137,16 @@ test.suite("validateManifest", function() }, }) - check.is_false(success) + assert.eq(success, false) end) - test.case("denies empty object", function() + suite:case("denies empty object", function() local success = types.validateManifest({}) - check.is_false(success) + assert.eq(success, false) end) - test.case("denies invalid types", function() + suite:case("denies invalid types", function() local success = types.validateManifest({ assets = { main = { @@ -163,12 +165,12 @@ test.suite("validateManifest", function() }, }) - check.is_false(success) + assert.eq(success, false) end) end) -test.suite("validateLockfile", function() - test.case("must have an assetId", function() +test.suite("validateLockfile", function(suite) + suite:case("must have an assetId", function() local success = types.validateLockfile({ assets = { foo = { @@ -178,10 +180,10 @@ test.suite("validateLockfile", function() images = {}, }) - check.is_true(success) + assert.eq(success, true) end) - test.case("handles storing images", function() + suite:case("handles storing images", function() local success = types.validateLockfile({ assets = { foo = { @@ -196,12 +198,12 @@ test.suite("validateLockfile", function() }, }) - check.is_true(success) + assert.eq(success, true) end) - test.case("denies empty object", function() + suite:case("denies empty object", function() local success = types.validateLockfile({}) - check.is_false(success) + assert.eq(success, false) end) end)