Pass sceneKind to sg_scene_generate CLI when specified, allowing generation of different scene kinds (report_collection, monitoring, etc.). 🤖 Generated with [Qoder][https://qoder.com]
183 lines
4.7 KiB
JavaScript
183 lines
4.7 KiB
JavaScript
const { spawn } = require("child_process");
|
||
const path = require("path");
|
||
|
||
function runGenerator(params, sseWriter, projectRoot) {
|
||
const { sourceDir, sceneId, sceneName, sceneKind, outputRoot, lessons } = params;
|
||
|
||
const normalize = (p) => p.replace(/\\/g, "/");
|
||
|
||
const args = [
|
||
"run",
|
||
"--bin",
|
||
"sg_scene_generate",
|
||
"--",
|
||
"--source-dir",
|
||
normalize(sourceDir),
|
||
"--scene-id",
|
||
sceneId,
|
||
"--scene-name",
|
||
sceneName,
|
||
];
|
||
|
||
// 只有明确指定 sceneKind 时才添加参数(否则使用默认值 report_collection)
|
||
if (sceneKind) {
|
||
args.push("--scene-kind", sceneKind);
|
||
}
|
||
|
||
args.push(
|
||
"--output-root",
|
||
normalize(outputRoot),
|
||
"--lessons",
|
||
normalize(lessons)
|
||
);
|
||
|
||
return new Promise((resolve, reject) => {
|
||
sseWriter.write(
|
||
`event: status\ndata: ${JSON.stringify({
|
||
message: "开始生成 skill 包...",
|
||
})}\n\n`
|
||
);
|
||
sseWriter.write(
|
||
`event: status\ndata: ${JSON.stringify({
|
||
message: `执行: cargo ${args.join(" ")}`,
|
||
})}\n\n`
|
||
);
|
||
|
||
const child = spawn("cargo", args, {
|
||
cwd: projectRoot,
|
||
stdio: ["ignore", "pipe", "pipe"],
|
||
env: { ...process.env, RUST_BACKTRACE: "1" },
|
||
});
|
||
|
||
let stdout = "";
|
||
let stderr = "";
|
||
let timedOut = false;
|
||
|
||
const timeout = setTimeout(() => {
|
||
timedOut = true;
|
||
child.kill("SIGTERM");
|
||
sseWriter.write(
|
||
`event: error\ndata: ${JSON.stringify({
|
||
message: "生成超时(5分钟)",
|
||
})}\n\n`
|
||
);
|
||
resolve({ success: false, error: "timeout" });
|
||
}, 5 * 60 * 1000);
|
||
|
||
child.stdout.on("data", (data) => {
|
||
const text = data.toString();
|
||
stdout += text;
|
||
sseWriter.write(
|
||
`event: log\ndata: ${JSON.stringify({ message: text.trim() })}\n\n`
|
||
);
|
||
});
|
||
|
||
child.stderr.on("data", (data) => {
|
||
const text = data.toString();
|
||
stderr += text;
|
||
sseWriter.write(
|
||
`event: log\ndata: ${JSON.stringify({ message: text.trim() })}\n\n`
|
||
);
|
||
});
|
||
|
||
child.on("close", (code) => {
|
||
clearTimeout(timeout);
|
||
if (timedOut) return;
|
||
|
||
if (code === 0) {
|
||
const match = stdout.match(/generated scene package:\s*(.+)/);
|
||
const skillRoot = match ? match[1] : null;
|
||
sseWriter.write(
|
||
`event: status\ndata: ${JSON.stringify({
|
||
message: "✅ 生成成功",
|
||
})}\n\n`
|
||
);
|
||
sseWriter.write(
|
||
`event: complete\ndata: ${JSON.stringify({
|
||
success: true,
|
||
skillRoot,
|
||
})}\n\n`
|
||
);
|
||
resolve({ success: true, skillRoot });
|
||
} else {
|
||
sseWriter.write(
|
||
`event: error\ndata: ${JSON.stringify({
|
||
message: `生成失败 (exit code ${code})`,
|
||
})}\n\n`
|
||
);
|
||
if (stderr.trim()) {
|
||
sseWriter.write(
|
||
`event: error\ndata: ${JSON.stringify({
|
||
message: stderr.substring(0, 500),
|
||
})}\n\n`
|
||
);
|
||
}
|
||
resolve({ success: false, code, stderr });
|
||
}
|
||
});
|
||
|
||
child.on("error", (err) => {
|
||
clearTimeout(timeout);
|
||
sseWriter.write(
|
||
`event: error\ndata: ${JSON.stringify({
|
||
message: `无法启动 cargo: ${err.message}`,
|
||
})}\n\n`
|
||
);
|
||
reject(err);
|
||
});
|
||
});
|
||
}
|
||
|
||
function readDirectory(sourceDir) {
|
||
const fs = require("fs");
|
||
const p = require("path");
|
||
|
||
if (!fs.existsSync(sourceDir)) {
|
||
throw new Error(`Directory not found: ${sourceDir}`);
|
||
}
|
||
|
||
const stat = fs.statSync(sourceDir);
|
||
if (!stat.isDirectory()) {
|
||
throw new Error(`Not a directory: ${sourceDir}`);
|
||
}
|
||
|
||
const result = {};
|
||
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
||
|
||
const treeLines = [];
|
||
for (const entry of entries) {
|
||
treeLines.push(`├── ${entry.name}`);
|
||
}
|
||
result.tree = treeLines.join("\n");
|
||
|
||
const sceneTomlPath = p.join(sourceDir, "scene.toml");
|
||
if (fs.existsSync(sceneTomlPath)) {
|
||
result["scene.toml"] = fs.readFileSync(sceneTomlPath, "utf-8");
|
||
}
|
||
|
||
const skillTomlPath = p.join(sourceDir, "SKILL.toml");
|
||
if (fs.existsSync(skillTomlPath)) {
|
||
result["SKILL.toml"] = fs.readFileSync(skillTomlPath, "utf-8");
|
||
}
|
||
|
||
const skillMdPath = p.join(sourceDir, "SKILL.md");
|
||
if (fs.existsSync(skillMdPath)) {
|
||
result["SKILL.md"] = fs.readFileSync(skillMdPath, "utf-8");
|
||
}
|
||
|
||
const scripts = {};
|
||
for (const entry of entries) {
|
||
if (entry.isFile() && entry.name.endsWith(".js")) {
|
||
const scriptPath = p.join(sourceDir, entry.name);
|
||
scripts[entry.name] = fs.readFileSync(scriptPath, "utf-8");
|
||
}
|
||
}
|
||
if (Object.keys(scripts).length > 0) {
|
||
result.scripts = scripts;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
module.exports = { runGenerator, readDirectory };
|