auth-yes/.forum/src/installer.ts

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);
}