Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.ts (1 of 2 files). Show all
Unified · Split
| @@ -1,4 +1,3 @@ | |||
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 2 | /** | 1 | /** |
| 3 | * WorkspaceFileSystem - Effect service contract for workspace file mutations. | 2 | * WorkspaceFileSystem - Effect service contract for workspace file mutations. |
| 4 | * | 3 | * |
| @@ -7,8 +6,6 @@ · 2 unmodified lines | |||
| 7 | * | 6 | * |
| 8 | * @module WorkspaceFileSystem | 7 | * @module WorkspaceFileSystem |
| 9 | */ | 8 | */ |
| 10 | import * as NodeFSP from "node:fs/promises"; | ||
| 11 | |||
| 12 | import type { | 9 | import type { |
| 13 | ProjectReadFileInput, | 10 | ProjectReadFileInput, |
| 14 | ProjectReadFileResult, | 11 | ProjectReadFileResult, |
| @@ -86,11 +83,11 @@ export const make = Effect.gen(function* () { · 71 unmodified lines | |||
| 86 | relativePath: input.relativePath, | 83 | relativePath: input.relativePath, |
| 87 | }); | 84 | }); |
| 88 | 85 | ||
| 89 | return yield* Effect.tryPromise({ | 86 | return yield* Effect.scoped( |
| 90 | try: async () => { | 87 | Effect.gen(function* () { |
| 91 | const [realWorkspaceRoot, realTargetPath] = await Promise.all([ | 88 | const [realWorkspaceRoot, realTargetPath] = yield* Effect.all([ |
| 92 | NodeFSP.realpath(input.cwd), | 89 | fileSystem.realPath(input.cwd), |
| 93 | NodeFSP.realpath(target.absolutePath), | 90 | fileSystem.realPath(target.absolutePath), |
| 94 | ]); | 91 | ]); |
| 95 | const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath); | 92 | const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath); |
| 96 | if ( | 93 | if ( |
| @@ -98,41 +95,43 @@ export const make = Effect.gen(function* () { · 1 unmodified line | |||
| 98 | relativeRealPath === ".." || | 95 | relativeRealPath === ".." || |
| 99 | path.isAbsolute(relativeRealPath) | 96 | path.isAbsolute(relativeRealPath) |
| 100 | ) { | 97 | ) { |
| 101 | throw new Error("Workspace file path resolves outside the project root."); | 98 | return yield* Effect.fail( |
| 99 | new Error("Workspace file path resolves outside the project root."), | ||
| 100 | ); | ||
| 102 | } | 101 | } |
| 103 | 102 | ||
| 104 | const handle = await NodeFSP.open(realTargetPath, "r"); | 103 | const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }); |
| 105 | try { | 104 | const stat = yield* handle.stat; |
| 106 | const stat = await handle.stat(); | 105 | if (stat.type !== "File") { |
| 107 | if (!stat.isFile()) { | 106 | return yield* Effect.fail(new Error("Workspace path is not a file.")); |
| 108 | throw new Error("Workspace path is not a file."); | ||
| 109 | } | ||
| 110 | const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES); | ||
| 111 | const buffer = Buffer.alloc(bytesToRead); | ||
| 112 | const { bytesRead } = await handle.read(buffer, 0, bytesToRead, 0); | ||
| 113 | const fileBytes = buffer.subarray(0, bytesRead); | ||
| 114 | if (fileBytes.includes(0)) { | ||
| 115 | throw new Error("Binary files cannot be previewed as text."); | ||
| 116 | } | ||
| 117 | const contents = new TextDecoder("utf-8").decode(fileBytes); | ||
| 118 | return { | ||
| 119 | relativePath: target.relativePath, | ||
| 120 | contents, | ||
| 121 | byteLength: stat.size, | ||
| 122 | truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES, | ||
| 123 | }; | ||
| 124 | } finally { | ||
| 125 | await handle.close(); | ||
| 126 | } | 107 | } |
| 127 | }, | 108 | const byteLength = Number(stat.size); |
| 128 | catch: (cause) => | 109 | const bytesToRead = Math.min(byteLength, PROJECT_READ_FILE_MAX_BYTES); |
| 129 | new WorkspaceFileSystemError({ | 110 | const buffer = new Uint8Array(bytesToRead); |
| 130 | cwd: input.cwd, | 111 | const bytesRead = yield* handle.read(buffer); |
| 131 | relativePath: input.relativePath, | 112 | const fileBytes = buffer.subarray(0, Number(bytesRead)); |
| 132 | operation: "workspaceFileSystem.readFile", | 113 | if (fileBytes.includes(0)) { |
| 133 | cause, | 114 | return yield* Effect.fail(new Error("Binary files cannot be previewed as text.")); |
| 134 | }), | 115 | } |
| 135 | }); | 116 | const contents = new TextDecoder("utf-8").decode(fileBytes); |
| 117 | return { | ||
| 118 | relativePath: target.relativePath, | ||
| 119 | contents, | ||
| 120 | byteLength, | ||
| 121 | truncated: stat.size > BigInt(PROJECT_READ_FILE_MAX_BYTES), | ||
| 122 | }; | ||
| 123 | }), | ||
| 124 | ).pipe( | ||
| 125 | Effect.mapError( | ||
| 126 | (cause) => | ||
| 127 | new WorkspaceFileSystemError({ | ||
| 128 | cwd: input.cwd, | ||
| 129 | relativePath: input.relativePath, | ||
| 130 | operation: "workspaceFileSystem.readFile", | ||
| 131 | cause, | ||
| 132 | }), | ||
| 133 | ), | ||
| 134 | ); | ||
| 136 | }); | 135 | }); |
| 137 | 136 | ||
| 138 | const writeFile: WorkspaceFileSystem["Service"]["writeFile"] = Effect.fn( | 137 | const writeFile: WorkspaceFileSystem["Service"]["writeFile"] = Effect.fn( |