First Commit

This commit is contained in:
2026-08-06 12:54:48 -04:00
commit dd01ccb258
15 changed files with 1780 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
-- indexer.lua -- generates packages/index.lua from all package.lua manifests.
--
-- Scans <output>/packages/ for every subdirectory containing a package.lua.
-- Each manifest is loaded in a sandboxed environment (no io, os, etc.) and
-- its name, version, and summary are extracted. The resulting index is
-- written as a Lua list, sorted by package name.
local indexer = {}
local path = require("path")
local log = require("log")
---------------------------------------------------------------------------
-- Sandboxed manifest loading
---------------------------------------------------------------------------
-- Load a package.lua file and extract { name, version, summary }.
-- Runs in a locked-down environment — the manifest cannot access io, os,
-- require, or any global state. Only pure table constructors are allowed.
local function load_manifest_meta(filepath)
local f, err = io.open(filepath, "rb")
if not f then return nil, err end
local src = f:read("*a")
f:close()
local env = {}
local chunk
if setfenv then
chunk, err = loadstring(src, "@" .. filepath)
if chunk then setfenv(chunk, env) end
else
chunk, err = load(src, "@" .. filepath, "t", env)
end
if not chunk then
return nil, ("%s: syntax error: %s"):format(filepath, tostring(err))
end
local ok, raw = pcall(chunk)
if not ok or type(raw) ~= "table" then
return nil, ("%s: must return a table"):format(filepath)
end
local meta = {
name = type(raw.name) == "string" and raw.name or nil,
version = type(raw.version) == "string" and raw.version or nil,
summary = type(raw.summary) == "string" and raw.summary or nil,
}
if not meta.name then
return nil, ("%s: missing or invalid name"):format(filepath)
end
return meta
end
---------------------------------------------------------------------------
-- Index generation
---------------------------------------------------------------------------
-- Scan <packages_dir> for all subdirectories containing package.lua and
-- write a sorted index.lua. Returns the path to the written index.
function indexer.generate(packages_dir)
if not path.exists(packages_dir) then
log.warn("packages directory not found: " .. packages_dir)
return nil
end
log.step("generating index for " .. packages_dir)
-- Collect all package.lua files.
local f = io.popen("find " .. path.quote(packages_dir)
.. " -mindepth 2 -maxdepth 2 -name package.lua"
.. " -type f 2>/dev/null | sort")
if not f then
error(("cannot scan %s"):format(packages_dir), 0)
end
local entries = {}
for filepath in f:lines() do
local meta, err = load_manifest_meta(filepath)
if meta then
entries[#entries + 1] = meta
else
log.warn(tostring(err))
end
end
f:close()
-- Sort by name.
table.sort(entries, function(a, b) return a.name < b.name end)
-- Write index.lua
local index_path = path.join(packages_dir, "index.lua")
local lines = { "return {" }
for _, e in ipairs(entries) do
lines[#lines + 1] = string.format(
' { name = %q, version = %q, summary = %q },',
e.name,
e.version or "",
e.summary or ""
)
end
lines[#lines + 1] = "}"
local tmp = index_path .. ".tmp"
local out, err = io.open(tmp, "wb")
if not out then
error(("cannot write %s: %s"):format(tmp, tostring(err)), 0)
end
out:write(table.concat(lines, "\n") .. "\n")
out:close()
os.rename(tmp, index_path)
log.ok(("index updated (%d packages)"):format(#entries))
return index_path
end
return indexer