diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebb9422 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.zig-cache/ +zig-out/ +zig-pkg/ +*.o diff --git a/README.md b/README.md index 766f9f6..9a273fb 100644 --- a/README.md +++ b/README.md @@ -6,34 +6,14 @@ A [vfox](https://github.com/version-fox/vfox) / [mise](https://mise.jdx.dev) plu - **Dynamic version fetching**: Automatically fetches available versions from lua.org - **Always up-to-date**: No static version list to maintain -- **Compiles from source**: Uses official Lua source releases +- **Compiles from source** via Zig: Cross-platform build with a single compiler - **LuaRocks included**: Automatically installs LuaRocks for Lua 5.x versions -- **Cross-platform**: Works on Linux and macOS +- **Cross-platform**: Works on Linux, macOS, and Windows ## Requirements -- A C compiler (gcc or clang) -- make -- curl - -### macOS - -```bash -xcode-select --install -``` - -### Debian/Ubuntu - -```bash -sudo apt-get install build-essential libreadline-dev -``` - -### RHEL/CentOS - -```bash -sudo yum groupinstall "Development Tools" -sudo yum install readline-devel -``` +- [Zig](https://ziglang.org/download/) 0.16.0 +- make (not needed on Windows) ## Installation @@ -81,7 +61,7 @@ This plugin: 1. Fetches the list of available versions from [lua.org/ftp](https://www.lua.org/ftp/) 2. Downloads the source tarball for the requested version -3. Compiles Lua from source using `make` +3. Copies `build.zig` and a readline shim into the source tree, then compiles with `zig build` 4. Installs LuaRocks (for Lua 5.x versions) ## License diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..9adf6a2 --- /dev/null +++ b/build.zig @@ -0,0 +1,113 @@ +const std = @import("std"); + +fn addExe(b: *std.Build, name: []const u8, src: []const u8, flags: []const []const u8, lib: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Step.Compile { + const mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }); + mod.addIncludePath(b.path("src")); + mod.addCSourceFile(.{ .file = b.path(src), .flags = flags }); + mod.linkLibrary(lib); + + if (target.result.os.tag == .linux) { + mod.linkSystemLibrary("dl", .{}); + } + + const exe = b.addExecutable(.{ .name = name, .root_module = mod }); + return exe; +} + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const readline = b.option(bool, "readline", "Link readline for REPL") orelse false; + + const src_dir = b.build_root.handle.openDir(b.graph.io, "src", .{ .iterate = true }) catch + @panic("src/ directory not found — run zig build from extracted Lua source root"); + defer src_dir.close(b.graph.io); + + var lib_srcs = std.ArrayListUnmanaged([]const u8).empty; + defer lib_srcs.deinit(b.allocator); + + var iter = src_dir.iterate(); + while (iter.next(b.graph.io) catch @panic("failed to read src/")) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.name, ".c")) continue; + if (std.mem.eql(u8, entry.name, "lua.c") or std.mem.eql(u8, entry.name, "luac.c")) continue; + try lib_srcs.append(b.allocator, b.dupe(entry.name)); + } + + const src_root = b.path("src"); + const os_tag = target.result.os.tag; + + var flags = std.ArrayListUnmanaged([]const u8).empty; + defer flags.deinit(b.allocator); + + try flags.append(b.allocator, "-std=gnu99"); + + if (os_tag == .linux or os_tag == .windows) { + try flags.append(b.allocator, "-fvisibility=hidden"); + } + + switch (os_tag) { + .linux => try flags.append(b.allocator, "-DLUA_USE_LINUX"), + .macos => try flags.append(b.allocator, "-DLUA_USE_MACOSX"), + .windows => {}, + else => try flags.append(b.allocator, "-DLUA_USE_POSIX"), + } + + if (readline) { + try flags.append(b.allocator, "-DLUA_USE_READLINE"); + } + + if (optimize == .Debug) { + try flags.append(b.allocator, "-DLUA_USE_APICHECK"); + try flags.append(b.allocator, "-fno-sanitize=undefined"); + } + + const lib_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }); + lib_module.addIncludePath(src_root); + lib_module.addCSourceFiles(.{ + .root = src_root, + .files = lib_srcs.items, + .flags = flags.items, + .language = .c, + }); + + const lib = b.addLibrary(.{ + .name = "lua", + .linkage = .static, + .root_module = lib_module, + }); + + const lua_exe = addExe(b, "lua", "src/lua.c", flags.items, lib, target, optimize); + const luac_exe = addExe(b, "luac", "src/luac.c", flags.items, lib, target, optimize); + + if (readline) { + const linenoize = b.dependency("linenoize", .{ + .target = target, + .optimize = optimize, + }); + lua_exe.root_module.addIncludePath(linenoize.path("include")); + lua_exe.root_module.addIncludePath(b.path(".")); + lua_exe.root_module.addCSourceFile(.{ .file = b.path("readline_shim.c") }); + lua_exe.root_module.linkLibrary(linenoize.artifact("linenoise")); + } + + b.installArtifact(lib); + b.installArtifact(lua_exe); + b.installArtifact(luac_exe); + + const include_install = b.addInstallDirectory(.{ + .source_dir = src_root, + .install_dir = .{ .custom = "include" }, + .install_subdir = "", + .include_extensions = &.{ "lua.h", "luaconf.h", "lualib.h", "lauxlib.h" }, + }); + b.getInstallStep().dependOn(&include_install.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..976cb3e --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = .vfox_lua, + .version = "0.16.0", + .dependencies = .{ + .linenoize = .{ + .url = "git+https://github.com/hazre/linenoize.git#ddfa9a28c607b77359b2b28b84495699759ae3b5", + .hash = "linenoize-0.1.1-J7HK8IT0AABVYIWXsdseNxD1rOuQmikD0tmd1BYlTnFv", + }, + }, + .paths = .{""}, + .fingerprint = 0x6d25d9ed3c490967, +} diff --git a/hk.pkl b/hk.pkl index e4c759b..de5e3a5 100644 --- a/hk.pkl +++ b/hk.pkl @@ -11,6 +11,11 @@ local linters = new Mapping { check = "stylua --check {{files}}" fix = "stylua {{files}}" } + ["zig-fmt"] { + glob = "*.zig" + check = "zig fmt --check {{files}}" + fix = "zig fmt {{files}}" + } ["actionlint"] { glob = ".github/workflows/*.{yml,yaml}" check = "actionlint {{files}}" diff --git a/hooks/env_keys.lua b/hooks/env_keys.lua index 9d775e5..8d6cba7 100644 --- a/hooks/env_keys.lua +++ b/hooks/env_keys.lua @@ -2,33 +2,23 @@ --- @param ctx table Context provided by vfox --- @return table Environment configuration function PLUGIN:EnvKeys(ctx) - local sdkInfo = ctx.sdkInfo["lua"] - local version = sdkInfo.version - local installDir = sdkInfo.path + local version = require("version") - -- Extract major.minor version for Lua paths - local shortVersion = string.match(version, "^(%d+%.%d+)") + local sdkInfo = ctx.sdkInfo["lua"] + local installDir = sdkInfo.path:gsub("\\", "/") + local v = version.parse(sdkInfo.version) + local shortVersion = v and (v.major .. "." .. v.minor) local envs = { - { - key = "PATH", - value = installDir .. "/bin", - }, + { key = "PATH", value = installDir .. "/bin" }, + { key = "PATH", value = installDir .. "/luarocks" }, + { key = "PATH", value = installDir .. "/luarocks/bin" }, } - -- Add LuaRocks bin to PATH if it exists - local luarocksBin = installDir .. "/luarocks/bin" - local f = io.open(luarocksBin, "r") - if f ~= nil then - f:close() - table.insert(envs, { - key = "PATH", - value = luarocksBin, - }) - end - -- Set LUA_INIT for package paths (similar to asdf-lua) if shortVersion then + local soext = (RUNTIME.osType == "windows") and "dll" or "so" + local packagePath = string.format( "package.path = package.path .. ';%s/share/lua/%s/?.lua;%s/share/lua/%s/?/init.lua;%s/luarocks/share/lua/%s/?.lua;%s/luarocks/share/lua/%s/?/init.lua'", installDir, @@ -41,11 +31,13 @@ function PLUGIN:EnvKeys(ctx) shortVersion ) local packageCpath = string.format( - "package.cpath = package.cpath .. ';%s/lib/lua/%s/?.so;%s/luarocks/lib/lua/%s/?.so'", + "package.cpath = package.cpath .. ';%s/lib/lua/%s/?.%s;%s/luarocks/lib/lua/%s/?.%s'", installDir, shortVersion, + soext, installDir, - shortVersion + shortVersion, + soext ) table.insert(envs, { diff --git a/hooks/post_install.lua b/hooks/post_install.lua index 834b971..cbb5025 100644 --- a/hooks/post_install.lua +++ b/hooks/post_install.lua @@ -1,57 +1,98 @@ ---- Compiles and installs Lua from source +--- Compiles and installs Lua from source using the zig build system. --- @param ctx table Context provided by vfox --- @field ctx.sdkInfo table SDK information with version and path function PLUGIN:PostInstall(ctx) + local platform = require("platform") + local version = require("version") + local cmd = require("cmd") local http = require("http") local json = require("json") local sdkInfo = ctx.sdkInfo["lua"] - local version = sdkInfo.version + local luaVersion = sdkInfo.version local sdkPath = sdkInfo.path + local sep = platform.sep - -- mise extracts tarball and strips top-level directory, so sdkPath IS the source directory - - -- Determine OS-specific make target - local os_type = RUNTIME.osType - local make_target = "guess" - - if os_type == "darwin" then - make_target = "macosx" - elseif os_type == "linux" then - -- For Lua < 5.4, use "linux", otherwise "guess" - local major, minor = string.match(version, "^(%d+)%.(%d+)") - if major and minor then - local ver_num = tonumber(major) * 100 + tonumber(minor) - if ver_num < 504 then - make_target = "linux" - end - end + local function join(...) + return table.concat({ ... }, sep) end - -- Build Lua - local major = tonumber(string.match(version, "^(%d+)")) - local buildCmd - - if major and major >= 5 then - -- Lua 5.x: use make local target which creates install/ subdirectory - buildCmd = string.format("cd '%s' && make %s && make local", sdkPath, make_target) - else - -- Older versions - buildCmd = string.format("cd '%s' && make && make install INSTALL_ROOT=install", sdkPath) + local plug = RUNTIME.pluginDirPath + + -- Copy build.zig and readline shim into extracted source tree + -- Shim files are flattened to root level to avoid creating lib/ before the zig install move + platform.mkdir(join(sdkPath, "readline")) + + local files = { + "build.zig", + "build.zig.zon", + { src = "lib/readline_shim.c", dst = "readline_shim.c" }, + { src = "lib/readline/readline.h", dst = "readline/readline.h" }, + { src = "lib/readline/history.h", dst = "readline/history.h" }, + } + for _, f in ipairs(files) do + local src, dst + if type(f) == "string" then + src = plug .. "/" .. f + dst = sdkPath .. "/" .. f + else + src = plug .. "/" .. f.src + dst = sdkPath .. "/" .. f.dst + end + if not platform.cp(src, dst) then + error("Failed to copy " .. (type(f) == "string" and f or f.src)) + end + end + -- Build Lua via zig (compiles liblua.a, lua, luac, and installs headers) + -- Resolve zig path: mise which works for mise-managed zig; fallback to plain "zig" for standalone vfox + local zig_bin = "zig" + local f = io.popen("mise which zig 2>nul") + if f then + local path = f:read("*l") + f:close() + if path and #path > 0 then + zig_bin = path + end + end + local ok, out = pcall( + cmd.exec, + zig_bin + .. " build --build-file " + .. sdkPath + .. sep + .. "build.zig -Dreadline --prefix " + .. sdkPath + .. sep + .. "install", + { cwd = sdkPath } + ) + if not ok then + error("zig build failed: " .. tostring(out)) end - local status = os.execute(buildCmd) - if status ~= 0 and status ~= true then - error("Failed to build Lua: make failed") + -- Move build artifacts from install/ to sdkPath root + local installDir = join(sdkPath, "install") + for _, entry in ipairs(platform.listdir(installDir)) do + platform.mv(join(installDir, entry), join(sdkPath, entry)) end - -- After make local, files are in install/ subdirectory - -- Move them to the root of sdkPath (overwriting source files is fine) - local moveCmd = string.format("cd '%s' && mv install/* . 2>/dev/null || cp -r install/* . 2>/dev/null", sdkPath) - os.execute(moveCmd) + -- Remove build artifacts and source files + for _, item in ipairs({ + "install", + "zig-out", + ".zig-cache", + "zig-pkg", + "build.zig", + "build.zig.zon", + "readline", + "readline_shim.c", + }) do + platform.rm(join(sdkPath, item)) + end -- Install LuaRocks for Lua 5.x - if major and major >= 5 then + local ver = version.parse(luaVersion) + if ver and ver.major >= 5 then -- Get latest LuaRocks version from GitHub releases local luarocksVersion = "3.11.1" -- Default fallback @@ -72,45 +113,71 @@ function PLUGIN:PostInstall(ctx) -- Download and install LuaRocks local luarocksUrl = "https://github.com/luarocks/luarocks/archive/refs/tags/v" .. luarocksVersion .. ".tar.gz" - local luarocksArchive = sdkPath .. "/luarocks.tar.gz" + local luarocksArchive = sdkPath .. sep .. "luarocks.tar.gz" - local downloadCmd = string.format("curl -sL '%s' -o '%s'", luarocksUrl, luarocksArchive) - status = os.execute(downloadCmd) - if status ~= 0 and status ~= true then - -- LuaRocks installation is optional, don't fail - return + local archiver = require("archiver") + local _, dlerr = http.download_file({ url = luarocksUrl }, luarocksArchive) + if dlerr then + error("failed to download luarocks: " .. tostring(dlerr)) end - local extractCmd = string.format("cd '%s' && tar xzf luarocks.tar.gz", sdkPath) - status = os.execute(extractCmd) - if status ~= 0 and status ~= true then - return + archiver.decompress(luarocksArchive, sdkPath) + + local luarocksDir = sdkPath .. sep .. "luarocks-" .. luarocksVersion + + if RUNTIME.osType == "windows" then + -- Windows: use install.bat provided by LuaRocks + local libName = "lua" .. luaVersion:match("^(%d+%.%d+)") .. ".lib" + platform.cp(sdkPath .. sep .. "lib" .. sep .. "lua.lib", sdkPath .. sep .. "lib" .. sep .. libName) + local ok, out = pcall( + cmd.exec, + sdkPath + .. sep + .. "bin" + .. sep + .. "lua.exe install.bat /LUA " + .. sdkPath + .. " /P " + .. sdkPath + .. sep + .. "luarocks /NOADMIN /NOREG /F", + { cwd = luarocksDir } + ) + if not ok then + error("luarocks install failed:\n" .. tostring(out)) + end + else + -- Unix: configure + make bootstrap + local ok, out = pcall( + cmd.exec, + "./configure --with-lua='" + .. sdkPath + .. "' --with-lua-include='" + .. sdkPath + .. "/include' --with-lua-lib='" + .. sdkPath + .. "/lib' --prefix='" + .. sdkPath + .. "/luarocks'", + { cwd = luarocksDir } + ) + if ok then + local ok2, out2 = pcall(cmd.exec, "make bootstrap", { cwd = luarocksDir }) + if not ok2 then + error("luarocks build failed:\n" .. tostring(out2)) + end + end end - local luarocksDir = sdkPath .. "/luarocks-" .. luarocksVersion - local configureCmd = string.format( - "cd '%s' && ./configure --with-lua='%s' --with-lua-include='%s/include' --with-lua-lib='%s/lib' --prefix='%s/luarocks' 2>/dev/null", - luarocksDir, - sdkPath, - sdkPath, - sdkPath, - sdkPath - ) - status = os.execute(configureCmd) - if status ~= 0 and status ~= true then - -- Clean up and return without luarocks - os.execute(string.format("rm -rf '%s/luarocks.tar.gz' '%s/luarocks-'*", sdkPath, sdkPath)) - return + -- Clean up LuaRocks source and archive + platform.rm(luarocksArchive) + if platform.exists(luarocksDir) then + platform.rm(luarocksDir) end - - local bootstrapCmd = string.format("cd '%s' && make bootstrap 2>&1", luarocksDir) - os.execute(bootstrapCmd) - - -- Clean up LuaRocks source - os.execute(string.format("rm -rf '%s/luarocks.tar.gz' '%s/luarocks-'*", sdkPath, sdkPath)) end - -- Clean up Lua source files (keep only bin, lib, include, man, share, luarocks) - local cleanCmd = string.format("cd '%s' && rm -rf src doc Makefile README install 2>/dev/null", sdkPath) - os.execute(cleanCmd) + -- Clean up Lua source files, keeping only bin/, lib/, include/, share/ + for _, item in ipairs({ "src", "doc", "Makefile", "README" }) do + platform.rm(sdkPath .. sep .. item) + end end diff --git a/lib/platform.lua b/lib/platform.lua new file mode 100644 index 0000000..9ad4a18 --- /dev/null +++ b/lib/platform.lua @@ -0,0 +1,112 @@ +local os_type = RUNTIME.osType + +local M = {} + +M.sep = (os_type == "windows") and "\\" or "/" + +function M.cp(src, dst) + if os_type == "windows" then + src = src:gsub("\\", "/") + dst = dst:gsub("\\", "/") + local f = io.open(src, "rb") + if not f then + return false, "source not found: " .. src + end + local content = f:read("*a") + f:close() + f = io.open(dst, "wb") + if not f then + return false, "cannot write: " .. dst + end + f:write(content) + f:close() + return true + else + local ok = os.execute("cp '" .. src .. "' '" .. dst .. "' 2>/dev/null") + if ok ~= 0 and ok ~= true then + return false, "cp failed: " .. src .. " -> " .. dst + end + return true + end +end + +function M.mkdir(path) + if os_type == "windows" then + local p = io.popen('mkdir "' .. path .. '" 2>nul') + if p then + p:close() + end + else + os.execute("mkdir -p '" .. path .. "' 2>/dev/null") + end +end + +function M.mv(src, dst) + if os_type == "windows" then + local ok, _ = pcall(os.rename, src, dst) + if not ok then + M.cp(src, dst) + local p = io.popen('del /f /q "' .. src .. '" 2>nul') + if p then + p:close() + end + end + else + local ok = os.execute("mv '" .. src .. "' '" .. dst .. "' 2>/dev/null") + if ok ~= 0 and ok ~= true then + M.cp(src, dst) + os.execute("rm -rf '" .. src .. "' 2>/dev/null") + end + end +end + +function M.listdir(path) + local entries = {} + if os_type == "windows" then + local f = io.popen('dir /b "' .. path .. '" 2>nul') + if f then + for entry in f:lines() do + table.insert(entries, entry) + end + f:close() + end + else + local f = io.popen("ls -1 '" .. path .. "' 2>/dev/null") + if f then + for entry in f:lines() do + table.insert(entries, entry) + end + f:close() + end + end + return entries +end + +function M.exists(path) + local f, _, code = io.open(path) + if code == 13 or code == 5 or code == 21 then + return true + end + if f then + f:close() + return true + end + return false +end + +function M.rm(path) + if os_type == "windows" then + local p = io.popen('rmdir /s /q "' .. path .. '" 2>nul') + if p then + p:close() + end + p = io.popen('del /f /q "' .. path .. '" 2>nul') + if p then + p:close() + end + else + os.execute("rm -rf '" .. path .. "' 2>/dev/null") + end +end + +return M diff --git a/lib/readline/history.h b/lib/readline/history.h new file mode 100644 index 0000000..29568c9 --- /dev/null +++ b/lib/readline/history.h @@ -0,0 +1,7 @@ +#ifndef _READLINE_HISTORY_H_ +#define _READLINE_HISTORY_H_ + +void add_history(const char *); +void using_history(void); + +#endif diff --git a/lib/readline/readline.h b/lib/readline/readline.h new file mode 100644 index 0000000..70df3d6 --- /dev/null +++ b/lib/readline/readline.h @@ -0,0 +1,9 @@ +#ifndef _READLINE_H_ +#define _READLINE_H_ + +extern char *rl_readline_name; + +char *readline(const char *); +void rl_free(void *); + +#endif diff --git a/lib/readline_shim.c b/lib/readline_shim.c new file mode 100644 index 0000000..5035289 --- /dev/null +++ b/lib/readline_shim.c @@ -0,0 +1,21 @@ +#include +#include "linenoise.h" + +char *rl_readline_name = "linenoise"; +static int history_initialized = 0; + +char *readline(const char *prompt) { + return linenoise(prompt); +} + +void using_history(void) { + if (!history_initialized) { + linenoiseHistorySetMaxLen(1000); + history_initialized = 1; + } +} + +void add_history(const char *line) { + using_history(); + linenoiseHistoryAdd(line); +} diff --git a/lib/version.lua b/lib/version.lua new file mode 100644 index 0000000..e00fea6 --- /dev/null +++ b/lib/version.lua @@ -0,0 +1,15 @@ +local M = {} + +function M.parse(s) + local major, minor, patch = string.match(s, "^(%d+)%.?(%d*)%.?(%d*)$") + if not major then + return nil + end + return { + major = tonumber(major), + minor = tonumber(#minor > 0 and minor or 0), + patch = tonumber(#patch > 0 and patch or 0), + } +end + +return M diff --git a/metadata.lua b/metadata.lua index 69c12cc..04425e8 100644 --- a/metadata.lua +++ b/metadata.lua @@ -1,24 +1,20 @@ -PLUGIN = {} -PLUGIN.name = "lua" -PLUGIN.version = "0.1.0" -PLUGIN.homepage = "https://github.com/mise-plugins/vfox-lua" -PLUGIN.license = "MIT" -PLUGIN.description = "Lua version manager - compiles from source" -PLUGIN.minRuntimeVersion = "0.3.0" -PLUGIN.notes = { - "Compiles Lua from source. Requires a C compiler (gcc/clang).", - "Automatically installs LuaRocks for Lua 5.x versions.", -} +PLUGIN = { + name = "lua", + version = "0.1.0", + homepage = "https://github.com/mise-plugins/vfox-lua", + license = "MIT", + description = "Lua version manager - compiles from source", + minRuntimeVersion = "0.3.0", + notes = { + "Compiles Lua from source. Requires zig compiler (auto-installed via mise).", + "Automatically installs LuaRocks for Lua 5.x versions.", + }, --- System prerequisites checked by mise before installing (see the `system_deps` --- setting). Detection is the source of truth; the `packages` map only provides --- remediation hints. -PLUGIN.systemDependencies = { - { bin = "cc", packages = { apt = "build-essential", dnf = "gcc" } }, - { bin = "make", packages = { brew = "make", apt = "build-essential", dnf = "make" } }, - -- LuaRocks is downloaded with curl in post_install - { bin = "curl", packages = { brew = "curl", apt = "curl", dnf = "curl" } }, - -- Lua links libreadline for interactive line editing on the default build - { pkgconfig = "readline", optional = "readline line editing", - packages = { brew = "readline", apt = "libreadline-dev", dnf = "readline-devel" } }, + -- System prerequisites checked by mise before installing (see the `system_deps` + -- setting). Detection is the source of truth; the `packages` map only provides + -- remediation hints. + systemDependencies = { + { bin = "zig", packages = { brew = "zig", apt = "zig", dnf = "zig" } }, + { bin = "make", packages = { brew = "make", apt = "build-essential", dnf = "make" } }, + }, } diff --git a/mise-tasks/test.mjs b/mise-tasks/test.mjs new file mode 100644 index 0000000..4c11880 --- /dev/null +++ b/mise-tasks/test.mjs @@ -0,0 +1,54 @@ +#!/usr/bin/env node +//MISE description="Run plugin tests - install and execute tool" +//MISE tools={node="latest"} + +import { execSync, spawnSync } from "node:child_process"; + +function run(cmd, opts = {}) { + const result = execSync(cmd, { + encoding: "utf-8", + stdio: opts.silent ? "pipe" : "inherit", + timeout: 300000, + ...opts, + }); + return result?.trim() ?? ""; +} + +function test(name, fn) { + process.stdout.write(` ${name}... `); + try { + fn(); + console.log("āœ“"); + } catch (e) { + console.log("āœ—"); + console.log(` ${e.message}`); + process.exit(1); + } +} + +const LUA_VERSION = process.env.LUA_TEST_VERSION || "5.4.7"; + +run("mise plugin link --force lua ."); +run("mise cache clear"); +run(`mise uninstall lua@${LUA_VERSION}`); +run(`mise install lua@${LUA_VERSION}`); + +test("lua binary reports correct version", () => { + const out = run(`mise exec lua@${LUA_VERSION} -- lua -v 2>&1`, { + stdio: "pipe", + }); + if (!out.includes(LUA_VERSION)) { + throw new Error(`expected ${LUA_VERSION} in output:\n${out}`); + } +}); + +test("luarocks is available", () => { + const out = run(`mise exec lua@${LUA_VERSION} -- luarocks --version`, { + stdio: "pipe", + }); + if (!out.toLowerCase().includes("luarocks")) { + throw new Error(`luarocks not found:\n${out}`); + } +}); + +console.log("\nāœ“ All tests passed"); diff --git a/mise.toml b/mise.toml index bdb971e..40e0f40 100644 --- a/mise.toml +++ b/mise.toml @@ -6,12 +6,14 @@ actionlint = "latest" hk = "latest" lua = "5.4" lua-language-server = "latest" +make = "latest" pkl = "latest" stylua = "latest" +zig = "0.16.0" [tasks.format] description = "Format Lua scripts" -run = "stylua metadata.lua hooks/" +run = "stylua metadata.lua hooks/ lib/" [tasks.lint] description = "Lint Lua scripts and GitHub Actions using hk" @@ -24,3 +26,4 @@ run = "hk fix" [tasks.ci] description = "Run all CI checks" depends = ["lint", "test"] +