Added support for dependency cascading, virtual packages, and groups when removing packages

IT'S NOT SPAGHETTI GUYS TRUST ME
This commit is contained in:
2026-03-22 18:37:39 +02:00
parent 3ab72fe1dd
commit 88f2a55ca0
2 changed files with 145 additions and 52 deletions
+144 -52
View File
@@ -56,7 +56,9 @@ cliparse.config({
["c"] = 0, ["c"] = 0,
["clean"] = 0, ["clean"] = 0,
["s"] = 1, ["s"] = 1,
["source"] = 1 ["source"] = 1,
["C"] = 0,
["cascade"] = 0,
}) })
local parsed, errorMessage = cliparse.parse(...) local parsed, errorMessage = cliparse.parse(...)
@@ -92,30 +94,27 @@ end
local packages = parsed.args local packages = parsed.args
table.remove(packages, 1) table.remove(packages, 1)
-- Remove the command from the actual package list -- Remove the command from the actual package list
local result, data local result, data, failure
if parsed.flags.u or parsed.flags["update-registry"] then do
terminal.write("Updating registry...") local function check(condition, message)
result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json") if not condition then
if not result then print(message)
print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data)) failure = true
return end
end end
local handle, errorMessage = fs.open("/ag2/registry.json", "w")
if not handle then if parsed.flags.u or parsed.flags["update-registry"] then
print(("\27[91mFailed to open write handle to registry: %s\n\27[0mExiting."):format(errorMessage)) terminal.write("Updating registry...")
return result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json")
end check(result, "\27[91mFailed to get registry: " .. data)
local success, errorMessage = handle:write(data) local handle, errorMessage = fs.open("/ag2/registry.json", "w")
if not success then check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage)
print(("\27[91mFailed to write to registry: %s\n\27[0mExiting."):format(errorMessage)) local success, errorMessage = handle:write(data)
return check(success, "\27[91mFailed to write to registry: " .. errorMessage)
end handle:close()
handle:close() else
else result, data = getFile("/ag2/registry.json")
result, data = getFile("/ag2/registry.json") check(result, "\27[91mFailed to get registry: " .. data)
if not result then
print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data))
return
end end
end end
@@ -127,8 +126,25 @@ if not success then
return return
end end
local function getServersidePackageConfig(source)
local success, data = getFile(fs.concat(source, "/ag2.json"))
if not success then
return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(packages[i])
end
local success, packageConfig = pcall(function()
return json.decode(data)
end)
if not success then
return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(packages[i])
end
if not packageConfig[packages[i]] then
return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package)
end
return packageConfig[package]
end
-- Check if everything is valid -- Check if everything is valid
local failure = false failure = false
local dependencyCounter = 0 local dependencyCounter = 0
if command == "install" then if command == "install" then
for i = 1, #packages do for i = 1, #packages do
@@ -138,49 +154,123 @@ if command == "install" then
i = i - 1 i = i - 1
goto SKIP goto SKIP
end end
local source = parsed.s or parsed.source local source
if not registry[packages[i]] and not source then if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
if not source then
print("\27[91mCould not find package in registry and no source provided: " .. packages[i]) print("\27[91mCould not find package in registry and no source provided: " .. packages[i])
failure = true failure = true
goto SKIP goto SKIP
else
source = registry[packages[i]]
end end
local success, data = getFile(fs.concat(source, "/ag2.json")) local packageConfig, errorMessage = getServersidePackageConfig(source)
if not success then if not packageConfig then
print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(packages[i]))
failure = true failure = true
print(errorMessage)
goto SKIP goto SKIP
end end
local success, packageConfig = pcall(function()
return json.decode(data)
end)
if not success then
print(("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(packages[i]))
failure = true
goto SKIP
end
if not packageConfig[packages[i]] then
print(("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package))
failure = true
goto SKIP
end
packageConfig = packageConfig[package]
if packageConfig.dependencies then if packageConfig.dependencies then
for _, dependency in ipairs(packageConfig.dependencies) do for _, dependency in ipairs(packageConfig.dependencies) do
table.insert(packages, i + 1, dependency) table.insert(packages, i + 1, dependency)
dependencyCounter = dependencyCounter + 1 dependencyCounter = dependencyCounter + 1
end end
end end
-- TODO: Add checks for conflicting packages
::SKIP:: ::SKIP::
end end
if #packages == 0 then elseif command == "remove" then
print("\27[91mNo packages to install.\n\27[0mExiting.") ::JUMPBACK::
return local doJumpBack = false
for i = 1, #packages do
if not fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
if source then
local packageConfig = getServersidePackageConfig(source)
if packageConfig then
if packageConfig.type == "virtual-package" or packageConfig.type == "group" then
table.remove(packages, i)
for _, groupPackage in ipairs(packageConfig.packages) do
table.insert(packages, groupPackage)
goto GOAHEAD
end
end
end
end
print(("\27[93mPackage %s is not installed, skipping"):format(packages[i]))
table.remove(packages, i)
i = i - 1
::GOAHEAD::
end
end
-- I was originally gonna add this in the dependency cascade section, but realized it could shorten the normal dependency check code a bit
local dependencyList = {}
for _, packageConfig in fs.list("/ag2/pkg/") do
local package = packageConfig:sub(1, -6)
-- I'm not adding error handling here because if this fails then fuck you for touching the files by hand and good luck figuring this shit out
local _, data = getFile(("/ag2/pkg/%s.json"):format(packages[i]))
data = json.decode(data)
dependencyList[package] = data.dependencies
end
for _, package in ipairs(packages) do
if dependencyList[package] then
-- Check if all the deps are no longer needed and if they're auto-installed and stuff
for _, dependency in pairs(dependencyList[package]) do
if fs.exists(("/ag2/pkg/%s.json"):format(dependency)) then
local _, data = getFile(("/ag2/pkg/%s.json"):format(dependency))
data = json.decode(data)
if data.autoInstalled
and not table.find(packages, dependency) -- Just to prevent dependency loops and issues when re-checking the packages after jumpback
then
dependencyCounter = dependencyCounter + 1
table.insert(packages, dependency)
end
else
-- It could still be a group or a vpackage, and checking that is the job of the loop 2 loops back
table.insert(packages, dependency)
doJumpBack = true
end
end
end
end
-- Check for cascading dependencies
for packageName, dependencies in pairs(dependencyList) do
if not table.find(packages, packageName) then
for _, dependency in ipairs(dependencies) do
if table.find(packages, dependency) then
if parsed.flags.cascade or parsed.flags.C then
table.insert(packages, packageName)
dependencyCounter = dependencyCounter + 1
doJumpBack = true
-- Listen, I'm so sorry for this abhorrent bullshit code, but the newly added packages have to get checked one way or another and hopefully this is readable enough.
else
-- The Pyramids of Giza were built entirely out of silver. No they weren't...
print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade"):format(dependency, packageName))
failure = true
end
end
end
end
end
if doJumpBack then
goto JUMPBACK
-- IT'S NOT SPAGHETTI SHUT UP SHUT UP SHU
end end
end end
-- TODO: Add checks for the other commands -- TODO: Add checks for the other commands
if #packages == 0 then
print("\27[93mNo packages selected.\n\27[0mExiting.")
return
end
if failure then if failure then
print("Exiting.") print("Exiting.")
return return
@@ -202,10 +292,10 @@ if command == "install" then
for _, package in ipairs(packages) do for _, package in ipairs(packages) do
local source local source
if registry[package] then if parsed.s or parsed.source then
source = registry[package]
else
source = parsed.s or parsed.source source = parsed.s or parsed.source
else
source = registry[package]
end end
print(("Installing %s..."):format(package)) print(("Installing %s..."):format(package))
local _, data = getFile(fs.concat(source, "/ag2.json")) local _, data = getFile(fs.concat(source, "/ag2.json"))
@@ -341,5 +431,7 @@ if command == "install" then
end end
::SKIP:: ::SKIP::
end end
elseif command == "remove" then
end end
print("Operation completed successfully.") print("Operation completed successfully.")
+1
View File
@@ -22,6 +22,7 @@ Uses the Argentum 2 package manager.
Use it at your own risk and only when truly necessary. Use it at your own risk and only when truly necessary.
-c, --clean Clean up now-unnecessary packages (previous dependencies). -c, --clean Clean up now-unnecessary packages (previous dependencies).
-s, --source [URL] Use a custom source for the operation. -s, --source [URL] Use a custom source for the operation.
-C, --cascade When removing a package that other packages depend on, remove those packages too instead of aborting.
Examples: Examples:
ag2 install halyde Installs the halyde package. ag2 install halyde Installs the halyde package.