@@ -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