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

47 lines
1.4 KiB
TypeScript

import * as fs from "node:fs/promises";
import * as path from "node:path";
const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$/;
export const assertSafeRepoName = (name: string): string => {
const trimmed = name.trim();
if (!SAFE_NAME.test(trimmed)) {
throw new Error("Invalid repo name: use letters, digits, . _ - only; no path segments.");
}
if (trimmed.includes("..")) {
throw new Error("Invalid repo name: path traversal rejected.");
}
return trimmed;
};
export const getCodeRoot = (): string => {
const raw = process.env.REPOS_DEVTOOLS_ROOT ?? "/home/ncantu/code";
return path.resolve(raw);
};
export const repoDirForName = (codeRoot: string, name: string): string => {
const safe = assertSafeRepoName(name);
return path.join(codeRoot, safe);
};
export const isGitRepo = async (dir: string): Promise<boolean> => {
try {
const stat = await fs.stat(path.join(dir, ".git"));
return stat.isDirectory() || stat.isFile();
} catch {
return false;
}
};
export const repoNameFromGitUrl = (rawUrl: string): string => {
const trimmed = rawUrl.trim();
const noQuery = trimmed.split("?")[0] ?? trimmed;
const base = noQuery.replace(/[/]+$/, "");
const segment = base.split("/").filter((s) => s.length > 0).pop() ?? "";
const withoutGit = segment.replace(/\.git$/i, "");
if (withoutGit.length === 0) {
throw new Error("Could not derive repository name from URL.");
}
return assertSafeRepoName(withoutGit);
};