Skip to content

Commit 1b56e38

Browse files
committed
fix(config): escape project index json
1 parent 350089e commit 1b56e38

5 files changed

Lines changed: 71 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.32] — 2026-05-30
7+
8+
### 修复
9+
10+
- 修复 project-local `.xlings.json` 生成时未转义 JSON 字符串的问题,
11+
避免 Windows 本地 index 路径中的反斜杠导致 xlings 跳过项目索引。
12+
613
## [0.0.31] — 2026-05-30
714

815
### 修复

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.31"
3+
version = "0.0.32"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/config.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ bool ensure_project_index_dir(
666666
if (spec.is_builtin()) continue;
667667
if (spec.is_local()) {
668668
auto source = resolve_project_index_path(projectDir, spec);
669-
customRepos.emplace_back(name, source.string());
669+
customRepos.emplace_back(name, source.generic_string());
670670
continue;
671671
}
672672
customRepos.emplace_back(name, spec.url);

src/xlings.cppm

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,29 @@ void write_file(const std::filesystem::path& p, std::string_view content) {
276276
os << content;
277277
}
278278

279+
std::string json_escape(std::string_view value) {
280+
std::string out;
281+
out.reserve(value.size());
282+
for (unsigned char ch : value) {
283+
switch (ch) {
284+
case '"': out += "\\\""; break;
285+
case '\\': out += "\\\\"; break;
286+
case '\b': out += "\\b"; break;
287+
case '\f': out += "\\f"; break;
288+
case '\n': out += "\\n"; break;
289+
case '\r': out += "\\r"; break;
290+
case '\t': out += "\\t"; break;
291+
default:
292+
if (ch < 0x20) {
293+
out += std::format("\\u{:04x}", static_cast<unsigned>(ch));
294+
} else {
295+
out.push_back(static_cast<char>(ch));
296+
}
297+
}
298+
}
299+
return out;
300+
}
301+
279302
// LineScan: cheap field extraction for bootstrap install progress lines.
280303
// Handles flat JSON; no nested array/object — the keys we extract are
281304
// all leaves.
@@ -794,12 +817,13 @@ void seed_xlings_json(const Env& env,
794817
json += " \"index_repos\": [\n";
795818
for (std::size_t i = 0; i < repos.size(); ++i) {
796819
json += std::format(" {{ \"name\": \"{}\", \"url\": \"{}\" }}{}\n",
797-
repos[i].first, repos[i].second,
820+
json_escape(repos[i].first),
821+
json_escape(repos[i].second),
798822
i + 1 == repos.size() ? "" : ",");
799823
}
800824
json += " ],\n";
801825
json += " \"lang\": \"en\",\n";
802-
json += std::format(" \"mirror\": \"{}\"\n", mirror);
826+
json += std::format(" \"mirror\": \"{}\"\n", json_escape(mirror));
803827
json += "}\n";
804828
write_file(path, json);
805829
}

tests/unit/test_config.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,39 @@ TEST(Config, ResolveProjectIndexPathUsesProjectRootForRelativeLocalIndex) {
4747
EXPECT_EQ(mcpp::config::resolve_project_index_path(project, spec),
4848
(project / "mcpp").lexically_normal());
4949
}
50+
51+
TEST(Config, ProjectIndexJsonEscapesLocalIndexPath) {
52+
auto project = make_tempdir("mcpp-config-json-escape");
53+
auto index = project / "local" / "index";
54+
std::filesystem::create_directories(index / "pkgs");
55+
56+
mcpp::pm::IndexSpec local;
57+
local.name = "local\"dev";
58+
local.path = index;
59+
60+
mcpp::pm::IndexSpec remote;
61+
remote.name = "remote";
62+
remote.url = R"(https://example.com/a\b"c)";
63+
64+
std::map<std::string, mcpp::pm::IndexSpec> indices;
65+
indices.emplace(local.name, local);
66+
indices.emplace(remote.name, remote);
67+
68+
mcpp::config::GlobalConfig cfg;
69+
ASSERT_TRUE(mcpp::config::ensure_project_index_dir(cfg, project, indices));
70+
71+
std::ifstream is(project / ".mcpp" / ".xlings.json");
72+
ASSERT_TRUE(is);
73+
std::stringstream ss;
74+
ss << is.rdbuf();
75+
auto content = ss.str();
76+
77+
EXPECT_NE(content.find(R"("name": "local\"dev")"), std::string::npos)
78+
<< content;
79+
EXPECT_NE(content.find(index.generic_string()), std::string::npos)
80+
<< content;
81+
EXPECT_NE(content.find(R"(https://example.com/a\\b\"c)"), std::string::npos)
82+
<< content;
83+
84+
std::filesystem::remove_all(project);
85+
}

0 commit comments

Comments
 (0)