fix(ccpm): drop github registry support

This commit is contained in:
Guillaume ARM 2026-06-15 01:12:53 +02:00
parent e10a1d5976
commit ac1a7385e9
8 changed files with 370 additions and 313 deletions

View File

@ -37,7 +37,7 @@ Use [`docs/README.md`](docs/README.md) as the entrypoint for CC:Tweaked, CraftOS
- Preserve `periphemu` guards used for CraftOS-PC emulation; see [`docs/craftos_pc_glossary.md`](docs/craftos_pc_glossary.md) for upstream emulator references. - Preserve `periphemu` guards used for CraftOS-PC emulation; see [`docs/craftos_pc_glossary.md`](docs/craftos_pc_glossary.md) for upstream emulator references.
- Global CraftOS-PC save data lives in `~/Library/Application Support/CraftOS-PC` on macOS and `~/.local/share/craftos-pc` on Linux. Repo launchers use local state instead: `.craftos/` for TrapOS and `.craftos-vanilla/` for vanilla CraftOS-PC. See [`docs/install-craftos-pc.md`](docs/install-craftos-pc.md). - Global CraftOS-PC save data lives in `~/Library/Application Support/CraftOS-PC` on macOS and `~/.local/share/craftos-pc` on Linux. Repo launchers use local state instead: `.craftos/` for TrapOS and `.craftos-vanilla/` for vanilla CraftOS-PC. See [`docs/install-craftos-pc.md`](docs/install-craftos-pc.md).
- TrapOS ships as packages, each described by `packages/<name>/ccpm.json` (`name`, `version`, `dependencies`, `files`, `autostart`); `packages/index.json` lists them. Source files stay in place — descriptors only reference them. To ship a new file, add it to the right package's `files` (and `autostart` if it is a server). `packages/trapos/ccpm.json` is the full OS meta-package. See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md). - TrapOS ships as packages, each described by `packages/<name>/ccpm.json` (`name`, `version`, `dependencies`, `files`, `autostart`); `packages/index.json` lists them. Source files stay in place — descriptors only reference them. To ship a new file, add it to the right package's `files` (and `autostart` if it is a server). `packages/trapos/ccpm.json` is the full OS meta-package. See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md).
- `install-trapos.lua` is the one-time wget bootstrap (public entry point `https://trapos.trapcloud.fr/install`). It installs `trapos-core`/`ccpm`, seeds the default `guillaumearm/cc-libs` registry as a `gitea` registry on `git.trapcloud.fr` tracking `master` (the only channel), then runs `ccpm update` + `ccpm install trapos` and `trapos-postinstall`. Pass `--cpm-only` to stop after the ccpm bootstrap. The legacy `github` registry type still resolves but is deprecated. - `install-trapos.lua` is the one-time wget bootstrap (public entry point `https://trapos.trapcloud.fr/install`). It installs `trapos-core`/`ccpm`, seeds the default `guillaumearm/cc-libs` registry as a `gitea` registry on `git.trapcloud.fr` tracking `master` (the only channel), then runs `ccpm update` + `ccpm install trapos` and `trapos-postinstall`. Pass `--cpm-only` to stop after the ccpm bootstrap.
- `ccpm` (in `trapos-core`) is the package manager: `apis/libccpm.lua` is the testable core (factory with injectable `http`/`stateDir`/`installRoot`), `programs/ccpm.lua` the CLI. State: `/trapos/ccpm.json` (registries), `/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json` (available packages from `ccpm update`). Never use the word "manifest" in ccpm — it is reserved for the OS manifest. - `ccpm` (in `trapos-core`) is the package manager: `apis/libccpm.lua` is the testable core (factory with injectable `http`/`stateDir`/`installRoot`), `programs/ccpm.lua` the CLI. State: `/trapos/ccpm.json` (registries), `/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json` (available packages from `ccpm update`). Never use the word "manifest" in ccpm — it is reserved for the OS manifest.
- Add new servers to `startup/servers.lua` as needed. - Add new servers to `startup/servers.lua` as needed.

View File

@ -51,7 +51,7 @@ ccpm install <package> ccpm reinstall <package> ccpm uninstall <package>
ccpm update ccpm upgrade ccpm update ccpm upgrade
ccpm ls ccpm available [term] ccpm search [term] ccpm ls ccpm available [term] ccpm search [term]
ccpm info <package> ccpm info <package>
ccpm registry ls ccpm registry add <name> [--branch <b>] [--type gitea|github|http] ccpm registry ls ccpm registry add <name> [--branch <b>] [--type gitea|http]
ccpm registry rm <name> ccpm registry rm <name>
``` ```
@ -59,7 +59,8 @@ ccpm registry rm <name>
`ccpm available` lists cached packages and marks installed packages as up-to-date or `ccpm available` lists cached packages and marks installed packages as up-to-date or
updatable. `ccpm upgrade` upgrades installed packages based on that cache. updatable. `ccpm upgrade` upgrades installed packages based on that cache.
Registries default to GitHub (`owner/repo`); `http`/`https` base URLs are also Registries default to gitea (`owner/repo` resolved against
`git.trapcloud.fr/<owner>/<repo>`); `http`/`https` base URLs are also
supported. State lives in `/trapos/ccpm.json` (registries), supported. State lives in `/trapos/ccpm.json` (registries),
`/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json` `/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json`
(available packages). See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md). (available packages). See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md).

View File

@ -173,10 +173,7 @@ local function createCcpm(opts)
-- ---------- URL resolution ---------- -- ---------- URL resolution ----------
function api.registryBaseUrl(registry) function api.registryBaseUrl(registry)
if registry.type == 'github' then if registry.type == 'gitea' then
local branch = registry.branch or 'master';
return 'https://raw.githubusercontent.com/' .. registry.name .. '/' .. branch .. '/';
elseif registry.type == 'gitea' then
local branch = registry.branch or 'master'; local branch = registry.branch or 'master';
return 'https://git.trapcloud.fr/' .. registry.name .. '/raw/branch/' .. branch .. '/'; return 'https://git.trapcloud.fr/' .. registry.name .. '/raw/branch/' .. branch .. '/';
end end

View File

@ -67,9 +67,7 @@ Local state lives under `/trapos`:
- `ccpm.json` — ordered registry list `{ registries = { { name, type, branch } } }`. - `ccpm.json` — ordered registry list `{ registries = { { name, type, branch } } }`.
`type` is `gitea` (resolves to `git.trapcloud.fr/<name>/raw/branch/<branch>/`, the `type` is `gitea` (resolves to `git.trapcloud.fr/<name>/raw/branch/<branch>/`, the
default seeded by the bootstrap), `github` (resolves to default seeded by the bootstrap) or `http`/`https` (the `name` is a base URL).
`raw.githubusercontent.com/<name>/<branch>/`, deprecated but still supported), or
`http`/`https` (the `name` is a base URL).
- `ccpm.lock.json` — installed packages `{ packages = { <name> = { version, registry, - `ccpm.lock.json` — installed packages `{ packages = { <name> = { version, registry,
files, dependencies, autostart } } }`, used by `ls`, `uninstall`, and `reinstall`. files, dependencies, autostart } } }`, used by `ls`, `uninstall`, and `reinstall`.
- `ccpm.cache.json` — packages advertised by configured registries, written by - `ccpm.cache.json` — packages advertised by configured registries, written by

View File

@ -1,6 +1,6 @@
{ {
"packages": { "packages": {
"trapos-core": "0.6.0", "trapos-core": "0.6.1",
"trapos-test": "0.2.1", "trapos-test": "0.2.1",
"trapos-boot": "0.3.3", "trapos-boot": "0.3.3",
"trapos-net": "0.3.0", "trapos-net": "0.3.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "trapos-core", "name": "trapos-core",
"version": "0.6.0", "version": "0.6.1",
"description": "TrapOS base: package manager, event loop, upgrade and event tools", "description": "TrapOS base: package manager, event loop, upgrade and event tools",
"dependencies": [], "dependencies": [],
"files": [ "files": [

View File

@ -17,7 +17,7 @@ local function printUsage()
print('\t\tccpm search [term]'); print('\t\tccpm search [term]');
print('\t\tccpm info <package>'); print('\t\tccpm info <package>');
print('\t\tccpm registry ls'); print('\t\tccpm registry ls');
print('\t\tccpm registry add <name> [--branch <b>] [--type github|gitea|http]'); print('\t\tccpm registry add <name> [--branch <b>] [--type gitea|http]');
print('\t\tccpm registry rm <name>'); print('\t\tccpm registry rm <name>');
print('\t\tccpm version'); print('\t\tccpm version');
print('\t\tccpm help'); print('\t\tccpm help');
@ -178,9 +178,7 @@ if command == 'registry' then
return; return;
end end
for _, r in ipairs(registries) do for _, r in ipairs(registries) do
if r.type == 'github' then if r.type == 'gitea' then
print(r.name .. ' (github:' .. tostring(r.branch or 'master') .. ')');
elseif r.type == 'gitea' then
print(r.name .. ' (gitea:' .. tostring(r.branch or 'master') .. ')'); print(r.name .. ' (gitea:' .. tostring(r.branch or 'master') .. ')');
else else
print(r.name .. ' (' .. tostring(r.type or 'http') .. ')'); print(r.name .. ' (' .. tostring(r.type or 'http') .. ')');

View File

@ -1,352 +1,415 @@
local createLibTest = require('/apis/libtest'); local createLibTest = require("/apis/libtest")
local createCcpm = require('/apis/libccpm'); local createCcpm = require("/apis/libccpm")
local testlib = createLibTest({ ... }); local testlib = createLibTest({ ... })
local counter = 0; local counter = 0
-- Fresh, isolated state + install sandbox per case (never touches real /trapos). -- Fresh, isolated state + install sandbox per case (never touches real /trapos).
local function freshDirs() local function freshDirs()
counter = counter + 1; counter = counter + 1
local stateDir = '/ccpm-test/state-' .. counter; local stateDir = "/ccpm-test/state-" .. counter
local installRoot = '/ccpm-test/root-' .. counter; local installRoot = "/ccpm-test/root-" .. counter
fs.delete(stateDir); fs.delete(stateDir)
fs.delete(installRoot); fs.delete(installRoot)
return stateDir, installRoot; return stateDir, installRoot
end end
-- Minimal stub of the CC `http` API backed by a url -> body map. -- Minimal stub of the CC `http` API backed by a url -> body map.
local function fakeHttp(routes) local function fakeHttp(routes)
return { return {
get = function(url) get = function(url)
local body = routes[url]; local body = routes[url]
if not body then return nil; end if not body then
return { return nil
readAll = function() return body; end, end
close = function() end, return {
}; readAll = function()
end, return body
}; end,
end close = function() end,
}
local function ghBase(name, branch) end,
return 'https://raw.githubusercontent.com/' .. name .. '/' .. branch .. '/'; }
end end
local function giteaBase(name, branch) local function giteaBase(name, branch)
return 'https://git.trapcloud.fr/' .. name .. '/raw/branch/' .. branch .. '/'; return "https://git.trapcloud.fr/" .. name .. "/raw/branch/" .. branch .. "/"
end end
testlib.test('registryBaseUrl resolves a github branch', function() testlib.test("registryBaseUrl resolves a gitea branch", function()
local ccpm = createCcpm({ stateDir = freshDirs() }); local ccpm = createCcpm({ stateDir = freshDirs() })
testlib.assertEquals( testlib.assertEquals(
ccpm.registryBaseUrl({ name = 'guillaumearm/cc-libs', type = 'github', branch = 'next' }), ccpm.registryBaseUrl({ name = "guillaumearm/cc-libs", type = "gitea", branch = "next" }),
'https://raw.githubusercontent.com/guillaumearm/cc-libs/next/' "https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/"
); )
end); testlib.assertEquals(
ccpm.descriptorUrl({ name = "guillaumearm/cc-libs", type = "gitea", branch = "next" }, "trapos-net"),
"https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/packages/trapos-net/ccpm.json"
)
end)
testlib.test('registryBaseUrl resolves a gitea branch', function() testlib.test("registry urls resolve an http base", function()
local ccpm = createCcpm({ stateDir = freshDirs() }); local ccpm = createCcpm({ stateDir = freshDirs() })
testlib.assertEquals( testlib.assertEquals(
ccpm.registryBaseUrl({ name = 'guillaumearm/cc-libs', type = 'gitea', branch = 'next' }), ccpm.registryBaseUrl({ name = "http://example.com/repo", type = "http" }),
'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/' "http://example.com/repo/"
); )
testlib.assertEquals( testlib.assertEquals(
ccpm.descriptorUrl({ name = 'guillaumearm/cc-libs', type = 'gitea', branch = 'next' }, 'trapos-net'), ccpm.descriptorUrl({ name = "http://example.com/repo/", type = "http" }, "trapos-net"),
'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/packages/trapos-net/ccpm.json' "http://example.com/repo/packages/trapos-net/ccpm.json"
); )
end); end)
testlib.test('registry urls resolve an http base', function() testlib.test("resolve orders dependencies before dependents", function()
local ccpm = createCcpm({ stateDir = freshDirs() }); local base = giteaBase("me/repo", "master")
testlib.assertEquals( local routes = {
ccpm.registryBaseUrl({ name = 'http://example.com/repo', type = 'http' }), [base .. "packages/a/ccpm.json"] = textutils.serializeJSON({
'http://example.com/repo/' name = "a",
); version = "1",
testlib.assertEquals( dependencies = { "b", "c" },
ccpm.descriptorUrl({ name = 'http://example.com/repo/', type = 'http' }, 'trapos-net'), files = {},
'http://example.com/repo/packages/trapos-net/ccpm.json' }),
); [base .. "packages/b/ccpm.json"] = textutils.serializeJSON({
end); name = "b",
version = "1",
dependencies = { "c" },
files = {},
}),
[base .. "packages/c/ccpm.json"] = textutils.serializeJSON({
name = "c",
version = "1",
dependencies = {},
files = {},
}),
}
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
local ordered, err = ccpm.resolve("a")
testlib.assertTrue(ordered, tostring(err))
testlib.assertEquals(#ordered, 3)
testlib.assertEquals(ordered[1].name, "c")
testlib.assertEquals(ordered[2].name, "b")
testlib.assertEquals(ordered[3].name, "a")
end)
testlib.test('resolve orders dependencies before dependents', function() testlib.test("resolve reports a missing package", function()
local base = ghBase('me/repo', 'master'); local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp({}) })
local routes = { ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
[base .. 'packages/a/ccpm.json'] = textutils.serializeJSON({ name = 'a', version = '1', dependencies = { 'b', 'c' }, files = {} }), local ordered, err = ccpm.resolve("ghost")
[base .. 'packages/b/ccpm.json'] = textutils.serializeJSON({ name = 'b', version = '1', dependencies = { 'c' }, files = {} }), testlib.assertTrue(not ordered)
[base .. 'packages/c/ccpm.json'] = textutils.serializeJSON({ name = 'c', version = '1', dependencies = {}, files = {} }), testlib.assertTrue(string.find(err, "not found", 1, true))
}; end)
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } });
local ordered, err = ccpm.resolve('a');
testlib.assertTrue(ordered, tostring(err));
testlib.assertEquals(#ordered, 3);
testlib.assertEquals(ordered[1].name, 'c');
testlib.assertEquals(ordered[2].name, 'b');
testlib.assertEquals(ordered[3].name, 'a');
end);
testlib.test('resolve reports a missing package', function() testlib.test("resolve detects a dependency cycle", function()
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp({}) }); local base = giteaBase("me/repo", "master")
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } }); local routes = {
local ordered, err = ccpm.resolve('ghost'); [base .. "packages/a/ccpm.json"] = textutils.serializeJSON({
testlib.assertTrue(not ordered); name = "a",
testlib.assertTrue(string.find(err, 'not found', 1, true)); version = "1",
end); dependencies = { "b" },
}),
[base .. "packages/b/ccpm.json"] = textutils.serializeJSON({
name = "b",
version = "1",
dependencies = { "a" },
}),
}
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
local ordered, err = ccpm.resolve("a")
testlib.assertTrue(not ordered)
testlib.assertTrue(string.find(err, "cycle", 1, true))
end)
testlib.test('resolve detects a dependency cycle', function() testlib.test("install rejects an already-installed package", function()
local base = ghBase('me/repo', 'master'); local ccpm = createCcpm({ stateDir = freshDirs() })
local routes = { ccpm.writeLock({ packages = { foo = { version = "1", files = {} } } })
[base .. 'packages/a/ccpm.json'] = textutils.serializeJSON({ name = 'a', version = '1', dependencies = { 'b' } }), local ok, msg = ccpm.install("foo", {})
[base .. 'packages/b/ccpm.json'] = textutils.serializeJSON({ name = 'b', version = '1', dependencies = { 'a' } }), testlib.assertTrue(not ok)
}; testlib.assertTrue(string.find(msg, "already installed", 1, true))
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) }); testlib.assertTrue(string.find(msg, "reinstall foo", 1, true))
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } }); end)
local ordered, err = ccpm.resolve('a');
testlib.assertTrue(not ordered);
testlib.assertTrue(string.find(err, 'cycle', 1, true));
end);
testlib.test('install rejects an already-installed package', function() testlib.test("install downloads files and records the lock", function()
local ccpm = createCcpm({ stateDir = freshDirs() }); local base = giteaBase("me/repo", "master")
ccpm.writeLock({ packages = { foo = { version = '1', files = {} } } }); local routes = {
local ok, msg = ccpm.install('foo', {}); [base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({
testlib.assertTrue(not ok); name = "trapos-core",
testlib.assertTrue(string.find(msg, 'already installed', 1, true)); version = "1",
testlib.assertTrue(string.find(msg, 'reinstall foo', 1, true)); dependencies = {},
end); files = { "apis/eventloop.lua" },
}),
[base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({
name = "trapos-net",
version = "1",
dependencies = { "trapos-core" },
files = { "apis/net.lua" },
}),
[base .. "apis/eventloop.lua"] = "eventloop-body",
[base .. "apis/net.lua"] = "net-body",
}
local sd, root = freshDirs()
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
testlib.test('install downloads files and records the lock', function() local ok = ccpm.install("trapos-net", {})
local base = ghBase('me/repo', 'master'); testlib.assertTrue(ok)
local routes = { testlib.assertTrue(fs.exists(root .. "/apis/net.lua"))
[base .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-core', version = '1', dependencies = {}, files = { 'apis/eventloop.lua' } }), testlib.assertTrue(fs.exists(root .. "/apis/eventloop.lua"))
[base .. 'packages/trapos-net/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-net', version = '1', dependencies = { 'trapos-core' }, files = { 'apis/net.lua' } }),
[base .. 'apis/eventloop.lua'] = 'eventloop-body',
[base .. 'apis/net.lua'] = 'net-body',
};
local sd, root = freshDirs();
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } });
local ok = ccpm.install('trapos-net', {}); local f = fs.open(root .. "/apis/net.lua", "r")
testlib.assertTrue(ok); local body = f.readAll()
testlib.assertTrue(fs.exists(root .. '/apis/net.lua')); f.close()
testlib.assertTrue(fs.exists(root .. '/apis/eventloop.lua')); testlib.assertEquals(body, "net-body")
local f = fs.open(root .. '/apis/net.lua', 'r'); local lock = ccpm.readLock()
local body = f.readAll(); testlib.assertTrue(lock.packages["trapos-net"])
f.close(); testlib.assertTrue(lock.packages["trapos-core"])
testlib.assertEquals(body, 'net-body'); testlib.assertEquals(lock.packages["trapos-net"].registry, "me/repo")
end)
local lock = ccpm.readLock(); testlib.test("install downloads files from a gitea registry", function()
testlib.assertTrue(lock.packages['trapos-net']); local base = giteaBase("guillaumearm/cc-libs", "next")
testlib.assertTrue(lock.packages['trapos-core']); local routes = {
testlib.assertEquals(lock.packages['trapos-net'].registry, 'me/repo'); [base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({
end); name = "trapos-core",
version = "1",
dependencies = {},
files = { "apis/eventloop.lua" },
}),
[base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({
name = "trapos-net",
version = "1",
dependencies = { "trapos-core" },
files = { "apis/net.lua" },
}),
[base .. "apis/eventloop.lua"] = "eventloop-body",
[base .. "apis/net.lua"] = "net-body",
}
local sd, root = freshDirs()
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "guillaumearm/cc-libs", type = "gitea", branch = "next" } } })
testlib.test('install downloads files from a gitea registry', function() local ok = ccpm.install("trapos-net", {})
local base = giteaBase('guillaumearm/cc-libs', 'next'); testlib.assertTrue(ok)
local routes = { testlib.assertTrue(fs.exists(root .. "/apis/net.lua"))
[base .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-core', version = '1', dependencies = {}, files = { 'apis/eventloop.lua' } }), testlib.assertTrue(fs.exists(root .. "/apis/eventloop.lua"))
[base .. 'packages/trapos-net/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-net', version = '1', dependencies = { 'trapos-core' }, files = { 'apis/net.lua' } }),
[base .. 'apis/eventloop.lua'] = 'eventloop-body',
[base .. 'apis/net.lua'] = 'net-body',
};
local sd, root = freshDirs();
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'guillaumearm/cc-libs', type = 'gitea', branch = 'next' } } });
local ok = ccpm.install('trapos-net', {}); local f = fs.open(root .. "/apis/net.lua", "r")
testlib.assertTrue(ok); local body = f.readAll()
testlib.assertTrue(fs.exists(root .. '/apis/net.lua')); f.close()
testlib.assertTrue(fs.exists(root .. '/apis/eventloop.lua')); testlib.assertEquals(body, "net-body")
local f = fs.open(root .. '/apis/net.lua', 'r'); local lock = ccpm.readLock()
local body = f.readAll(); testlib.assertEquals(lock.packages["trapos-net"].registry, "guillaumearm/cc-libs")
f.close(); end)
testlib.assertEquals(body, 'net-body');
local lock = ccpm.readLock(); testlib.test("installing trapos writes aggregated os state", function()
testlib.assertEquals(lock.packages['trapos-net'].registry, 'guillaumearm/cc-libs'); local base = giteaBase("me/repo", "master")
end); local routes = {
[base .. "packages/trapos/ccpm.json"] = textutils.serializeJSON({
name = "trapos",
version = "1",
dependencies = { "trapos-net" },
files = {},
}),
[base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({
name = "trapos-core",
version = "1",
dependencies = {},
files = { "programs/ccpm.lua" },
}),
[base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({
name = "trapos-net",
version = "1",
dependencies = { "trapos-core" },
files = { "apis/net.lua" },
autostart = { "servers/ping-server" },
}),
[base .. "programs/ccpm.lua"] = "ccpm-body",
[base .. "apis/net.lua"] = "net-body",
}
local sd, root = freshDirs()
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
testlib.test('installing trapos writes aggregated os state', function() testlib.assertTrue(ccpm.install("trapos", {}))
local base = ghBase('me/repo', 'master');
local routes = {
[base .. 'packages/trapos/ccpm.json'] = textutils.serializeJSON({ name = 'trapos', version = '1', dependencies = { 'trapos-net' }, files = {} }),
[base .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-core', version = '1', dependencies = {}, files = { 'programs/ccpm.lua' } }),
[base .. 'packages/trapos-net/ccpm.json'] = textutils.serializeJSON({ name = 'trapos-net', version = '1', dependencies = { 'trapos-core' }, files = { 'apis/net.lua' }, autostart = { 'servers/ping-server' } }),
[base .. 'programs/ccpm.lua'] = 'ccpm-body',
[base .. 'apis/net.lua'] = 'net-body',
};
local sd, root = freshDirs();
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } });
testlib.assertTrue(ccpm.install('trapos', {})); local f = fs.open(sd .. "/manifest.json", "r")
local manifest = textutils.unserializeJSON(f.readAll())
f.close()
testlib.assertEquals(manifest.version, "1")
testlib.assertEquals(#manifest.files, 2)
testlib.assertEquals(manifest.autostart[1], "servers/ping-server")
end)
local f = fs.open(sd .. '/manifest.json', 'r'); testlib.test("registry add and remove round-trip", function()
local manifest = textutils.unserializeJSON(f.readAll()); local ccpm = createCcpm({ stateDir = freshDirs() })
f.close(); ccpm.writeConfig({ registries = {} })
testlib.assertEquals(manifest.version, '1'); testlib.assertTrue(ccpm.addRegistry("foo/bar", { branch = "next" }))
testlib.assertEquals(#manifest.files, 2);
testlib.assertEquals(manifest.autostart[1], 'servers/ping-server');
end);
testlib.test('registry add and remove round-trip', function() local regs = ccpm.listRegistries()
local ccpm = createCcpm({ stateDir = freshDirs() }); testlib.assertEquals(#regs, 1)
ccpm.writeConfig({ registries = {} }); testlib.assertEquals(regs[1].name, "foo/bar")
testlib.assertTrue(ccpm.addRegistry('foo/bar', { branch = 'next' })); testlib.assertEquals(regs[1].branch, "next")
local regs = ccpm.listRegistries(); local dupOk = ccpm.addRegistry("foo/bar", {})
testlib.assertEquals(#regs, 1); testlib.assertTrue(not dupOk)
testlib.assertEquals(regs[1].name, 'foo/bar');
testlib.assertEquals(regs[1].branch, 'next');
local dupOk = ccpm.addRegistry('foo/bar', {}); testlib.assertTrue(ccpm.removeRegistry("foo/bar"))
testlib.assertTrue(not dupOk); testlib.assertEquals(#ccpm.listRegistries(), 0)
testlib.assertTrue(ccpm.removeRegistry('foo/bar')); local rmOk = ccpm.removeRegistry("nope")
testlib.assertEquals(#ccpm.listRegistries(), 0); testlib.assertTrue(not rmOk)
end)
local rmOk = ccpm.removeRegistry('nope'); testlib.test("addRegistry defaults to a gitea registry", function()
testlib.assertTrue(not rmOk); local ccpm = createCcpm({ stateDir = freshDirs() })
end); ccpm.writeConfig({ registries = {} })
testlib.test('addRegistry defaults to a gitea registry', function() local ok, registry = ccpm.addRegistry("foo/bar", {})
local ccpm = createCcpm({ stateDir = freshDirs() }); testlib.assertTrue(ok)
ccpm.writeConfig({ registries = {} }); testlib.assertEquals(registry.type, "gitea")
testlib.assertEquals(registry.branch, "master")
local ok, registry = ccpm.addRegistry('foo/bar', {}); local regs = ccpm.listRegistries()
testlib.assertTrue(ok); testlib.assertEquals(regs[1].type, "gitea")
testlib.assertEquals(registry.type, 'gitea'); testlib.assertEquals(ccpm.registryBaseUrl(regs[1]), "https://git.trapcloud.fr/foo/bar/raw/branch/master/")
testlib.assertEquals(registry.branch, 'master');
local regs = ccpm.listRegistries(); -- An explicit type is still honored.
testlib.assertEquals(regs[1].type, 'gitea'); testlib.assertTrue(ccpm.addRegistry("http://example.com/repo", { type = "http" }))
-- The default registry must not silently fall back to github. local explicit = ccpm.listRegistries()[2]
testlib.assertTrue(regs[1].type ~= 'github'); testlib.assertEquals(explicit.type, "http")
testlib.assertEquals( end)
ccpm.registryBaseUrl(regs[1]),
'https://git.trapcloud.fr/foo/bar/raw/branch/master/'
);
-- An explicit type is still honored (github stays supported but opt-in). testlib.test("compareVersions treats padded zeros as equal", function()
testlib.assertTrue(ccpm.addRegistry('legacy/repo', { type = 'github' })); -- compareVersions is internal; probe via available() status
local legacy = ccpm.listRegistries()[2]; local ccpm = createCcpm({ stateDir = freshDirs() })
testlib.assertEquals(legacy.type, 'github'); ccpm.writeCache({ packages = { pkg = { version = "1.0.0", registry = "r" } } })
end); ccpm.writeLock({ packages = { pkg = { version = "1.0", registry = "r", files = {}, dependencies = {} } } })
local avail = ccpm.available()
testlib.assertEquals(avail[1].status, "up-to-date")
end)
testlib.test('compareVersions treats padded zeros as equal', function() testlib.test("update writes a package cache from registries", function()
-- compareVersions is internal; probe via available() status local base = giteaBase("me/repo", "master")
local ccpm = createCcpm({ stateDir = freshDirs() }); local routes = {
ccpm.writeCache({ packages = { pkg = { version = '1.0.0', registry = 'r' } } }); [base .. "packages/index.json"] = textutils.serializeJSON({ packages = { foo = "1.0.0", bar = "2.0.0" } }),
ccpm.writeLock({ packages = { pkg = { version = '1.0', registry = 'r', files = {}, dependencies = {} } } }); }
local avail = ccpm.available(); local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) })
testlib.assertEquals(avail[1].status, 'up-to-date'); ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
end);
testlib.test('update writes a package cache from registries', function() local cache = ccpm.update()
local base = ghBase('me/repo', 'master'); testlib.assertEquals(cache.packages.foo.version, "1.0.0")
local routes = { testlib.assertEquals(cache.packages.foo.registry, "me/repo")
[base .. 'packages/index.json'] = textutils.serializeJSON({ packages = { foo = '1.0.0', bar = '2.0.0' } }),
};
local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } });
local cache = ccpm.update(); local search = ccpm.search("ba")
testlib.assertEquals(cache.packages.foo.version, '1.0.0'); testlib.assertEquals(#search, 1)
testlib.assertEquals(cache.packages.foo.registry, 'me/repo'); testlib.assertEquals(search[1].name, "bar")
end)
local search = ccpm.search('ba'); testlib.test("available marks cached packages by install status", function()
testlib.assertEquals(#search, 1); local ccpm = createCcpm({ stateDir = freshDirs() })
testlib.assertEquals(search[1].name, 'bar'); ccpm.writeCache({
end); packages = {
alpha = { version = "1.0.0", registry = "me/repo" },
beta = { version = "2.0.0", registry = "me/repo" },
gamma = { version = "1.0.0", registry = "me/repo" },
},
})
ccpm.writeLock({
packages = {
alpha = { version = "1.0.0", files = {}, dependencies = {} },
beta = { version = "1.0.0", files = {}, dependencies = {} },
},
})
testlib.test('available marks cached packages by install status', function() local available = ccpm.available()
local ccpm = createCcpm({ stateDir = freshDirs() }); testlib.assertEquals(#available, 3)
ccpm.writeCache({ packages = { testlib.assertEquals(available[1].name, "alpha")
alpha = { version = '1.0.0', registry = 'me/repo' }, testlib.assertEquals(available[1].status, "up-to-date")
beta = { version = '2.0.0', registry = 'me/repo' }, testlib.assertEquals(available[2].name, "beta")
gamma = { version = '1.0.0', registry = 'me/repo' }, testlib.assertEquals(available[2].status, "updatable")
} }); testlib.assertEquals(available[3].name, "gamma")
ccpm.writeLock({ packages = { testlib.assertEquals(available[3].status, "available")
alpha = { version = '1.0.0', files = {}, dependencies = {} }, end)
beta = { version = '1.0.0', files = {}, dependencies = {} },
} });
local available = ccpm.available(); testlib.test("upgrade reinstalls outdated packages from cache", function()
testlib.assertEquals(#available, 3); local base = giteaBase("me/repo", "master")
testlib.assertEquals(available[1].name, 'alpha'); local routes = {
testlib.assertEquals(available[1].status, 'up-to-date'); [base .. "packages/foo/ccpm.json"] = textutils.serializeJSON({
testlib.assertEquals(available[2].name, 'beta'); name = "foo",
testlib.assertEquals(available[2].status, 'updatable'); version = "2.0.0",
testlib.assertEquals(available[3].name, 'gamma'); dependencies = {},
testlib.assertEquals(available[3].status, 'available'); files = { "programs/foo.lua" },
end); }),
[base .. "programs/foo.lua"] = "foo-v2",
}
local sd, root = freshDirs()
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) })
ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } })
ccpm.writeCache({ packages = { foo = { version = "2.0.0", registry = "me/repo" } } })
ccpm.writeLock({
packages = {
foo = { version = "1.0.0", registry = "me/repo", files = { "programs/foo.lua" }, dependencies = {} },
},
})
testlib.test('upgrade reinstalls outdated packages from cache', function() local ok, upgraded = ccpm.upgrade({})
local base = ghBase('me/repo', 'master'); testlib.assertTrue(ok)
local routes = { testlib.assertEquals(#upgraded, 1)
[base .. 'packages/foo/ccpm.json'] = textutils.serializeJSON({ name = 'foo', version = '2.0.0', dependencies = {}, files = { 'programs/foo.lua' } }), testlib.assertEquals(upgraded[1], "foo")
[base .. 'programs/foo.lua'] = 'foo-v2', testlib.assertEquals(ccpm.readLock().packages.foo.version, "2.0.0")
};
local sd, root = freshDirs();
local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) });
ccpm.writeConfig({ registries = { { name = 'me/repo', type = 'github', branch = 'master' } } });
ccpm.writeCache({ packages = { foo = { version = '2.0.0', registry = 'me/repo' } } });
ccpm.writeLock({ packages = { foo = { version = '1.0.0', registry = 'me/repo', files = { 'programs/foo.lua' }, dependencies = {} } } });
local ok, upgraded = ccpm.upgrade({}); local f = fs.open(root .. "/programs/foo.lua", "r")
testlib.assertTrue(ok); local body = f.readAll()
testlib.assertEquals(#upgraded, 1); f.close()
testlib.assertEquals(upgraded[1], 'foo'); testlib.assertEquals(body, "foo-v2")
testlib.assertEquals(ccpm.readLock().packages.foo.version, '2.0.0'); end)
local f = fs.open(root .. '/programs/foo.lua', 'r'); testlib.test("upgrade requires a package cache", function()
local body = f.readAll(); local ccpm = createCcpm({ stateDir = freshDirs() })
f.close(); ccpm.writeLock({ packages = { foo = { version = "1.0.0", files = {}, dependencies = {} } } })
testlib.assertEquals(body, 'foo-v2');
end);
testlib.test('upgrade requires a package cache', function() local ok, err = ccpm.upgrade({})
local ccpm = createCcpm({ stateDir = freshDirs() }); testlib.assertTrue(not ok)
ccpm.writeLock({ packages = { foo = { version = '1.0.0', files = {}, dependencies = {} } } }); testlib.assertTrue(string.find(err, "ccpm update", 1, true))
end)
local ok, err = ccpm.upgrade({}); testlib.test("uninstall refuses a package with dependents", function()
testlib.assertTrue(not ok); local ccpm = createCcpm({ stateDir = freshDirs() })
testlib.assertTrue(string.find(err, 'ccpm update', 1, true)); ccpm.writeLock({
end); packages = {
["trapos-core"] = { version = "1", files = {}, dependencies = {} },
["trapos-net"] = { version = "1", files = {}, dependencies = { "trapos-core" } },
},
})
local ok, err = ccpm.uninstall("trapos-core", {})
testlib.assertTrue(not ok)
testlib.assertTrue(string.find(err, "required by", 1, true))
testlib.assertTrue(string.find(err, "trapos-net", 1, true))
end)
testlib.test('uninstall refuses a package with dependents', function() testlib.test("uninstall removes a leaf package and its files", function()
local ccpm = createCcpm({ stateDir = freshDirs() }); local sd, root = freshDirs()
ccpm.writeLock({ packages = { fs.makeDir(root .. "/apis")
['trapos-core'] = { version = '1', files = {}, dependencies = {} }, local f = fs.open(root .. "/apis/net.lua", "w")
['trapos-net'] = { version = '1', files = {}, dependencies = { 'trapos-core' } }, f.write("x")
} }); f.close()
local ok, err = ccpm.uninstall('trapos-core', {});
testlib.assertTrue(not ok);
testlib.assertTrue(string.find(err, 'required by', 1, true));
testlib.assertTrue(string.find(err, 'trapos-net', 1, true));
end);
testlib.test('uninstall removes a leaf package and its files', function() local ccpm = createCcpm({ stateDir = sd, installRoot = root })
local sd, root = freshDirs(); ccpm.writeLock({
fs.makeDir(root .. '/apis'); packages = {
local f = fs.open(root .. '/apis/net.lua', 'w'); ["trapos-core"] = { version = "1", files = {}, dependencies = {} },
f.write('x'); ["trapos-net"] = { version = "1", files = { "apis/net.lua" }, dependencies = { "trapos-core" } },
f.close(); },
})
local ccpm = createCcpm({ stateDir = sd, installRoot = root }); testlib.assertTrue(ccpm.uninstall("trapos-net", {}))
ccpm.writeLock({ packages = { testlib.assertTrue(not fs.exists(root .. "/apis/net.lua"))
['trapos-core'] = { version = '1', files = {}, dependencies = {} }, testlib.assertTrue(ccpm.readLock().packages["trapos-net"] == nil)
['trapos-net'] = { version = '1', files = { 'apis/net.lua' }, dependencies = { 'trapos-core' } }, testlib.assertTrue(ccpm.readLock().packages["trapos-core"] ~= nil)
} }); end)
testlib.assertTrue(ccpm.uninstall('trapos-net', {})); testlib.run()
testlib.assertTrue(not fs.exists(root .. '/apis/net.lua'));
testlib.assertTrue(ccpm.readLock().packages['trapos-net'] == nil);
testlib.assertTrue(ccpm.readLock().packages['trapos-core'] ~= nil);
end);
testlib.run();