cc-libs/apis/libversion.lua

103 lines
3.0 KiB
Lua

-- libversion: resolve a file's version from its owning package descriptor.
--
-- A factory: `local createVersion = require('/apis/libversion'); local v = createVersion();`
--
-- Resolution order for `api.forFile(path)`:
-- 1. `<stateDir>/ccpm.lock.json` — production lookup against installed packages.
-- 2. `<repoRoot>/packages/*/ccpm.json` — dev fallback (e.g. `just trapos`,
-- where the repo is mounted read-only at `/trapos` and ccpm has never run).
-- 3. `'?'` when no descriptor lists the file.
--
-- `api.forSelf()` returns the version of the currently running program by
-- delegating to `shell.getRunningProgram()`.
local DEFAULT_STATE_DIR = '/trapos';
local DEFAULT_REPO_ROOT = '/trapos';
local function normalizePath(path)
if type(path) ~= 'string' or path == '' then return nil; end
path = path:gsub('\\', '/');
path = path:gsub('//+', '/');
if path:sub(1, 1) ~= '/' then
path = '/' .. path;
end
return path;
end
local function readJsonFile(fsLib, path)
if not fsLib.exists(path) then return nil; end
local f = fsLib.open(path, 'r');
if not f then return nil; end
local data = f.readAll();
f.close();
if not data or data == '' then return nil; end
return textutils.unserializeJSON(data);
end
local function fileMatches(files, target)
if type(files) ~= 'table' then return false; end
for _, raw in ipairs(files) do
if normalizePath(raw) == target then
return true;
end
end
return false;
end
local function createVersion(opts)
opts = opts or {};
local fsLib = opts.fs or fs;
local shellLib = opts.shell or shell;
local stateDir = opts.stateDir or DEFAULT_STATE_DIR;
local repoRoot = opts.repoRoot or DEFAULT_REPO_ROOT;
local lockPath = stateDir .. '/ccpm.lock.json';
local packagesDir = repoRoot .. '/packages';
local api = {};
local function lookupInLock(target)
local lock = readJsonFile(fsLib, lockPath);
if not lock or type(lock.packages) ~= 'table' then return nil; end
for _, entry in pairs(lock.packages) do
if fileMatches(entry.files, target) then
return entry.version;
end
end
return nil;
end
local function lookupInRepo(target)
if not fsLib.isDir(packagesDir) then return nil; end
for _, name in ipairs(fsLib.list(packagesDir)) do
local descPath = packagesDir .. '/' .. name .. '/ccpm.json';
local desc = readJsonFile(fsLib, descPath);
if desc and fileMatches(desc.files, target) then
return desc.version;
end
end
return nil;
end
function api.forFile(path)
local target = normalizePath(path);
if not target then return '?'; end
local v = lookupInLock(target);
if v then return v; end
v = lookupInRepo(target);
if v then return v; end
return '?';
end
function api.forSelf()
if not shellLib or not shellLib.getRunningProgram then return '?'; end
local path = shellLib.getRunningProgram();
if not path or path == '' then return '?'; end
return api.forFile(path);
end
return api;
end
return createVersion;