Improve project folder creation

This commit is contained in:
Federico Jaramillo Martinez
2026-05-08 14:38:12 +02:00
parent 6ea7e51e75
commit 2c7559c61c
8 changed files with 203 additions and 12 deletions
+10 -1
View File
@@ -7,6 +7,7 @@ import { ProjectStore } from "./storage/projectStore.js";
import { ProjectService } from "./projects/projectService.js";
import { WorkspaceService } from "./workspaces/workspaceService.js";
import { listFileSuggestions, listPathSuggestions } from "./workspaces/fileSuggestions.js";
import { listDirectorySuggestions } from "./projects/directorySuggestions.js";
import { registerSessionProxyRoutes } from "./sessiond/sessionProxyRoutes.js";
import { registerWorkspaceExplorerRoutes } from "./workspaceExplorerRoutes.js";
import { registerGitRoutes } from "./gitRoutes.js";
@@ -19,7 +20,7 @@ const workspaces = new WorkspaceService();
app.get("/api/projects", async () => projects.list());
app.post<{ Body: { name?: string; path: string } }>("/api/projects", async (request, reply) => {
app.post<{ Body: { name?: string; path: string; create?: boolean } }>("/api/projects", async (request, reply) => {
try {
return await projects.add(request.body);
} catch (error) {
@@ -27,6 +28,14 @@ app.post<{ Body: { name?: string; path: string } }>("/api/projects", async (requ
}
});
app.get<{ Querystring: { q?: string } }>("/api/project-directories", async (request, reply) => {
try {
return await listDirectorySuggestions(request.query.q ?? "");
} catch (error) {
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
}
});
app.get<{ Params: { projectId: string } }>("/api/projects/:projectId/workspaces", async (request, reply) => {
try {
const project = await projects.requireProject(request.params.projectId);
@@ -0,0 +1,36 @@
import { homedir } from "node:os";
import { basename, dirname, isAbsolute, resolve, sep } from "node:path";
import { readdir, stat } from "node:fs/promises";
import type { ClientFileSuggestion } from "../types.js";
export function expandUserPath(path: string): string {
if (path === "" || path === "~") return homedir();
if (path.startsWith(`~${sep}`) || path.startsWith("~/")) return resolve(homedir(), path.slice(2));
return isAbsolute(path) ? resolve(path) : resolve(process.cwd(), path);
}
export async function listDirectorySuggestions(query = ""): Promise<ClientFileSuggestion[]> {
const raw = query.trim();
const expanded = expandUserPath(raw);
const endsWithSeparator = raw === "" || raw.endsWith("/") || raw.endsWith("\\") || raw === "~";
const parent = endsWithSeparator ? expanded : dirname(expanded);
const search = endsWithSeparator ? "" : basename(expanded).toLowerCase();
const entries = await readdir(parent, { withFileTypes: true });
const suggestions: ClientFileSuggestion[] = [];
for (const entry of entries) {
if (!entry.name.toLowerCase().startsWith(search)) continue;
let isDirectory = entry.isDirectory();
const path = resolve(parent, entry.name);
if (!isDirectory && entry.isSymbolicLink()) {
try {
isDirectory = (await stat(path)).isDirectory();
} catch {
isDirectory = false;
}
}
if (isDirectory) suggestions.push({ path: `${path}/`, kind: "other" });
}
return suggestions.sort((a, b) => a.path.localeCompare(b.path)).slice(0, 80);
}
+6 -3
View File
@@ -1,6 +1,7 @@
import { realpath, stat } from "node:fs/promises";
import { mkdir, realpath, stat } from "node:fs/promises";
import type { ProjectStore } from "../storage/projectStore.js";
import type { Project } from "../types.js";
import { expandUserPath } from "./directorySuggestions.js";
export class ProjectService {
constructor(private readonly store: ProjectStore) {}
@@ -9,8 +10,10 @@ export class ProjectService {
return this.store.list();
}
async add(input: { name?: string; path: string }): Promise<Project> {
const resolved = await realpath(input.path);
async add(input: { name?: string; path: string; create?: boolean }): Promise<Project> {
const requestedPath = expandUserPath(input.path);
if (input.create === true) await mkdir(requestedPath, { recursive: true });
const resolved = await realpath(requestedPath);
const s = await stat(resolved);
if (!s.isDirectory()) throw new Error("Project path must be a directory");
return this.store.add(input.name === undefined ? { path: resolved } : { name: input.name, path: resolved });