History

Add workspace file browser and preview panel (#3087)

Julius Marminge committed 4 months agov0.0.28-nightly.20260615.565

Commit de8bdc11 parent 477795662 files · +2850 −9551Browse files

Showing the changes to apps/server/src/workspace/Layers/WorkspaceFileSystem.ts (1 of 62 files). Show all

Unified · Split

apps/server/src/workspace/Layers/WorkspaceFileSystem.ts+66−1
@@ -1,3 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off
2import fsPromises from "node:fs/promises";
3
1import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
2import * as FileSystem from "effect/FileSystem";5import * as FileSystem from "effect/FileSystem";
3import * as Layer from "effect/Layer";6import * as Layer from "effect/Layer";
@@ -11,12 +14,74 @@ import { · 7 unmodified lines
11import { WorkspaceEntries } from "../Services/WorkspaceEntries.ts";14import { WorkspaceEntries } from "../Services/WorkspaceEntries.ts";
12import { WorkspacePaths } from "../Services/WorkspacePaths.ts";15import { WorkspacePaths } from "../Services/WorkspacePaths.ts";
13 16
17const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;
18
14export const makeWorkspaceFileSystem = Effect.gen(function* () {19export const makeWorkspaceFileSystem = Effect.gen(function* () {
15 const fileSystem = yield* FileSystem.FileSystem;20 const fileSystem = yield* FileSystem.FileSystem;
16 const path = yield* Path.Path;21 const path = yield* Path.Path;
17 const workspacePaths = yield* WorkspacePaths;22 const workspacePaths = yield* WorkspacePaths;
18 const workspaceEntries = yield* WorkspaceEntries;23 const workspaceEntries = yield* WorkspaceEntries;
19 24
25 const readFile: WorkspaceFileSystemShape["readFile"] = Effect.fn("WorkspaceFileSystem.readFile")(
26 function* (input) {
27 const target = yield* workspacePaths.resolveRelativePathWithinRoot({
28 workspaceRoot: input.cwd,
29 relativePath: input.relativePath,
30 });
31
32 const result = yield* Effect.tryPromise({
33 try: async () => {
34 const [realWorkspaceRoot, realTargetPath] = await Promise.all([
35 fsPromises.realpath(input.cwd),
36 fsPromises.realpath(target.absolutePath),
37 ]);
38 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
39 if (
40 relativeRealPath.startsWith(`..${path.sep}`) ||
41 relativeRealPath === ".." ||
42 path.isAbsolute(relativeRealPath)
43 ) {
44 throw new Error("Workspace file path resolves outside the project root.");
45 }
46
47 const handle = await fsPromises.open(realTargetPath, "r");
48 try {
49 const stat = await handle.stat();
50 if (!stat.isFile()) {
51 throw new Error("Workspace path is not a file.");
52 }
53 const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES);
54 const buffer = Buffer.alloc(bytesToRead);
55 const { bytesRead } = await handle.read(buffer, 0, bytesToRead, 0);
56 const fileBytes = buffer.subarray(0, bytesRead);
57 if (fileBytes.includes(0)) {
58 throw new Error("Binary files cannot be previewed as text.");
59 }
60 const contents = new TextDecoder("utf-8").decode(fileBytes);
61 return {
62 relativePath: target.relativePath,
63 contents,
64 byteLength: stat.size,
65 truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES,
66 };
67 } finally {
68 await handle.close();
69 }
70 },
71 catch: (cause) =>
72 new WorkspaceFileSystemError({
73 cwd: input.cwd,
74 relativePath: input.relativePath,
75 operation: "workspaceFileSystem.readFile",
76 detail: cause instanceof Error ? cause.message : String(cause),
77 cause,
78 }),
79 });
80
81 return result;
82 },
83 );
84
20 const writeFile: WorkspaceFileSystemShape["writeFile"] = Effect.fn(85 const writeFile: WorkspaceFileSystemShape["writeFile"] = Effect.fn(
21 "WorkspaceFileSystem.writeFile",86 "WorkspaceFileSystem.writeFile",
22 )(function* (input) {87 )(function* (input) {
@@ -52,7 +117,7 @@ export const makeWorkspaceFileSystem = Effect.gen(function* () { · 29 unmodified lines
52 yield* workspaceEntries.invalidate(input.cwd);117 yield* workspaceEntries.invalidate(input.cwd);
53 return { relativePath: target.relativePath };118 return { relativePath: target.relativePath };
54 });119 });
55 return { writeFile } satisfies WorkspaceFileSystemShape;120 return { readFile, writeFile } satisfies WorkspaceFileSystemShape;
56});121});
57 122
58export const WorkspaceFileSystemLive = Layer.effect(WorkspaceFileSystem, makeWorkspaceFileSystem);123export const WorkspaceFileSystemLive = Layer.effect(WorkspaceFileSystem, makeWorkspaceFileSystem);