1export interface TreeNode {
2 name: string;
3 fullPath: string;
4 type: "file" | "directory";
5 children?: TreeNode[];
6}
7
8type BuildNode = {
9 children: { [key: string]: BuildNode };
10};
11
12export function buildTree(paths: string[] | Set<string>): TreeNode {
13 // sorry i used claude for this
14 const root: { [key: string]: BuildNode } = {};
15
16 for (const path of paths) {
17 const parts = path.split("/");
18 let current = root;
19
20 for (let i = 0; i < parts.length; i++) {
21 const part = parts[i];
22 current[part] ??= { children: {} };
23 if (i < parts.length - 1) {
24 current = current[part].children;
25 }
26 }
27 }
28
29 const entries = (obj: BuildNode["children"], path: string) =>
30 Object.entries(obj)
31 .map(([k, v]) => convert(path, v, k))
32 .sort((a, b) => {
33 if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
34
35 const aDot = a.name.startsWith(".");
36 const bDot = b.name.startsWith(".");
37 if (aDot !== bDot) return aDot ? -1 : 1;
38
39 return a.name.localeCompare(b.name, undefined, { numeric: true });
40 });
41
42 const convert = (path: string, node: BuildNode, name: string): TreeNode => {
43 const hasChildren = Object.keys(node.children).length > 0;
44 const fullPath = `${path}${name}`;
45 return {
46 name,
47 type: hasChildren ? "directory" : "file",
48 fullPath,
49 children: hasChildren
50 ? entries(node.children, `${fullPath}/`)
51 : undefined,
52 };
53 };
54
55 return {
56 name: "",
57 fullPath: "",
58 type: "directory",
59 children: entries(root, ""),
60 };
61}