231 lines
6.0 KiB
JavaScript
231 lines
6.0 KiB
JavaScript
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { basename, join, relative } from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const workspaceRoot =
|
|
process.env.WORKSPACE_FATHER_PATH || "/Users/Tabitha/.openclaw/workspace_Father";
|
|
const outputPath = join(process.cwd(), "public", "local-context.json");
|
|
const giteaBaseUrl = "http://gitea.orson.tealthrone";
|
|
const folderSearchRoots = (process.env.WORKSPACE_FOLDER_SEARCH_ROOTS || `/Users/Tabitha,${workspaceRoot}`)
|
|
.split(",")
|
|
.map((root) => root.trim())
|
|
.filter(Boolean);
|
|
const folderSearchMaxDepth = Number(process.env.WORKSPACE_FOLDER_SEARCH_MAX_DEPTH || 3);
|
|
const skippedFolderNames = new Set([
|
|
".git",
|
|
".Trash",
|
|
"Applications",
|
|
"Library",
|
|
"Movies",
|
|
"Music",
|
|
"node_modules",
|
|
"dist",
|
|
"build",
|
|
".next",
|
|
".vite"
|
|
]);
|
|
|
|
function runGit(args, cwd) {
|
|
try {
|
|
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function findGitWorktrees(root, maxDepth = 4) {
|
|
const found = [];
|
|
|
|
function walk(dir, depth) {
|
|
if (depth > maxDepth) {
|
|
return;
|
|
}
|
|
|
|
if (existsSync(join(dir, ".git"))) {
|
|
found.push(dir);
|
|
return;
|
|
}
|
|
|
|
let entries = [];
|
|
try {
|
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git") {
|
|
continue;
|
|
}
|
|
walk(join(dir, entry.name), depth + 1);
|
|
}
|
|
}
|
|
|
|
walk(root, 0);
|
|
return found.sort();
|
|
}
|
|
|
|
function findFolderCandidates(roots, maxDepth = 3) {
|
|
const found = new Map();
|
|
|
|
function addCandidate(path, source) {
|
|
found.set(path, {
|
|
name: basename(path),
|
|
path,
|
|
source
|
|
});
|
|
}
|
|
|
|
function walk(dir, depth, source) {
|
|
if (depth > maxDepth || found.has(dir)) {
|
|
return;
|
|
}
|
|
|
|
addCandidate(dir, source);
|
|
|
|
let entries = [];
|
|
try {
|
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory() || skippedFolderNames.has(entry.name)) {
|
|
continue;
|
|
}
|
|
walk(join(dir, entry.name), depth + 1, source);
|
|
}
|
|
}
|
|
|
|
for (const root of roots) {
|
|
if (!existsSync(root)) {
|
|
continue;
|
|
}
|
|
walk(root, 0, root === workspaceRoot ? "workspace-root" : "folder-search");
|
|
}
|
|
|
|
return Array.from(found.values())
|
|
.filter((candidate) => candidate.path !== "/Users/Tabitha")
|
|
.sort((a, b) => a.path.localeCompare(b.path))
|
|
.slice(0, 2000);
|
|
}
|
|
|
|
function normalizeGiteaRemote(remoteUrl) {
|
|
if (!remoteUrl) {
|
|
return null;
|
|
}
|
|
|
|
const match = remoteUrl.match(
|
|
/(?:gitea\.orson\.tealthrone|100\.79\.253\.19(?::3000)?)[/:]([^/]+)\/([^/.]+)(?:\.git)?$/
|
|
);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
|
|
const [, owner, name] = match;
|
|
return {
|
|
fullName: `${owner}/${name}`,
|
|
owner,
|
|
name,
|
|
webUrl: `${giteaBaseUrl}/${owner}/${name}`,
|
|
cloneUrl: `http://100.79.253.19:3000/${owner}/${name}.git`
|
|
};
|
|
}
|
|
|
|
function repoForWorktree(path) {
|
|
const remoteNames = runGit(["remote"], path).split(/\s+/).filter(Boolean);
|
|
const orderedNames = ["gitea", "hagia", "origin", ...remoteNames].filter((name, index, names) => {
|
|
return remoteNames.includes(name) && names.indexOf(name) === index;
|
|
});
|
|
|
|
for (const remoteName of orderedNames) {
|
|
const repo = normalizeGiteaRemote(runGit(["remote", "get-url", remoteName], path));
|
|
if (repo) {
|
|
return repo;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function buildContext() {
|
|
const topLevelWorkspaces = readdirSync(workspaceRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => ({
|
|
name: entry.name,
|
|
path: join(workspaceRoot, entry.name)
|
|
}))
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
const gitWorktrees = findGitWorktrees(workspaceRoot).map((path) => {
|
|
const repo = repoForWorktree(path);
|
|
const topWorkspaceName = relative(workspaceRoot, path).split("/")[0] || basename(path);
|
|
return {
|
|
name: basename(path),
|
|
path,
|
|
relativePath: relative(workspaceRoot, path),
|
|
topWorkspaceName,
|
|
branch: runGit(["branch", "--show-current"], path) || null,
|
|
giteaRepo: repo
|
|
};
|
|
});
|
|
|
|
const workspaces = topLevelWorkspaces.map((workspace) => ({
|
|
...workspace,
|
|
linkedRepos: gitWorktrees
|
|
.filter((worktree) => worktree.topWorkspaceName === workspace.name && worktree.giteaRepo)
|
|
.map((worktree) => ({
|
|
worktreePath: worktree.path,
|
|
worktreeRelativePath: worktree.relativePath,
|
|
fullName: worktree.giteaRepo.fullName,
|
|
webUrl: worktree.giteaRepo.webUrl
|
|
}))
|
|
}));
|
|
|
|
const workspaceData = gitWorktrees.find(
|
|
(worktree) => worktree.path === join(workspaceRoot, "workspace_Data")
|
|
);
|
|
|
|
return {
|
|
generatedAt: "build-time-local-context",
|
|
workspaceRoot,
|
|
workspaces,
|
|
gitWorktrees,
|
|
folderCandidates: findFolderCandidates(folderSearchRoots, folderSearchMaxDepth),
|
|
knownUrls: [
|
|
{
|
|
label: "Automated Training Monitor",
|
|
url: "http://100.66.226.22:23030/",
|
|
workspacePath: join(workspaceRoot, "workspace_Data"),
|
|
workspaceName: "workspace_Data",
|
|
giteaRepo: workspaceData?.giteaRepo ?? {
|
|
fullName: "jacob-mathison/home-grown-llm-data",
|
|
webUrl: `${giteaBaseUrl}/jacob-mathison/home-grown-llm-data`
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
export function writeLocalContext() {
|
|
mkdirSync(join(process.cwd(), "public"), { recursive: true });
|
|
|
|
if (!existsSync(workspaceRoot)) {
|
|
if (existsSync(outputPath)) {
|
|
console.log(`Workspace root missing; preserving ${outputPath}`);
|
|
return;
|
|
}
|
|
throw new Error(`Workspace root does not exist: ${workspaceRoot}`);
|
|
}
|
|
|
|
const context = buildContext();
|
|
writeFileSync(outputPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
console.log(`Wrote ${outputPath} with ${context.workspaces.length} workspaces`);
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
writeLocalContext();
|
|
}
|