139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
// src/installer.ts
|
|
import { copy } from "jsr:@std/fs/copy";
|
|
import { ensureDir } from "jsr:@std/fs/ensure-dir";
|
|
import { join, resolve } from "jsr:@std/path";
|
|
|
|
async function runCmd(
|
|
cmd: string,
|
|
args: string[],
|
|
cwd: string,
|
|
): Promise<string> {
|
|
const command = new Deno.Command(cmd, {
|
|
args,
|
|
cwd,
|
|
stdout: "piped",
|
|
stderr: "piped",
|
|
});
|
|
const output = await command.output();
|
|
if (output.code !== 0) {
|
|
const errText = new TextDecoder().decode(output.stderr).trim();
|
|
throw new Error(
|
|
`Command '${cmd} ${args.join(" ")}' failed in ${cwd}: ${errText}`,
|
|
);
|
|
}
|
|
return new TextDecoder().decode(output.stdout).trim();
|
|
}
|
|
|
|
export async function main(args: string[] = Deno.args) {
|
|
const rawTarget = args[0] ?? Deno.cwd();
|
|
const targetDir = resolve(rawTarget);
|
|
const engineRoot = resolve(new URL(".", import.meta.url).pathname, "..");
|
|
|
|
console.log(`-> Installing Agent Forum from ${engineRoot} to ${targetDir}`);
|
|
|
|
// 1. Verify target is a git repository
|
|
await runCmd("git", ["rev-parse", "--show-toplevel"], targetDir);
|
|
|
|
// 2. Scaffolding directories
|
|
const targetForum = join(targetDir, ".forum");
|
|
await ensureDir(join(targetForum, "src"));
|
|
await ensureDir(join(targetForum, "hooks"));
|
|
await ensureDir(join(targetForum, "config"));
|
|
|
|
// 3. Copy engine source and hooks
|
|
await copy(join(engineRoot, "src"), join(targetForum, "src"), {
|
|
overwrite: true,
|
|
});
|
|
await copy(join(engineRoot, "src", "hooks"), join(targetForum, "hooks"), {
|
|
overwrite: true,
|
|
});
|
|
|
|
// 4. Configure git hooks path
|
|
await runCmd("git", ["config", "core.hooksPath", ".forum/hooks"], targetDir);
|
|
|
|
// 5. Append managed block to target .gitignore
|
|
const gitignorePath = join(targetDir, ".gitignore");
|
|
let gitignoreContent = "";
|
|
try {
|
|
gitignoreContent = await Deno.readTextFile(gitignorePath);
|
|
} catch {
|
|
// File does not exist yet
|
|
}
|
|
|
|
const managedHeader = "# === AGENT-FORUM MANAGED BLOCK ===";
|
|
const managedFooter = "# === END AGENT-FORUM MANAGED BLOCK ===";
|
|
const block = [
|
|
managedHeader,
|
|
".forum/ast/",
|
|
".forum/security/",
|
|
".forum/reports/",
|
|
".forum/worktree_meta/",
|
|
managedFooter,
|
|
].join("\n");
|
|
|
|
if (!gitignoreContent.includes(managedHeader)) {
|
|
const updated = gitignoreContent
|
|
? `${gitignoreContent.trimEnd()}\n\n${block}\n`
|
|
: `${block}\n`;
|
|
await Deno.writeTextFile(gitignorePath, updated);
|
|
}
|
|
|
|
// 5.5 Create agent-forum configuration in the target root if it does not exist
|
|
const configPath = join(targetDir, "agent-forum.json");
|
|
try {
|
|
await Deno.stat(configPath);
|
|
console.log(
|
|
"-> Existing agent-forum.json found in repository root. Preserving it.",
|
|
);
|
|
} catch {
|
|
console.log("-> Creating default agent-forum.json configuration...");
|
|
const defaultConfig = {
|
|
"stryker": {
|
|
"ignoreTests": ["test/integration/"],
|
|
"showProgress": true,
|
|
},
|
|
};
|
|
await Deno.writeTextFile(
|
|
configPath,
|
|
JSON.stringify(defaultConfig, null, 2),
|
|
);
|
|
}
|
|
|
|
// Cleanup old config if it exists in .forum/
|
|
const oldConfigPath = join(targetForum, "agent-forum.json");
|
|
try {
|
|
await Deno.stat(oldConfigPath);
|
|
await Deno.remove(oldConfigPath);
|
|
console.log("-> Cleaned up old agent-forum.json from .forum/ directory.");
|
|
} catch {
|
|
// Old config does not exist, nothing to clean up
|
|
}
|
|
|
|
// 6. Bootstrap meta-state branch
|
|
await runCmd(
|
|
"deno",
|
|
["run", "-A", join(targetForum, "src", "cli.ts"), "bootstrap"],
|
|
targetDir,
|
|
);
|
|
|
|
// 7. Sync Vector Database
|
|
console.log("-> Synchronizing Vector Databases...");
|
|
await runCmd(
|
|
"deno",
|
|
[
|
|
"run",
|
|
"-A",
|
|
join(targetForum, "src", "cli.ts"),
|
|
"replay",
|
|
"--sync",
|
|
],
|
|
targetDir,
|
|
);
|
|
|
|
console.log("✅ Agent Forum installed successfully.");
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
await main(Deno.args);
|
|
}
|