Skip to content

Commit d7570b7

Browse files
committed
feat(index): heal existing registry .xlings.json — org URL + artifact injection (#267-A #269)
Existing installs only run the text migration (seed is skipped when the file exists), so it now rewrites the org URL and injects the artifact declaration next to the canonical mcpplibs url. Idempotency gate: skip injection when the res base is already present (plain replace_all would re-inject every run). Covers both writer spacings (mcpp pretty / xlings compact); unrelated keys (subos, bindings) are preserved byte-for-byte.
1 parent f9c344e commit d7570b7

2 files changed

Lines changed: 85 additions & 4 deletions

File tree

‎src/fallback/config_migration.cppm‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
// mcpp.fallback.config_migration — legacy index name migration.
1+
// mcpp.fallback.config_migration — legacy index config migration.
22
//
3-
// Older mcpp sandboxes used "mcpp-index" as the default index name.
4-
// These helpers rename it to "mcpplibs" in config.toml and .xlings.json
5-
// so xlings config/list output matches mcpp's default namespace.
3+
// Older mcpp sandboxes used "mcpp-index" as the default index name and the
4+
// pre-migration mcpp-community org URL. These helpers rename the index to
5+
// "mcpplibs", rewrite the org URL (#267), and inject the xlings >= 0.4.68
6+
// per-repo artifact declaration (#269) into config.toml / .xlings.json.
7+
// Deliberately text-based: .xlings.json carries xlings-owned state (subos,
8+
// version bindings) that a regeneration would destroy. This is a LEAF
9+
// module (no mcpp.config import), so the URL literals are repeated here.
610

711
export module mcpp.fallback.config_migration;
812

@@ -83,6 +87,22 @@ bool migrate_xlings_json_index_names(const std::filesystem::path& path) {
8387
replace_all(updated, "https://github.com/mcpp-community/mcpp-index.git",
8488
"https://github.com/mcpplibs/mcpp-index.git");
8589

90+
// Artifact injection (#269, xlings >= 0.4.68 per-repo artifact source).
91+
// Existing installs never re-seed .xlings.json, so this migration is
92+
// their only channel to the artifact declaration; older xlings safely
93+
// ignores the key. Idempotency gate: the res base appearing anywhere
94+
// means a previous run (or a fresh seed) already declared it — a plain
95+
// replace_all would re-inject on every run. Both spacing variants
96+
// because the file has two writers (mcpp pretty / xlings compact).
97+
if (updated.find("xlings-res/mcpp-index") == std::string::npos) {
98+
constexpr std::string_view art =
99+
", \"artifact\": \"https://github.com/xlings-res/mcpp-index\"";
100+
for (std::string_view urlkv : {
101+
"\"url\": \"https://github.com/mcpplibs/mcpp-index.git\"",
102+
"\"url\":\"https://github.com/mcpplibs/mcpp-index.git\"" })
103+
replace_all(updated, urlkv, std::string(urlkv) + std::string(art));
104+
}
105+
86106
return write_text_if_changed(path, original, updated);
87107
}
88108

‎tests/unit/test_config.cpp‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,64 @@ TEST(ConfigIndexMigration, CanonicalizeRespectsExplicitGitSourceAndCustomArtifac
250250
EXPECT_EQ(cfg.indexRepos[1].artifact, "https://example.com/my-mirror");
251251
EXPECT_TRUE(cfg.indexRepos[2].artifact.empty()); // foreign repo untouched
252252
}
253+
254+
TEST(ConfigIndexMigration, XlingsJsonHealsPrettyLegacyEntry) {
255+
auto dir = make_tempdir("mcpp-migrate-xj");
256+
auto p = dir / ".xlings.json";
257+
{
258+
std::ofstream os(p);
259+
os << "{\n \"index_repos\": [\n"
260+
" { \"name\": \"mcpplibs\", \"url\": "
261+
"\"https://github.com/mcpp-community/mcpp-index.git\" }\n"
262+
" ],\n \"subos\": \"default\",\n \"mirror\": \"auto\"\n}\n";
263+
}
264+
EXPECT_TRUE(mcpp::fallback::migrate_xlings_json_index_names(p));
265+
auto text = read_all(p);
266+
EXPECT_NE(text.find(
267+
"\"url\": \"https://github.com/mcpplibs/mcpp-index.git\", "
268+
"\"artifact\": \"https://github.com/xlings-res/mcpp-index\""),
269+
std::string::npos) << text;
270+
EXPECT_NE(text.find("\"subos\": \"default\""), std::string::npos);
271+
EXPECT_EQ(text.find("mcpp-community/mcpp-index"), std::string::npos);
272+
EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p)); // idempotent
273+
EXPECT_EQ(read_all(p), text);
274+
std::filesystem::remove_all(dir);
275+
}
276+
277+
TEST(ConfigIndexMigration, XlingsJsonHealsCompactLegacyNameAndUrl) {
278+
auto dir = make_tempdir("mcpp-migrate-xjc");
279+
auto p = dir / ".xlings.json";
280+
{
281+
std::ofstream os(p);
282+
os << "{\"index_repos\":[{\"name\":\"mcpp-index\","
283+
"\"url\":\"https://github.com/mcpp-community/mcpp-index.git\"}],"
284+
"\"mirror\":\"auto\"}";
285+
}
286+
EXPECT_TRUE(mcpp::fallback::migrate_xlings_json_index_names(p));
287+
auto text = read_all(p);
288+
EXPECT_NE(text.find("\"name\":\"mcpplibs\""), std::string::npos);
289+
EXPECT_NE(text.find(
290+
"\"url\":\"https://github.com/mcpplibs/mcpp-index.git\", "
291+
"\"artifact\": \"https://github.com/xlings-res/mcpp-index\""),
292+
std::string::npos) << text;
293+
EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p));
294+
std::filesystem::remove_all(dir);
295+
}
296+
297+
TEST(ConfigIndexMigration, XlingsJsonSkipsInjectionWhenArtifactPresent) {
298+
auto dir = make_tempdir("mcpp-migrate-xja");
299+
auto p = dir / ".xlings.json";
300+
std::string body =
301+
"{\n \"index_repos\": [\n"
302+
" { \"name\": \"mcpplibs\", \"url\": "
303+
"\"https://github.com/mcpplibs/mcpp-index.git\", "
304+
"\"artifact\": \"https://github.com/xlings-res/mcpp-index\" }\n"
305+
" ]\n}\n";
306+
{
307+
std::ofstream os(p);
308+
os << body;
309+
}
310+
EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p));
311+
EXPECT_EQ(read_all(p), body);
312+
std::filesystem::remove_all(dir);
313+
}

0 commit comments

Comments
 (0)