89 lines
2.1 KiB
TypeScript
89 lines
2.1 KiB
TypeScript
import { exec } from "node:child_process";
|
|
import { existsSync, statSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { basename, normalize, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
export const runtime = "nodejs";
|
|
|
|
/**
|
|
* GET /api/workspace/path-info?path=...
|
|
* Resolves and inspects a filesystem path for in-app preview routing.
|
|
*/
|
|
export async function GET(req: Request) {
|
|
const url = new URL(req.url);
|
|
const rawPath = url.searchParams.get("path");
|
|
|
|
if (!rawPath) {
|
|
return Response.json(
|
|
{ error: "Missing 'path' query parameter" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
let candidatePath = rawPath;
|
|
|
|
// Convert file:// URLs into local paths first.
|
|
if (candidatePath.startsWith("file://")) {
|
|
try {
|
|
candidatePath = fileURLToPath(candidatePath);
|
|
} catch {
|
|
return Response.json(
|
|
{ error: "Invalid file URL" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
}
|
|
|
|
// Expand "~/..." to the current user's home directory.
|
|
const expandedPath = candidatePath.startsWith("~/")
|
|
? candidatePath.replace(/^~/, homedir())
|
|
: candidatePath;
|
|
let resolvedPath = resolve(normalize(expandedPath));
|
|
|
|
// If the path doesn't exist and looks like a bare filename, try to locate it
|
|
// using macOS Spotlight (mdfind).
|
|
if (!existsSync(resolvedPath) && !rawPath.includes("/")) {
|
|
const found = await new Promise<string | null>((res) => {
|
|
exec(
|
|
`mdfind -name ${JSON.stringify(rawPath)} | head -1`,
|
|
(err, stdout) => {
|
|
if (err || !stdout.trim()) {res(null);}
|
|
else {res(stdout.trim().split("\n")[0]);}
|
|
},
|
|
);
|
|
});
|
|
if (found && existsSync(found)) {
|
|
resolvedPath = found;
|
|
}
|
|
}
|
|
|
|
if (!existsSync(resolvedPath)) {
|
|
return Response.json(
|
|
{ error: "Path not found", path: resolvedPath },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const stat = statSync(resolvedPath);
|
|
const type = stat.isDirectory()
|
|
? "directory"
|
|
: stat.isFile()
|
|
? "file"
|
|
: "other";
|
|
|
|
return Response.json({
|
|
path: resolvedPath,
|
|
name: basename(resolvedPath) || resolvedPath,
|
|
type,
|
|
});
|
|
} catch {
|
|
return Response.json(
|
|
{ error: "Cannot stat path", path: resolvedPath },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|