-- 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. `/ccpm.lock.json` — production lookup against installed packages. -- 2. `/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;