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.
- 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).
- `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.
- 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 ls ccpm available [term] ccpm search [term]
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>
```
@ -59,7 +59,8 @@ ccpm registry rm <name>
`ccpm available` lists cached packages and marks installed packages as up-to-date or
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),
`/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json`
(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 ----------
function api.registryBaseUrl(registry)
if registry.type == 'github' then
local branch = registry.branch or 'master';
return 'https://raw.githubusercontent.com/' .. registry.name .. '/' .. branch .. '/';
elseif registry.type == 'gitea' then
if registry.type == 'gitea' then
local branch = registry.branch or 'master';
return 'https://git.trapcloud.fr/' .. registry.name .. '/raw/branch/' .. branch .. '/';
end

View File

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

View File

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

View File

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

View File

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

View File

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