98 lines
2.4 KiB
TypeScript
98 lines
2.4 KiB
TypeScript
import { exec } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import { resolve, normalize } from "node:path";
|
|
import { homedir } from "node:os";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
export const runtime = "nodejs";
|
|
|
|
/**
|
|
* POST /api/workspace/open-file
|
|
* Opens a file or directory using the system's default application.
|
|
* On macOS this uses `open`, on Linux `xdg-open`.
|
|
*/
|
|
export async function POST(req: Request) {
|
|
let body: { path?: string; reveal?: boolean };
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return Response.json(
|
|
{ error: "Invalid JSON body" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const rawPath = body.path;
|
|
if (!rawPath || typeof rawPath !== "string") {
|
|
return Response.json(
|
|
{ error: "Missing 'path' in request body" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Expand ~ to home directory
|
|
const expanded = rawPath.startsWith("~/")
|
|
? rawPath.replace(/^~/, homedir())
|
|
: rawPath;
|
|
|
|
let resolved = resolve(normalize(expanded));
|
|
|
|
// If the file doesn't exist and looks like a bare filename, try to locate it
|
|
// using macOS Spotlight (mdfind).
|
|
if (!existsSync(resolved) && !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)) {
|
|
resolved = found;
|
|
}
|
|
}
|
|
|
|
if (!existsSync(resolved)) {
|
|
return Response.json(
|
|
{ error: "File not found", path: resolved },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
const platform = process.platform;
|
|
const reveal = body.reveal === true;
|
|
|
|
let cmd: string;
|
|
if (platform === "darwin") {
|
|
// macOS: use `open` — `-R` reveals in Finder instead of opening
|
|
cmd = reveal
|
|
? `open -R ${JSON.stringify(resolved)}`
|
|
: `open ${JSON.stringify(resolved)}`;
|
|
} else if (platform === "linux") {
|
|
// Linux: xdg-open (no reveal equivalent)
|
|
cmd = `xdg-open ${JSON.stringify(resolved)}`;
|
|
} else {
|
|
return Response.json(
|
|
{ error: `Unsupported platform: ${platform}` },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
return new Promise<Response>((res) => {
|
|
exec(cmd, (error) => {
|
|
if (error) {
|
|
res(
|
|
Response.json(
|
|
{ error: `Failed to open file: ${error.message}` },
|
|
{ status: 500 },
|
|
),
|
|
);
|
|
} else {
|
|
res(Response.json({ ok: true, path: resolved }));
|
|
}
|
|
});
|
|
});
|
|
}
|