Skip to content

Commit 1818656

Browse files
committed
fix(install): resolve transitive dependencies for global packages
2 parents 8e9dcf1 + 056f7a1 commit 1818656

1 file changed

Lines changed: 139 additions & 50 deletions

File tree

src/commands/InstallCommand.cpp

Lines changed: 139 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -613,11 +613,19 @@ namespace vix::commands
613613

614614
const json versions = entry.at("versions");
615615
if (!versions.contains(spec.resolvedVersion))
616+
{
616617
throw std::runtime_error(
617618
"version not found: " + spec.id() + "@" + spec.resolvedVersion);
619+
}
618620

619621
const json v = versions.at(spec.resolvedVersion);
620622

623+
if (!v.contains("tag") || !v["tag"].is_string())
624+
throw std::runtime_error("invalid registry entry: missing version tag for " + spec.id());
625+
626+
if (!v.contains("commit") || !v["commit"].is_string())
627+
throw std::runtime_error("invalid registry entry: missing version commit for " + spec.id());
628+
621629
DepResolved dep;
622630
dep.id = spec.id();
623631
dep.version = spec.resolvedVersion;
@@ -638,12 +646,18 @@ namespace vix::commands
638646
}
639647

640648
json root = read_json_or_throw(global_manifest_path());
649+
650+
if (!root.is_object())
651+
{
652+
return json{
653+
{"packages", json::array()}};
654+
}
655+
641656
if (!root.contains("packages") || !root["packages"].is_array())
642657
root["packages"] = json::array();
643658

644659
return root;
645660
}
646-
647661
static void save_global_manifest(const json &root)
648662
{
649663
fs::create_directories(global_root_dir());
@@ -983,98 +997,173 @@ namespace vix::commands
983997
if (ensure_registry_present() != 0)
984998
return 1;
985999

986-
std::optional<DepResolved> resolvedOpt;
1000+
std::unordered_map<std::string, DepResolved> resolvedById;
1001+
std::queue<std::string> pendingSpecs;
1002+
pendingSpecs.push(specRaw);
1003+
9871004
try
9881005
{
989-
resolvedOpt = resolve_package_from_registry(specRaw);
1006+
while (!pendingSpecs.empty())
1007+
{
1008+
const std::string currentSpec = pendingSpecs.front();
1009+
pendingSpecs.pop();
1010+
1011+
std::optional<DepResolved> resolvedOpt = resolve_package_from_registry(currentSpec);
1012+
if (!resolvedOpt)
1013+
{
1014+
vix::cli::util::err_line(std::cerr, "invalid package spec or package not found: " + currentSpec);
1015+
vix::cli::util::warn_line(std::cerr, "Expected: @namespace/name[@version]");
1016+
vix::cli::util::warn_line(std::cerr, "Example: vix install -g @gk/jwt@1.0.0");
1017+
return 1;
1018+
}
1019+
1020+
DepResolved dep = *resolvedOpt;
1021+
1022+
if (resolvedById.find(dep.id) != resolvedById.end())
1023+
continue;
1024+
1025+
std::string outDir;
1026+
const int rc = clone_checkout(dep.repo, sanitize_id_dot(dep.id), dep.commit, outDir);
1027+
if (rc != 0)
1028+
{
1029+
vix::cli::util::err_line(std::cerr, "fetch failed: " + dep.id);
1030+
vix::cli::util::warn_line(std::cerr, "Check git access, network, or registry metadata.");
1031+
return rc;
1032+
}
1033+
1034+
dep.checkout = fs::path(outDir);
1035+
dep.hash = vix::cli::util::sha256_directory(dep.checkout).value_or("");
1036+
1037+
if (!verify_dependency_hash(dep))
1038+
{
1039+
vix::cli::util::warn_line(std::cerr, "The cached checkout appears modified or corrupt.");
1040+
vix::cli::util::warn_line(std::cerr, "Try: vix store gc");
1041+
return 1;
1042+
}
1043+
1044+
load_dep_manifest(dep);
1045+
1046+
resolvedById[dep.id] = dep;
1047+
1048+
for (const auto &childId : dep.dependencies)
1049+
{
1050+
if (resolvedById.find(childId) == resolvedById.end())
1051+
pendingSpecs.push(childId);
1052+
}
1053+
}
9901054
}
9911055
catch (const std::exception &ex)
9921056
{
9931057
vix::cli::util::err_line(std::cerr, std::string("install failed: ") + ex.what());
9941058
return 1;
9951059
}
9961060

997-
if (!resolvedOpt)
1061+
std::vector<DepResolved> resolved;
1062+
resolved.reserve(resolvedById.size());
1063+
1064+
for (auto &[_, dep] : resolvedById)
1065+
resolved.push_back(dep);
1066+
1067+
std::vector<DepResolved> ordered;
1068+
try
9981069
{
999-
vix::cli::util::err_line(std::cerr, "invalid package spec or package not found");
1000-
vix::cli::util::warn_line(std::cerr, "Expected: @namespace/name[@version]");
1001-
vix::cli::util::warn_line(std::cerr, "Example: vix install -g @gk/jwt@1.0.0");
1070+
ordered = sort_deps_topologically(resolved);
1071+
}
1072+
catch (const std::exception &ex)
1073+
{
1074+
vix::cli::util::err_line(std::cerr, std::string("install failed: ") + ex.what());
10021075
return 1;
10031076
}
10041077

1005-
DepResolved dep = *resolvedOpt;
1006-
10071078
fs::create_directories(global_pkgs_dir());
10081079

1009-
const bool checkoutExistedBefore = fs::exists(dep.checkout);
1010-
if (!checkoutExistedBefore)
1080+
bool printedHeader = false;
1081+
bool didWork = false;
1082+
fs::path rootInstalledPath;
1083+
1084+
auto print_header_once = [&]()
10111085
{
1012-
vix::cli::util::section(std::cout, "Installing global package");
1013-
vix::cli::util::one_line_spacer(std::cout);
1086+
if (!printedHeader)
1087+
{
1088+
vix::cli::util::section(std::cout, "Installing global package");
1089+
vix::cli::util::one_line_spacer(std::cout);
1090+
printedHeader = true;
1091+
}
1092+
};
10141093

1015-
std::string outDir;
1016-
const int rc = clone_checkout(dep.repo, sanitize_id_dot(dep.id), dep.commit, outDir);
1017-
if (rc != 0)
1094+
for (auto &dep : ordered)
1095+
{
1096+
const fs::path dst = global_pkg_dir(dep.id, dep.commit);
1097+
1098+
const bool checkoutExistedBefore = fs::exists(dep.checkout);
1099+
const bool linkExistedBefore = fs::exists(dst);
1100+
1101+
try
10181102
{
1019-
vix::cli::util::err_line(std::cerr, "fetch failed: " + dep.id);
1020-
vix::cli::util::warn_line(std::cerr, "Check git access, network, or registry metadata.");
1021-
return rc;
1103+
ensure_symlink_or_copy_dir(dep.checkout, dst);
1104+
}
1105+
catch (const std::exception &ex)
1106+
{
1107+
vix::cli::util::err_line(std::cerr, std::string("install failed: ") + ex.what());
1108+
return 1;
10221109
}
10231110

1024-
dep.checkout = fs::path(outDir);
1025-
}
1111+
dep.linkDir = dst;
1112+
save_global_install(dep, dst);
10261113

1027-
dep.hash = vix::cli::util::sha256_directory(dep.checkout).value_or("");
1028-
if (!verify_dependency_hash(dep))
1029-
{
1030-
vix::cli::util::warn_line(std::cerr, "The cached checkout appears modified or corrupt.");
1031-
vix::cli::util::warn_line(std::cerr, "Try: vix store gc");
1032-
return 1;
1033-
}
1114+
if (!checkoutExistedBefore || !linkExistedBefore)
1115+
{
1116+
print_header_once();
1117+
didWork = true;
10341118

1035-
load_dep_manifest(dep);
1119+
std::cout << " " << CYAN << "" << RESET << " "
1120+
<< CYAN << BOLD << dep.id << RESET
1121+
<< GRAY << "@" << RESET
1122+
<< YELLOW << BOLD << dep.version << RESET
1123+
<< " "
1124+
<< GRAY << "installed globally" << RESET
1125+
<< "\n";
1126+
}
10361127

1037-
const fs::path dst = global_pkg_dir(dep.id, dep.commit);
1038-
const bool linkExistedBefore = fs::exists(dst);
1128+
if (dep.id == ordered.back().id)
1129+
{
1130+
// rien
1131+
}
1132+
}
10391133

1134+
std::optional<DepResolved> rootOpt;
10401135
try
10411136
{
1042-
ensure_symlink_or_copy_dir(dep.checkout, dst);
1137+
rootOpt = resolve_package_from_registry(specRaw);
10431138
}
10441139
catch (const std::exception &ex)
10451140
{
10461141
vix::cli::util::err_line(std::cerr, std::string("install failed: ") + ex.what());
10471142
return 1;
10481143
}
10491144

1050-
dep.linkDir = dst;
1051-
save_global_install(dep, dst);
1052-
1053-
if (!checkoutExistedBefore || !linkExistedBefore)
1145+
if (!rootOpt)
10541146
{
1055-
if (checkoutExistedBefore)
1056-
{
1057-
vix::cli::util::section(std::cout, "Installing global package");
1058-
vix::cli::util::one_line_spacer(std::cout);
1059-
}
1060-
1061-
std::cout << " " << CYAN << "" << RESET << " "
1062-
<< CYAN << BOLD << dep.id << RESET
1063-
<< GRAY << "@" << RESET
1064-
<< YELLOW << BOLD << dep.version << RESET
1065-
<< " "
1066-
<< GRAY << "installed globally" << RESET
1067-
<< "\n";
1147+
vix::cli::util::err_line(std::cerr, "invalid package spec or package not found");
1148+
return 1;
10681149
}
1150+
1151+
const auto rootIt = resolvedById.find(rootOpt->id);
1152+
if (rootIt != resolvedById.end())
1153+
rootInstalledPath = global_pkg_dir(rootIt->second.id, rootIt->second.commit);
10691154
else
1155+
rootInstalledPath = global_pkg_dir(rootOpt->id, rootOpt->commit);
1156+
1157+
if (!didWork)
10701158
{
10711159
vix::cli::util::ok_line(std::cout, "Global package already up to date");
10721160
}
10731161

10741162
vix::cli::util::one_line_spacer(std::cout);
10751163
vix::cli::util::ok_line(std::cout, "Global package ready");
1076-
vix::cli::util::info(std::cout, "Installed into: " + dst.string());
1164+
vix::cli::util::info(std::cout, "Installed into: " + rootInstalledPath.string());
10771165
vix::cli::util::info(std::cout, "Manifest updated: " + global_manifest_path().string());
1166+
vix::cli::util::info(std::cout, std::to_string(ordered.size()) + " package(s) available globally");
10781167

10791168
return 0;
10801169
}

0 commit comments

Comments
 (0)