Update paths object to return array of path objects #11
Triggered via pull request
February 21, 2024 21:54
jhiemstrawisc
opened
#11
Status
Success
Total duration
23s
Artifacts
–
Annotations
1 error and 8 warnings
Run linters
This action does not have permission to push to forks. You may want to run it only on `push` events.
|
Run linters:
src/lotman_internal.cpp#L567
[lint] reported by reviewdog 🐶
Raw Output:
src/lotman_internal.cpp:567:- json path_arr = json::array();
src/lotman_internal.cpp:568:- std::string path_query = "SELECT path, recursive FROM paths WHERE lot_name = ?;";
src/lotman_internal.cpp:569:- //std::string recursive_query = "SELECT recursive FROM paths WHERE path = ? AND lot_name = ?;";
src/lotman_internal.cpp:570:- std::map<std::string, std::vector<int>> path_query_str_map{{lot_name, {1}}};
src/lotman_internal.cpp:571:- auto rp = lotman::Checks::SQL_get_matches_multi_col(path_query, 2, path_query_str_map);
src/lotman_internal.cpp:572:- if (!rp.second.empty()) { // There was an error
src/lotman_internal.cpp:638:+ json path_arr = json::array();
src/lotman_internal.cpp:639:+ std::string path_query =
src/lotman_internal.cpp:640:+ "SELECT path, recursive FROM paths WHERE lot_name = ?;";
src/lotman_internal.cpp:641:+ // std::string recursive_query = "SELECT recursive FROM paths WHERE path = ?
src/lotman_internal.cpp:642:+ // AND lot_name = ?;";
src/lotman_internal.cpp:643:+ std::map<std::string, std::vector<int>> path_query_str_map{{lot_name, {1}}};
src/lotman_internal.cpp:644:+ auto rp = lotman::Checks::SQL_get_matches_multi_col(path_query, 2,
src/lotman_internal.cpp:645:+ path_query_str_map);
src/lotman_internal.cpp:646:+ if (!rp.second.empty()) { // There was an error
src/lotman_internal.cpp:647:+ std::string int_err = rp.second;
src/lotman_internal.cpp:648:+ std::string ext_err = "Failure on call to SQL_get_matches_multi_col: ";
src/lotman_internal.cpp:649:+ return std::make_pair(path_arr, ext_err + int_err);
src/lotman_internal.cpp:650:+ }
src/lotman_internal.cpp:651:+
src/lotman_internal.cpp:652:+ for (const auto &path_rec : rp.first) { // the path is at index 0, and the
src/lotman_internal.cpp:653:+ // recursion of the path is at index 1
src/lotman_internal.cpp:654:+ json path_obj_internal;
src/lotman_internal.cpp:655:+ path_obj_internal["lot_name"] = this->lot_name;
src/lotman_internal.cpp:656:+ path_obj_internal["recursive"] = static_cast<bool>(std::stoi(path_rec[1]));
src/lotman_internal.cpp:657:+ path_obj_internal["path"] = path_rec[0];
src/lotman_internal.cpp:658:+ path_arr.push_back(path_obj_internal);
src/lotman_internal.cpp:659:+ }
src/lotman_internal.cpp:660:+
src/lotman_internal.cpp:661:+ if (recursive) { // Not recursion of path, but recursion of dirs associated to
src/lotman_internal.cpp:662:+ // a lot, ie the directories associated with lot proper's
src/lotman_internal.cpp:663:+ // children
src/lotman_internal.cpp:664:+ auto rp_vec_str = this->get_children(true);
src/lotman_internal.cpp:665:+ if (!rp_vec_str.second.empty()) { // There was an error
src/lotman_internal.cpp:666:+ std::string int_err = rp_vec_str.second;
src/lotman_internal.cpp:667:+ std::string ext_err = "Failure to get children.";
src/lotman_internal.cpp:668:+ return std::make_pair(json::array(), ext_err + int_err);
src/lotman_internal.cpp:669:+ }
src/lotman_internal.cpp:670:+ for (const auto &child : recursive_children) {
src/lotman_internal.cpp:671:+ std::map<std::string, std::vector<int>> child_path_query_str_map{
src/lotman_internal.cpp:672:+ {child.lot_name, {1}}};
src/lotman_internal.cpp:673:+ rp = lotman::Checks::SQL_get_matches_multi_col(path_query, 2,
src/lotman_internal.cpp:674:+ child_path_query_str_map);
src/lotman_internal.cpp:675:+ if (!rp.second.empty()) { // There was an error
|
Run linters:
src/lotman_internal.cpp#L586
[lint] reported by reviewdog 🐶
Raw Output:
src/lotman_internal.cpp:586:- if (recursive) { // Not recursion of path, but recursion of dirs associated to a lot, ie the directories associated with lot proper's children
src/lotman_internal.cpp:587:- auto rp_vec_str = this->get_children(true);
src/lotman_internal.cpp:588:- if (!rp_vec_str.second.empty()) { // There was an error
src/lotman_internal.cpp:589:- std::string int_err = rp_vec_str.second;
src/lotman_internal.cpp:590:- std::string ext_err = "Failure to get children.";
src/lotman_internal.cpp:591:- return std::make_pair(json::array(), ext_err+int_err);
src/lotman_internal.cpp:592:- }
src/lotman_internal.cpp:593:- for (const auto &child : recursive_children) {
src/lotman_internal.cpp:594:- std::map<std::string, std::vector<int>> child_path_query_str_map{{child.lot_name, {1}}};
src/lotman_internal.cpp:595:- rp = lotman::Checks::SQL_get_matches_multi_col(path_query, 2, child_path_query_str_map);
src/lotman_internal.cpp:596:- if (!rp.second.empty()) { // There was an error
src/lotman_internal.cpp:597:- std::string int_err = rp.second;
src/lotman_internal.cpp:598:- std::string ext_err = "Failure on call to SQL_get_matches_multi_col: ";
src/lotman_internal.cpp:599:- return std::make_pair(path_arr, ext_err + int_err);
src/lotman_internal.cpp:600:- }
src/lotman_internal.cpp:601:-
src/lotman_internal.cpp:602:- for (const auto &path_rec : rp.first) {
src/lotman_internal.cpp:603:- json path_obj_internal;
src/lotman_internal.cpp:604:- path_obj_internal["lot_name"] = child.lot_name;
src/lotman_internal.cpp:605:- path_obj_internal["recursive"] = static_cast<bool>(std::stoi(path_rec[1]));
src/lotman_internal.cpp:606:- path_obj_internal["path"] = path_rec[0];
src/lotman_internal.cpp:607:- path_arr.push_back(path_obj_internal);
src/lotman_internal.cpp:608:- }
src/lotman_internal.cpp:609:- }
src/lotman_internal.cpp:610:- }
src/lotman_internal.cpp:611:-
src/lotman_internal.cpp:612:- return std::make_pair(path_arr, "");
src/lotman_internal.cpp:692:+ return std::make_pair(path_arr, "");
|
Run linters:
src/lotman_internal.cpp#L615
[lint] reported by reviewdog 🐶
Raw Output:
src/lotman_internal.cpp:615:-std::pair<std::string, std::string> lotman::Lot::get_lot_from_dir(const std::string dir_path) {
src/lotman_internal.cpp:616:- std::string lot_name_query = "SELECT lot_name FROM paths WHERE path = ?;";
src/lotman_internal.cpp:617:- std::map<std::string, std::vector<int>> query_map{{dir_path, {1}}};
src/lotman_internal.cpp:618:- auto rp = lotman::Checks::SQL_get_matches(lot_name_query, query_map);
src/lotman_internal.cpp:619:- if (!rp.second.empty()) { // There was an error
src/lotman_internal.cpp:620:- std::string int_err = rp.second;
src/lotman_internal.cpp:621:- std::string ext_err = "Failure on call to SQL_get_matches: ";
src/lotman_internal.cpp:622:- return std::make_pair("", ext_err + int_err);
src/lotman_internal.cpp:623:- }
src/lotman_internal.cpp:624:-
src/lotman_internal.cpp:625:- // rp.first[0] now contains the name of the lot, if one existed
src/lotman_internal.cpp:626:- if (rp.first.empty()) {
src/lotman_internal.cpp:627:- return std::make_pair("", ""); // Nothing existed, and no error. Return empty strings!
src/lotman_internal.cpp:628:- }
src/lotman_internal.cpp:629:- return std::make_pair(rp.first[0], "");
src/lotman_internal.cpp:695:+std::pair<std::string, std::string>
src/lotman_internal.cpp:696:+lotman::Lot::get_lot_from_dir(const std::string dir_path) {
src/lotman_internal.cpp:697:+ std::string lot_name_query = "SELECT lot_name FROM paths WHERE path = ?;";
src/lotman_internal.cpp:698:+ std::map<std::string, std::vector<int>> query_map{{dir_path, {1}}};
src/lotman_internal.cpp:699:+ auto rp = lotman::Checks::SQL_get_matches(lot_name_query, query_map);
src/lotman_internal.cpp:700:+ if (!rp.second.empty()) { // There was an error
src/lotman_internal.cpp:701:+ std::string int_err = rp.second;
src/lotman_internal.cpp:702:+ std::string ext_err = "Failure on call to SQL_get_matches: ";
src/lotman_internal.cpp:703:+ return std::make_pair("", ext_err + int_err);
src/lotman_internal.cpp:704:+ }
src/lotman_internal.cpp:705:+
src/lotman_internal.cpp:706:+ // rp.first[0] now contains the name of the lot, if one existed
src/lotman_internal.cpp:707:+ if (rp.first.empty()) {
src/lotman_internal.cpp:708:+ return std::make_pair(
src/lotman_internal.cpp:709:+ "", ""); // Nothing existed, and no error. Return empty strings!
src/lotman_internal.cpp:710:+ }
src/lotman_internal.cpp:711:+ return std::make_pair(rp.first[0], "");
|
Run linters:
test/main.cpp#L449
[lint] reported by reviewdog 🐶
Raw Output:
test/main.cpp:449:- TEST_F(LotManTest, GetLotDirs) {
test/main.cpp:450:- // Try to get a lot that doesn't exist
test/main.cpp:451:- char *err_msg;
test/main.cpp:452:- const char *non_existent_lot = "non_existent_lot";
test/main.cpp:453:- const bool recursive = true;
test/main.cpp:454:- char *output;
test/main.cpp:455:- int rv = lotman_get_lot_dirs(non_existent_lot, recursive, &output, &err_msg);
test/main.cpp:456:- ASSERT_FALSE(rv == 0);
test/main.cpp:457:- free(err_msg);
test/main.cpp:458:-
test/main.cpp:459:- char *err_msg2;
test/main.cpp:460:- const char *lot_name = "lot5";
test/main.cpp:461:- rv = lotman_get_lot_dirs(lot_name, recursive, &output, &err_msg2);
test/main.cpp:462:- ASSERT_TRUE(rv == 0);
test/main.cpp:463:-
test/main.cpp:464:- std::unique_ptr<char, decltype(&free)> output_ptr(output, &free); // Wrap output with a unique_ptr
test/main.cpp:465:- json json_out = json::parse(output);
test/main.cpp:466:- for (const auto &path_obj : json_out) {
test/main.cpp:467:- if (path_obj["path"] == "/1/2/3/4" && path_obj["recursive"] == true && path_obj["lot_name"] == "lot4") {
test/main.cpp:468:- continue;
test/main.cpp:469:- }
test/main.cpp:470:- else if (path_obj["path"] == "/345" && path_obj["recursive"] == true && path_obj["lot_name"] == "lot4") {
test/main.cpp:471:- continue;
test/main.cpp:472:- }
test/main.cpp:473:- else if (path_obj["path"] == "/456" && path_obj["recursive"] == false && path_obj["lot_name"] == "lot5") {
test/main.cpp:474:- continue;
test/main.cpp:475:- }
test/main.cpp:476:- else if (path_obj["path"] == "/567" && path_obj["recursive"] == true && path_obj["lot_name"] == "lot5") {
test/main.cpp:477:- continue;
test/main.cpp:478:- }
test/main.cpp:479:- else {
test/main.cpp:480:- ASSERT_TRUE(false) << "Unexpected path object: " << output;
test/main.cpp:481:- }
test/main.cpp:482:- }
test/main.cpp:612:+TEST_F(LotManTest, GetLotDirs) {
test/main.cpp:613:+ // Try to get a lot that doesn't exist
test/main.cpp:614:+ char *err_msg;
test/main.cpp:615:+ const char *non_existent_lot = "non_existent_lot";
test/main.cpp:616:+ const bool recursive = true;
test/main.cpp:617:+ char *output;
test/main.cpp:618:+ int rv = lotman_get_lot_dirs(non_existent_lot, recursive, &output, &err_msg);
test/main.cpp:619:+ ASSERT_FALSE(rv == 0);
test/main.cpp:620:+ free(err_msg);
test/main.cpp:621:+
test/main.cpp:622:+ char *err_msg2;
test/main.cpp:623:+ const char *lot_name = "lot5";
test/main.cpp:624:+ rv = lotman_get_lot_dirs(lot_name, recursive, &output, &err_msg2);
test/main.cpp:625:+ ASSERT_TRUE(rv == 0);
test/main.cpp:626:+
test/main.cpp:627:+ std::unique_ptr<char, decltype(&free)> output_ptr(
test/main.cpp:628:+ output, &free); // Wrap output with a unique_ptr
test/main.cpp:629:+ json json_out = json::parse(output);
test/main.cpp:630:+ for (const auto &path_obj : json_out) {
test/main.cpp:631:+ if (path_obj["path"] == "/1/2/3/4" && path_obj["recursive"] == true &&
test/main.cpp:632:+ path_obj["lot_name"] == "lot4") {
test/main.cpp:633:+ continue;
test/main.cpp:634:+ } else if (path_obj["path"] == "/345" && path_obj["recursive"] == true &&
test/main.cpp:635:+ path_obj["lot_name"] == "lot4") {
test/main.cpp:636:+ continue;
test/main.cpp:637:+ } else if (path_obj["path"] == "/456" && path_obj["recursive"] == false &&
test/main.cpp:638:+ path_obj["lot_name"] == "lot5") {
test/main.cpp:639:+ continue;
test/main.cpp:640:+ } else if (path_obj["path"] == "/567" && path_obj["recursive"] == true &&
test/main.cpp:641:+ path_obj["lot_name"] == "lot5") {
test/main.cpp:642:+ continue;
test/main.cpp:643:+ } else {
test/main.cpp:644:+ ASSERT_TRUE(false) << "Unexpected path object: " << output;
|
Run linters:
test/main.cpp#L485
[lint] reported by reviewdog 🐶
Raw Output:
test/main.cpp:485:- TEST_F(LotManTest, ContextTest) {
test/main.cpp:486:- // Any actions that modify the properties of a lot must have proper auth
test/main.cpp:487:- // These tests should all fail (context set to nonexistant owner)
test/main.cpp:488:-
test/main.cpp:489:- char *err_msg1;
test/main.cpp:490:- const char *lot6 = "{ \"lot_name\": \"lot6\", \"owner\": \"owner1\", \"parents\": [\"lot5\"], \"paths\": [], \"management_policy_attrs\": { \"dedicated_GB\":3,\"opportunistic_GB\":2.1,\"max_num_objects\":40,\"creation_time\":123,\"expiration_time\":231,\"deletion_time\":315}}";
test/main.cpp:491:-
test/main.cpp:492:- // Try to add a lot without correct context
test/main.cpp:493:- auto rv = lotman_set_context_str("caller", "notAnOwner", &err_msg1);
test/main.cpp:494:- rv = lotman_add_lot(lot6, &err_msg1);
test/main.cpp:495:- ASSERT_FALSE(rv == 0) << err_msg1;
test/main.cpp:496:- free(err_msg1);
test/main.cpp:497:-
test/main.cpp:498:- char *err_msg2;
test/main.cpp:499:- rv = lotman_lot_exists("lot6", &err_msg2);
test/main.cpp:500:- ASSERT_FALSE(rv == 1);
test/main.cpp:501:-
test/main.cpp:502:- // Try to remove a lot without correct context
test/main.cpp:503:- rv = lotman_remove_lots_recursive("lot1", &err_msg2);
test/main.cpp:504:- ASSERT_FALSE(rv == 0);
test/main.cpp:505:- free(err_msg2);
test/main.cpp:506:-
test/main.cpp:507:- char *err_msg3;
test/main.cpp:508:- rv = lotman_lot_exists("lot1", &err_msg3);
test/main.cpp:509:- ASSERT_TRUE(rv == 1);
test/main.cpp:510:-
test/main.cpp:511:- // Try to update a lot without correct context
test/main.cpp:512:- const char *modified_lot = "{ \"lot_name\": \"lot3\", \"owner\": \"Bad Update\"}";
test/main.cpp:513:- rv = lotman_update_lot(modified_lot, &err_msg3);
test/main.cpp:514:- ASSERT_FALSE(rv == 0);
test/main.cpp:515:- free(err_msg3);
test/main.cpp:516:-
test/main.cpp:517:- // Try to update lot usage without correct context
test/main.cpp:518:- char *err_msg4;
test/main.cpp:519:- const char *usage_update_JSON = "{\"lot_name\":\"lot5\",\"self_GB\":99}";
test/main.cpp:520:- rv = lotman_update_lot_usage(usage_update_JSON, false, &err_msg4);
test/main.cpp:521:- ASSERT_FALSE(rv == 0);
test/main.cpp:522:- free(err_msg4);
test/main.cpp:523:- }
test/main.cpp:649:+TEST_F(LotManTest, ContextTest) {
test/main.cpp:650:+ // Any actions that modify the properties of a lot must have proper auth
test/main.cpp:651:+ // These tests should all fail (context set to nonexistant owner)
test/main.cpp:652:+
test/main.cpp:653:+ char *err_msg1;
test/main.cpp:654:+ const char *lot6 =
test/main.cpp:655:+ "{ \"lot_name\": \"lot6\", \"owner\": \"owner1\", \"parents\": "
test/main.cpp:656:+ "[\"lot5\"], \"paths\": [], \"management_policy_attrs\": { "
test/main.cpp:657:+ "\"dedicated_GB\":3,\"opportunistic_GB\":2.1,\"max_num_objects\":40,"
test/main.cpp:658:+ "\"creation_time\":123,\"expiration_time\":231,\"deletion_time\":315}}";
test/main.cpp:659:+
test/main.cpp:660:+ // Try to add a lot without correct context
test/main.cpp:661:+ auto rv = lotman_set_context_str("caller", "notAnOwner", &err_msg1);
test/main.cpp:662:+ rv = lotman_add_lot(lot6, &err_msg1);
test/main.cpp:663:+ ASSERT_FALSE(rv == 0) << err_msg1;
test/main.cpp:664:+ free(err_msg1);
test/main.cpp:665:+
test/main.cpp:666:+ char *err_msg2;
test/main.cpp:667:+ rv = lotman_lot_exists("lot6", &err_msg2);
test/main.cpp:668:+ ASSERT_FALSE(rv == 1);
test/main.cpp:669:+
test/main.cpp:670:+ // Try to remove a lot without correct context
test/main.cpp:671:+ rv = lotman_remove_lots_recursive("lot1", &err_msg2);
test/main.cpp:672:+ ASSERT_FALSE(rv == 0);
test/main.cpp:673:+ free(err_msg2);
test/main.cpp:674:+
test/main.cpp:675:+ char *err_msg3;
test/main.cpp:676:+ rv = lotman_lot_exists("lot1", &err_msg3);
test/main.cpp:677:+ ASSERT_TRUE(rv == 1);
test/m
|
Run linters:
test/main.cpp#L607
[lint] reported by reviewdog 🐶
Raw Output:
test/main.cpp:607:-
test/main.cpp:608:- TEST_F(LotManTest, GetLotJSONTest) {
test/main.cpp:609:- // Try to get a lot that doesn't exist
test/main.cpp:610:- char *err_msg;
test/main.cpp:611:- char *output;
test/main.cpp:612:- auto rv = lotman_get_lot_as_json("non_existent_lot", true, &output, &err_msg);
test/main.cpp:613:- ASSERT_FALSE(rv == 0);
test/main.cpp:614:- free(err_msg);
test/main.cpp:615:-
test/main.cpp:616:- char *err_msg2;
test/main.cpp:617:- rv = lotman_get_lot_as_json("lot3", true, &output, &err_msg);
test/main.cpp:618:- ASSERT_TRUE(rv == 0);
test/main.cpp:619:-
test/main.cpp:620:- json output_JSON = json::parse(output);
test/main.cpp:621:- free(output);
test/main.cpp:622:- json expected_output = R"({"children":["lot4","lot5"],"lot_name":"lot3","management_policy_attrs":{"creation_time":{"lot_name":"lot3","value":123.0},"dedicated_GB":{"lot_name":"sep_node","value":3.0},"deletion_time":{"lot_name":"lot3","value":333.0},"expiration_time":{"lot_name":"lot3","value":222.0},"max_num_objects":{"lot_name":"sep_node","value":10.0},"opportunistic_GB":{"lot_name":"lot2","value":1.5}},"owners":["not owner1","owner1"],"parents":["lot1","lot2","sep_node"],"paths":[{"lot_name":"lot3","path":"/another/path","recursive":true},{"lot_name":"lot3","path":"/updated/path","recursive":false},{"lot_name":"lot3","path":"/foo/barr","recursive":true},{"lot_name":"lot4","path":"/1/2/3/4","recursive":true},{"lot_name":"lot4","path":"/345","recursive":true},{"lot_name":"lot5","path":"/456","recursive":false},{"lot_name":"lot5","path":"/567","recursive":true}],"usage":{"GB_being_written":{"children_contrib":3.4,"self_contrib":0.0},"dedicated_GB":{"children_contrib":8.64,"self_contrib":0.0,"total":8.64},"num_objects":{"children_contrib":10.0,"self_contrib":0.0},"objects_being_written":{"children_contrib":7.0,"self_contrib":0.0},"opportunistic_GB":{"children_contrib":0.0,"self_contrib":0.0,"total":0.0},"total_GB":{"children_contrib":8.64,"self_contrib":0.0}}})"_json;
test/main.cpp:623:- ASSERT_TRUE(output_JSON == expected_output) << output_JSON;
test/main.cpp:720:+ }
test/main.cpp:721:+ ASSERT_FALSE(check);
test/main.cpp:722:+ lotman_free_string_list(output2);
test/main.cpp:723:+
test/main.cpp:724:+ // Check for lots past opportunistic storage limit
test/main.cpp:725:+ char **output3;
test/main.cpp:726:+ rv = lotman_get_lots_past_opp(true, true, &output3, &err_msg);
test/main.cpp:727:+ ASSERT_TRUE(rv == 0) << err_msg;
test/main.cpp:728:+
test/main.cpp:729:+ check = false;
test/main.cpp:730:+ for (int iter = 0; output3[iter]; iter++) {
test/main.cpp:731:+ if (strcmp(output3[iter], "default") == 0) {
test/main.cpp:732:+ check = true;
|
Run linters:
test/main.cpp#L626
[lint] reported by reviewdog 🐶
Raw Output:
test/main.cpp:626:- TEST_F(LotManTest, LotsFromDirTest) {
test/main.cpp:627:- char *err_msg;
test/main.cpp:628:- char **output;
test/main.cpp:629:- const char *dir = "/1/2/3/4"; // Get a path
test/main.cpp:630:- auto rv = lotman_get_lots_from_dir(dir, true, &output, &err_msg);
test/main.cpp:631:- ASSERT_TRUE(rv == 0) << err_msg;
test/main.cpp:632:-
test/main.cpp:633:- for (int iter = 0; output[iter]; iter++) {
test/main.cpp:634:- ASSERT_TRUE(static_cast<std::string>(output[iter]) == "lot4" || static_cast<std::string>(output[iter]) == "lot1" ||
test/main.cpp:635:- static_cast<std::string>(output[iter]) == "lot2" || static_cast<std::string>(output[iter]) == "lot3" ||
test/main.cpp:636:- static_cast<std::string>(output[iter]) == "lot5" || static_cast<std::string>(output[iter]) == "sep_node");
test/main.cpp:637:- }
test/main.cpp:638:-
test/main.cpp:639:- lotman_free_string_list(output);
test/main.cpp:767:+TEST_F(LotManTest, GetAllLotsTest) {
test/main.cpp:768:+ char *err_msg;
test/main.cpp:769:+ char **output;
test/main.cpp:770:+ auto rv = lotman_list_all_lots(&output, &err_msg);
test/main.cpp:771:+ int size = 0;
test/main.cpp:772:+ for (size; output[size]; ++size) {
test/main.cpp:773:+ }
test/main.cpp:774:+ ASSERT_TRUE(size == 7);
test/main.cpp:775:+ lotman_free_string_list(output);
test/main.cpp:776:+}
|
Run linters
Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: actions/checkout@v3, wearerequired/lint-action@v2. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.
|