From ead9ea76fa6a515c8b3c8a3dc94da2526fcb8777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E7=82=8E?= <635735027@qq.com> Date: Thu, 16 Apr 2026 22:10:48 +0800 Subject: [PATCH] feat: add config-loader.js and initial server test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add config-loader.js module for loading sgclaw_config.json credentials and resolving project root directory. Add initial Rust source-guard test to verify server file paths exist. 🤖 Generated with [Qoder][https://qoder.com] --- frontend/scene-generator/config-loader.js | 89 +++++++++++++++++++++++ tests/scene_generator_server_test.rs | 39 ++++++++++ 2 files changed, 128 insertions(+) create mode 100644 frontend/scene-generator/config-loader.js create mode 100644 tests/scene_generator_server_test.rs diff --git a/frontend/scene-generator/config-loader.js b/frontend/scene-generator/config-loader.js new file mode 100644 index 0000000..fbafb92 --- /dev/null +++ b/frontend/scene-generator/config-loader.js @@ -0,0 +1,89 @@ +const fs = require("fs"); +const path = require("path"); + +function resolveProjectRoot() { + const envRoot = process.env.SGCLAW_PROJECT_ROOT; + if (envRoot && fs.existsSync(envRoot)) { + return path.resolve(envRoot); + } + + const configPath = resolveConfigPath(); + if (configPath && fs.existsSync(configPath)) { + return path.dirname(configPath); + } + + return path.resolve(__dirname); +} + +function resolveConfigPath() { + const envPath = process.env.SGCLAW_CONFIG_PATH; + if (envPath && fs.existsSync(envPath)) { + return path.resolve(envPath); + } + + const candidates = [ + path.resolve(__dirname, "..", "..", "sgclaw_config.json"), + path.resolve(__dirname, "..", "sgclaw_config.json"), + path.resolve(__dirname, "sgclaw_config.json"), + ]; + + for (const p of candidates) { + if (fs.existsSync(p)) return p; + } + + return null; +} + +function loadConfig() { + const configPath = resolveConfigPath(); + if (!configPath) { + throw new Error( + "sgclaw_config.json not found. Set SGCLAW_CONFIG_PATH or place it in the project root." + ); + } + + const raw = fs.readFileSync(configPath, "utf-8"); + const config = JSON.parse(raw); + + const apiKey = config.apiKey || ""; + const baseUrl = config.baseUrl || ""; + const model = config.model || ""; + + if (!apiKey) throw new Error("sgclaw_config.json: 'apiKey' is required"); + if (!baseUrl) throw new Error("sgclaw_config.json: 'baseUrl' is required"); + if (!model) throw new Error("sgclaw_config.json: 'model' is required"); + + return { + apiKey, + baseUrl: normalizeBaseUrl(baseUrl), + model, + projectRoot: resolveProjectRoot(), + configPath, + }; +} + +function normalizeBaseUrl(url) { + url = url.replace(/\/+$/, ""); + if (!url.endsWith("/v1")) url = url + "/v1"; + return url; +} + +function getDefaults() { + const config = loadConfig(); + const projectRoot = config.projectRoot; + + return { + outputRoot: path.join(projectRoot, "examples", "generated_scene_platform"), + lessonsPath: path.join( + projectRoot, + "docs", + "superpowers", + "references", + "tq-lineloss-lessons-learned.toml" + ), + llmBaseUrl: config.baseUrl, + llmModel: config.model, + }; +} + +module.exports = { loadConfig, getDefaults, resolveProjectRoot, resolveConfigPath }; diff --git a/tests/scene_generator_server_test.rs b/tests/scene_generator_server_test.rs new file mode 100644 index 0000000..4238849 --- /dev/null +++ b/tests/scene_generator_server_test.rs @@ -0,0 +1,39 @@ +use std::fs; +use std::path::PathBuf; + +#[test] +fn scene_generator_server_files_exist() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let server_js = manifest_dir + .join("frontend") + .join("scene-generator") + .join("server.js"); + let config_loader = manifest_dir + .join("frontend") + .join("scene-generator") + .join("config-loader.js"); + + assert!( + server_js.exists(), + "server.js not found at {:?}", + server_js + ); + assert!( + config_loader.exists(), + "config-loader.js not found at {:?}", + config_loader + ); +} + +#[test] +fn sgclaw_config_is_readable() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let config_path = manifest_dir.join("sgclaw_config.json"); + let content = fs::read_to_string(&config_path) + .unwrap_or_else(|err| panic!("sgclaw_config.json not found: {}", err)); + let parsed: serde_json::Value = + serde_json::from_str(&content).expect("should be valid JSON"); + assert!(parsed.get("apiKey").is_some(), "missing apiKey"); + assert!(parsed.get("baseUrl").is_some(), "missing baseUrl"); + assert!(parsed.get("model").is_some(), "missing model"); +}