Nicolas Cantu 69ab265560 feat: initial RAG sync with .4nkaiignore (extension 0.3, server 0.2)
**Motivations:**
- Seed AnythingLLM workspace from cloned repo using gitignore-style filters

**Root causes:**
- N/A

**Correctifs:**
- N/A

**Evolutions:**
- Template 4nkaiignore.default; server copies after clone; extension uploads via POST /api/v1/document/upload
- New commands /workspace-sync; settings initialSync*; dependency ignore

**Pages affectées:**
- extensions/anythingllm-workspaces/*
- services/repos-devtools-server/*
- docs/features/initial-rag-sync-4nkaiignore.md
2026-03-24 22:36:37 +01:00

147 lines
3.9 KiB
TypeScript

import * as fs from "node:fs/promises";
import type { IncomingMessage, ServerResponse } from "node:http";
import * as path from "node:path";
import { runGit } from "./gitSpawn.js";
import { readJsonBody } from "./httpUtil.js";
import {
getCodeRoot,
isGitRepo,
repoDirForName,
repoNameFromGitUrl,
} from "./paths.js";
import { copyDefault4nkaiignoreIfMissing } from "./write4nkaiignore.js";
const json = (res: ServerResponse, status: number, body: unknown): void => {
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify(body));
};
const isRecord = (v: unknown): v is Record<string, unknown> =>
typeof v === "object" && v !== null && !Array.isArray(v);
const readBranch = (body: unknown, fallback: string): string => {
if (!isRecord(body)) {
return fallback;
}
const b = body.branch;
return typeof b === "string" && b.trim().length > 0 ? b.trim() : fallback;
};
const readUrl = (body: unknown): string => {
if (!isRecord(body)) {
throw new Error("Expected JSON object with url");
}
const u = body.url;
if (typeof u !== "string" || u.trim().length === 0) {
throw new Error("Missing or invalid url");
}
return u.trim();
};
const readName = (body: unknown): string => {
if (!isRecord(body)) {
throw new Error("Expected JSON object with name");
}
const n = body.name;
if (typeof n !== "string" || n.trim().length === 0) {
throw new Error("Missing or invalid name");
}
return n.trim();
};
export const handleReposClone = async (
req: IncomingMessage,
res: ServerResponse,
): Promise<void> => {
const codeRoot = getCodeRoot();
const body = await readJsonBody(req);
const url = readUrl(body);
const branch = readBranch(body, "test");
const name = repoNameFromGitUrl(url);
const dest = path.join(codeRoot, name);
try {
await fs.access(dest);
json(res, 409, {
error: "Target directory already exists",
name,
path: dest,
});
return;
} catch {
/* absent */
}
const r = await runGit(
["clone", "-b", branch, "--single-branch", url, dest],
codeRoot,
);
if (r.code !== 0) {
json(res, 500, {
error: "git clone failed",
code: r.code,
stderr: r.stderr,
stdout: r.stdout,
});
return;
}
let fourNkAiIgnoreTemplateWrote = false;
try {
const c = await copyDefault4nkaiignoreIfMissing(dest);
fourNkAiIgnoreTemplateWrote = c.wrote;
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
json(res, 500, {
error: "clone ok but failed to write default .4nkaiignore template",
detail: msg,
name,
path: dest,
});
return;
}
json(res, 200, {
ok: true,
name,
path: dest,
branch,
url,
fourNkAiIgnoreTemplateWrote,
});
};
export const handleReposList = async (res: ServerResponse): Promise<void> => {
const codeRoot = getCodeRoot();
const entries = await fs.readdir(codeRoot, { withFileTypes: true });
const repos: { name: string; path: string }[] = [];
for (const ent of entries) {
if (!ent.isDirectory()) {
continue;
}
const full = path.join(codeRoot, ent.name);
if (await isGitRepo(full)) {
repos.push({ name: ent.name, path: full });
}
}
repos.sort((a, b) => a.name.localeCompare(b.name));
json(res, 200, { repos, codeRoot });
};
export const handleReposLoad = async (
req: IncomingMessage,
res: ServerResponse,
): Promise<void> => {
const codeRoot = getCodeRoot();
const body = await readJsonBody(req);
const name = readName(body);
const dest = repoDirForName(codeRoot, name);
try {
await fs.access(dest);
} catch {
json(res, 404, { error: "Repository directory not found", name, path: dest });
return;
}
if (!(await isGitRepo(dest))) {
json(res, 400, { error: "Directory is not a git repository", name, path: dest });
return;
}
json(res, 200, { ok: true, name, path: dest });
};