3030#include < string>
3131#include < vector>
3232#include < cstdio>
33+ #include < chrono>
34+ #include < iomanip>
3335
3436#if defined(_WIN32)
3537#define vix_popen _popen
@@ -95,14 +97,34 @@ namespace vix::commands
9597
9698 static fs::path registry_repo_dir ()
9799 {
100+ // vix registry sync clones into ~/.vix/registry/index
98101 return vix_root () / " registry" / " index" ;
99102 }
100103
101104 static fs::path registry_index_dir ()
102105 {
106+ // entries folder inside the registry repo: ~/.vix/registry/index/index
103107 return registry_repo_dir () / " index" ;
104108 }
105109
110+ static std::string iso_utc_now ()
111+ {
112+ using namespace std ::chrono;
113+ const auto now = system_clock::now ();
114+ const std::time_t t = system_clock::to_time_t (now);
115+
116+ std::tm tm{};
117+ #if defined(_WIN32)
118+ gmtime_s (&tm, &t);
119+ #else
120+ gmtime_r (&t, &tm);
121+ #endif
122+
123+ std::ostringstream oss;
124+ oss << std::put_time (&tm, " %Y-%m-%dT%H:%M:%SZ" );
125+ return oss.str ();
126+ }
127+
106128 static bool file_exists_nonempty (const fs::path &p)
107129 {
108130 std::error_code ec;
@@ -281,6 +303,21 @@ namespace vix::commands
281303 return std::make_pair (lower_copy (ns), lower_copy (name));
282304 }
283305
306+ static void mark_version_published_or_throw (
307+ const fs::path &entryPath,
308+ json &entry,
309+ const std::string &version)
310+ {
311+ if (!entry.contains (" versions" ) || !entry[" versions" ].is_object ())
312+ entry[" versions" ] = json::object ();
313+
314+ if (!entry[" versions" ].contains (version) || !entry[" versions" ][version].is_object ())
315+ entry[" versions" ][version] = json::object ();
316+
317+ entry[" versions" ][version][" status" ] = " published" ;
318+ write_json_or_throw (entryPath, entry);
319+ }
320+
284321 static PublishOptions parse_args_or_throw (const std::vector<std::string> &args)
285322 {
286323 PublishOptions opt;
@@ -364,62 +401,6 @@ namespace vix::commands
364401 return b;
365402 }
366403
367- static std::vector<int > parse_version_nums (const std::string &s)
368- {
369- std::vector<int > out;
370- std::string cur;
371- for (char ch : s)
372- {
373- if (std::isdigit (static_cast <unsigned char >(ch)))
374- {
375- cur.push_back (ch);
376- continue ;
377- }
378- if (ch == ' .' )
379- {
380- if (!cur.empty ())
381- {
382- out.push_back (std::stoi (cur));
383- cur.clear ();
384- }
385- continue ;
386- }
387- break ;
388- }
389- if (!cur.empty ())
390- out.push_back (std::stoi (cur));
391- while (out.size () < 3 )
392- out.push_back (0 );
393- return out;
394- }
395-
396- static int compare_versions (const std::string &a, const std::string &b)
397- {
398- const auto va = parse_version_nums (a);
399- const auto vb = parse_version_nums (b);
400- const size_t n = std::max (va.size (), vb.size ());
401- for (size_t i = 0 ; i < n; ++i)
402- {
403- const int ia = (i < va.size () ? va[i] : 0 );
404- const int ib = (i < vb.size () ? vb[i] : 0 );
405- if (ia < ib)
406- return -1 ;
407- if (ia > ib)
408- return 1 ;
409- }
410- return 0 ;
411- }
412-
413- static void update_latest_if_newer (json &entry, const std::string &newVersion)
414- {
415- std::string cur;
416- if (entry.contains (" latest" ) && entry[" latest" ].is_string ())
417- cur = entry[" latest" ].get <std::string>();
418-
419- if (cur.empty () || compare_versions (newVersion, cur) > 0 )
420- entry[" latest" ] = newVersion;
421- }
422-
423404 static int publish_impl (const PublishOptions &opt)
424405 {
425406 vix::cli::util::section (std::cout, " Publish" );
@@ -552,7 +533,6 @@ namespace vix::commands
552533 entry[" type" ] = " header-only" ;
553534 entry[" manifestPath" ] = " vix.json" ;
554535 entry[" homepage" ] = httpsUrl;
555- entry[" maintainers" ] = json::array ({json::object ({{" name" , " " }, {" github" , " " }})});
556536 entry[" versions" ] = json::object ();
557537 }
558538
@@ -567,24 +547,30 @@ namespace vix::commands
567547
568548 if (entry[" versions" ].contains (opt.version ))
569549 {
570- std::string latest;
571- if (entry.contains (" latest" ) && entry[" latest" ].is_string ())
572- latest = entry[" latest" ].get <std::string>();
573- vix::cli::util::err_line (std::cerr, " version already exists in registry entry: " + opt.version );
574- if (!latest.empty ())
575- vix::cli::util::warn_line (std::cerr, " current latest: " + latest);
576- return 1 ;
550+ const json &existing = entry[" versions" ][opt.version ];
551+ const std::string status = (existing.contains (" status" ) && existing[" status" ].is_string ())
552+ ? existing[" status" ].get <std::string>()
553+ : std::string ();
554+
555+ if (status == " published" )
556+ {
557+ vix::cli::util::err_line (std::cerr, " version already published in registry: " + opt.version );
558+ return 1 ;
559+ }
560+
561+ // pending or unknown -> resume
562+ vix::cli::util::warn_line (std::cout, " found existing pending entry, resuming publish for: " + opt.version );
577563 }
578564
579565 json v = json::object ();
580566 v[" tag" ] = tag;
581567 v[" commit" ] = commit;
568+ v[" publishedAt" ] = iso_utc_now ();
569+ v[" status" ] = " pending" ;
582570 if (!opt.notes .empty ())
583571 v[" notes" ] = opt.notes ;
584572
585573 entry[" versions" ][opt.version ] = v;
586- update_latest_if_newer (entry, opt.version );
587-
588574 if (opt.dryRun )
589575 {
590576 vix::cli::util::ok_line (std::cout, " dry-run: would update: " + entryPath.string ());
@@ -632,8 +618,9 @@ namespace vix::commands
632618 }
633619
634620 {
635- const std::string cmd =
636- " git -C " + regRepo.string () + " add " + entryPath.string ();
621+ const std::string relEntry = (fs::path (" index" ) / registry_file_name (*ns, *name)).generic_string ();
622+ const std::string cmd = " git -C " + regRepo.string () + " add " + relEntry;
623+
637624 const int rc = vix::cli::util::run_cmd_retry_debug (cmd);
638625 if (rc != 0 )
639626 return rc;
@@ -678,13 +665,37 @@ namespace vix::commands
678665 }
679666 }
680667
668+ // Mark as published locally after successful push.
669+ try
670+ {
671+ entry = read_json_or_throw (entryPath);
672+ mark_version_published_or_throw (entryPath, entry, opt.version );
673+
674+ const std::string relEntry =
675+ (fs::path (" index" ) / registry_file_name (*ns, *name)).generic_string ();
676+
677+ (void )vix::cli::util::run_cmd_retry_debug (" git -C " + regRepo.string () + " add " + relEntry);
678+ (void )vix::cli::util::run_cmd_retry_debug (
679+ " git -C " + regRepo.string () +
680+ " commit -q -m \" registry: mark published " + pkgId + " v" + opt.version + " \" " );
681+ (void )vix::cli::util::run_cmd_retry_debug (" git -C " + regRepo.string () + " push" );
682+ }
683+ catch (...)
684+ {
685+ // Not fatal: publish already pushed, only status update failed.
686+ vix::cli::util::warn_line (std::cout, " warning: could not mark version as published locally" );
687+ }
688+
681689 bool prCreated = false ;
682690
683691 if (command_exists (" gh" ))
684692 {
685- std::string out;
686- const int authRc = run_cmd_capture (" gh auth status -h github.com >/dev/null 2>&1; echo $?" , out);
687- const bool ghAuthed = (authRc == 0 && out == " 0" );
693+ bool ghAuthed = false ;
694+ {
695+ std::string out;
696+ const int rc = run_cmd_capture (" gh auth status -h github.com 2>/dev/null" , out);
697+ ghAuthed = (rc == 0 );
698+ }
688699
689700 if (ghAuthed)
690701 {
@@ -753,7 +764,7 @@ namespace vix::commands
753764 << " - Registry must be synced: vix registry sync\n\n "
754765 << " Examples:\n "
755766 << " vix publish 0.2.0\n "
756- << " vix publish 0.2.0 --notes \" Add count_leaves helper \"\n "
767+ << " vix publish 0.2.0 --notes \" Add count_leaves helpers \"\n "
757768 << " vix publish 0.2.0 --dry-run\n " ;
758769 return 0 ;
759770 }
0 commit comments