Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions apps/server/src/wsServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,213 @@ describe("WebSocket Server", () => {
});
});

it("supports filesystem.browse with directory-only results", async () => {
const workspace = makeTempDir("t3code-ws-filesystem-browse-");
fs.mkdirSync(path.join(workspace, "components"), { recursive: true });
fs.mkdirSync(path.join(workspace, "composables"), { recursive: true });
fs.writeFileSync(path.join(workspace, "composer.ts"), "export {};\n", "utf8");

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: path.join(workspace, "comp"),
});

expect(response.error).toBeUndefined();
expect(response.result).toEqual({
parentPath: workspace,
entries: [
{
name: "components",
fullPath: path.join(workspace, "components"),
},
{
name: "composables",
fullPath: path.join(workspace, "composables"),
},
],
});
});

it("includes hidden directories when browsing a full directory", async () => {
const workspace = makeTempDir("t3code-ws-filesystem-browse-hidden-");
fs.mkdirSync(path.join(workspace, ".config"), { recursive: true });
fs.mkdirSync(path.join(workspace, "docs"), { recursive: true });

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: `${workspace}/`,
});

expect(response.error).toBeUndefined();
expect(response.result).toEqual({
parentPath: workspace,
entries: [
{
name: ".config",
fullPath: path.join(workspace, ".config"),
},
{
name: "docs",
fullPath: path.join(workspace, "docs"),
},
],
});
});

it("skips unreadable or broken browse entries instead of failing the request", async () => {
if (process.platform === "win32") {
return;
}

const workspace = makeTempDir("t3code-ws-filesystem-browse-broken-entry-");
fs.mkdirSync(path.join(workspace, "docs"), { recursive: true });
fs.symlinkSync(path.join(workspace, "missing-target"), path.join(workspace, "broken-link"));

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: `${workspace}/`,
});

expect(response.error).toBeUndefined();
expect(response.result).toEqual({
parentPath: workspace,
entries: [
{
name: "docs",
fullPath: path.join(workspace, "docs"),
},
],
});
});

it("resolves relative filesystem.browse paths against the provided cwd", async () => {
const workspace = makeTempDir("t3code-ws-filesystem-browse-relative-");
fs.mkdirSync(path.join(workspace, "apps"), { recursive: true });
fs.mkdirSync(path.join(workspace, "docs"), { recursive: true });

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: "../d",
cwd: path.join(workspace, "apps"),
});

expect(response.error).toBeUndefined();
expect(response.result).toEqual({
parentPath: workspace,
entries: [
{
name: "docs",
fullPath: path.join(workspace, "docs"),
},
],
});
});

it("resolves bare dot and dot-dot filesystem.browse paths against the provided cwd", async () => {
const root = makeTempDir("t3code-ws-filesystem-browse-dot-relative-");
const workspace = path.join(root, "workspace");
fs.mkdirSync(path.join(workspace, "apps"), { recursive: true });

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const currentDirResponse = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: ".",
cwd: workspace,
});
expect(currentDirResponse.error).toBeUndefined();
expect(currentDirResponse.result).toEqual({
parentPath: root,
entries: [
{
name: "workspace",
fullPath: workspace,
},
],
});

const parentDirResponse = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: "..",
cwd: path.join(workspace, "apps"),
});
expect(parentDirResponse.error).toBeUndefined();
expect(parentDirResponse.result).toEqual({
parentPath: root,
entries: [
{
name: "workspace",
fullPath: workspace,
},
],
});
});

it("rejects relative filesystem.browse paths without a cwd", async () => {
server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: "./docs",
});

expect(response.result).toBeUndefined();
expect(response.error?.message).toContain(
"Relative filesystem browse paths require a current project.",
);
});

it("rejects windows-style filesystem.browse paths on non-windows hosts", async () => {
if (process.platform === "win32") {
return;
}

server = await createTestServer({ cwd: "/test" });
const addr = server.address();
const port = typeof addr === "object" && addr !== null ? addr.port : 0;

const [ws] = await connectAndAwaitWelcome(port);
connections.push(ws);

const response = await sendRequest(ws, WS_METHODS.filesystemBrowse, {
partialPath: "C:\\Work\\Repo",
});

expect(response.result).toBeUndefined();
expect(response.error?.message).toContain("Windows-style paths are only supported on Windows.");
});

it("supports projects.writeFile within the workspace root", async () => {
const workspace = makeTempDir("t3code-ws-write-file-");

Expand Down
78 changes: 78 additions & 0 deletions apps/server/src/wsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts";
import { expandHomePath } from "./os-jank.ts";
import { makeServerPushBus } from "./wsServer/pushBus.ts";
import { makeServerReadiness } from "./wsServer/readiness.ts";
import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path";
import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson";

/**
Expand Down Expand Up @@ -110,6 +111,23 @@ const isServerNotRunningError = (error: Error): boolean => {
);
};

function resolveFilesystemBrowseInputPath(input: {
cwd: string | undefined;
path: Path.Path;
partialPath: string;
}): Effect.Effect<string | null, never, Path.Path> {
return Effect.gen(function* () {
if (!isExplicitRelativePath(input.partialPath)) {
return input.path.resolve(yield* expandHomePath(input.partialPath));
}
if (!input.cwd) {
return null;
}
const expandedCwd = yield* expandHomePath(input.cwd);
return input.path.resolve(expandedCwd, input.partialPath);
});
}

function rejectUpgrade(socket: Duplex, statusCode: number, message: string): void {
socket.end(
`HTTP/1.1 ${statusCode} ${statusCode === 401 ? "Unauthorized" : "Bad Request"}\r\n` +
Expand Down Expand Up @@ -878,6 +896,66 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
return yield* terminalManager.close(body);
}

case WS_METHODS.filesystemBrowse: {
const body = stripRequestTag(request.body);
if (process.platform !== "win32" && isWindowsAbsolutePath(body.partialPath)) {
return yield* new RouteRequestError({
message: "Windows-style paths are only supported on Windows.",
});
}
const resolvedInputPath = yield* resolveFilesystemBrowseInputPath({
cwd: body.cwd,
path,
partialPath: body.partialPath,
});
if (resolvedInputPath === null) {
return yield* new RouteRequestError({
message: "Relative filesystem browse paths require a current project.",
});
}

const expanded = resolvedInputPath;
const endsWithSep = /[\\/]$/.test(body.partialPath) || body.partialPath === "~";
const parentDir = endsWithSep ? expanded : path.dirname(expanded);
const prefix = endsWithSep ? "" : path.basename(expanded);

const names = yield* fileSystem.readDirectory(parentDir).pipe(
Effect.mapError(
(cause) =>
new RouteRequestError({
message: `Unable to browse '${parentDir}': ${Cause.pretty(Cause.fail(cause)).trim()}`,
}),
),
);

const showHidden = endsWithSep || prefix.startsWith(".");
const lowerPrefix = prefix.toLowerCase();
const filtered = names
.filter(
(name) =>
name.toLowerCase().startsWith(lowerPrefix) && (showHidden || !name.startsWith(".")),
)
.toSorted((left, right) => left.localeCompare(right));

const entries = yield* Effect.forEach(
filtered,
(name) =>
fileSystem.stat(path.join(parentDir, name)).pipe(
Effect.match({
onFailure: () => null,
onSuccess: (s) =>
s.type === "Directory" ? { name, fullPath: path.join(parentDir, name) } : null,
}),
),
{ concurrency: 16 },
);

return {
parentPath: parentDir,
entries: entries.filter(Boolean),
};
}

case WS_METHODS.serverGetConfig:
const keybindingsConfig = yield* keybindingsManager.loadConfigState;
return {
Expand Down
Loading
Loading