feat: switch config to JSONC + split secrets.json

Load business config as JSONC (comments + trailing commas) via an
in-repo stripper then stdlib json — no new deps. Default path prefers
config.jsonc, falls back to legacy config.json. Auth write-back targets
only sibling secrets.json so JSONC comments are never wiped; secrets
overlay merges on load. Examples, compose, Docker, docs, favsort and
tools all share the same loader. Phase A only — no DB settings migration.
This commit was merged in pull request #4.
This commit is contained in:
bot-ci committed 2026-11-02 12:58:20 +08:00
1 parent b4f0f45c4e
commit 8e7ead1669
21 files changed
+652 -182

No files matched your search

+83 -2
View File
@@ -11,7 +11,13 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
from reaction_board.config import ConfigError, ConfigStore, load_config # noqa: E402
from reaction_board.config import ( # noqa: E402
ConfigError,
ConfigStore,
default_config_path,
load_config,
resolve_secrets_path,
)
def write(path: Path, data) -> None:
@@ -294,6 +300,78 @@ def test_schedule_recent_validation():
def test_jsonc_and_secrets_merge_and_hot_reload():
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
config_path = root / "config.jsonc"
secrets_path = root / "secrets.json"
config_path.write_text(
"""
// 业务配置
{
"folders": [
{"year": 2026, "media_id": 1, "title": "A"},
],
"track_fans": [],
"up_blacklist_seed": [],
"telegram": { "enabled": true },
"web": { "host": "127.0.0.1", "port": 8080 },
}
""",
encoding="utf-8",
)
secrets_path.write_text(json.dumps({
"bilibili": {
"read": {"mid": 11, "csrf": "rc", "cookie": "rk"},
"write": {"mid": 22, "csrf": "wc", "cookie": "wk"},
},
"telegram": {"bot_token": "tok", "chat_id": 9},
"web": {"auth_token": "secret-token"},
}), encoding="utf-8")
cfg = load_config(config_path, env={})
assert cfg.secrets_path == secrets_path
assert cfg.bilibili.read.cookie == "rk"
assert cfg.telegram.enabled is True
assert cfg.telegram.bot_token == "tok"
assert cfg.telegram.chat_id == 9
assert cfg.web.auth_token == "secret-token"
assert resolve_secrets_path(config_path) == secrets_path
# 主配置注释在热更新凭据后仍保留
original = config_path.read_text(encoding="utf-8")
store = ConfigStore(config_path, env={})
secrets = json.loads(secrets_path.read_text(encoding="utf-8"))
secrets["bilibili"]["read"]["cookie"] = "rk2"
secrets_path.write_text(json.dumps(secrets), encoding="utf-8")
bump_mtime(secrets_path)
assert store.get().bilibili.read.cookie == "rk2"
assert config_path.read_text(encoding="utf-8") == original
assert "// 业务配置" in original
def test_legacy_monolithic_json_still_loads():
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "config.json"
write(path, base_config())
cfg = load_config(path, env={})
assert cfg.bilibili.read.mid == 11
assert cfg.secrets_path == Path(tmp) / "secrets.json"
def test_default_config_path_prefers_jsonc():
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
json_path = root / "config.json"
jsonc_path = root / "config.jsonc"
json_path.write_text("{}", encoding="utf-8")
assert default_config_path(root) == json_path
jsonc_path.write_text("{}", encoding="utf-8")
assert default_config_path(root) == jsonc_path
def main():
test_dual_accounts_and_env_override()
test_flat_and_missing_account_rejected()
@@ -302,7 +380,10 @@ def main():
test_analytics_defaults_and_validation()
test_schedule_recent_validation()
test_web_track_defaults_and_validation()
print("OK: 双账号配置加载、拒绝 flat 格式、ENV 覆盖、热加载与 analytics/web.track 测试全部通过")
test_jsonc_and_secrets_merge_and_hot_reload()
test_legacy_monolithic_json_still_loads()
test_default_config_path_prefers_jsonc()
print("OK: 双账号配置加载、JSONC/secrets、拒绝 flat 格式、ENV 覆盖、热加载与 analytics/web.track 测试全部通过")
if __name__ == "__main__":