From 0d149a2d92a4a204d1dd0eba3ae4adc8eb8a6041 Mon Sep 17 00:00:00 2001 From: Nathan Harmon Date: Thu, 16 Apr 2026 15:20:27 -0400 Subject: [PATCH] fix(server): honor gitignored files in workspace search --- apps/server/src/server.test.ts | 91 ++++++++++++++++++++++++++++------ apps/server/src/server.ts | 17 +++++-- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index b2c16abb427..35af55cd26d 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -166,16 +166,6 @@ const makeDefaultOrchestrationReadModel = () => { }; }; -const workspaceAndProjectServicesLayer = Layer.mergeAll( - WorkspacePathsLive, - WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive)), - WorkspaceFileSystemLive.pipe( - Layer.provide(WorkspacePathsLive), - Layer.provide(WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive))), - ), - ProjectFaviconResolverLive, -); - const browserOtlpTracingLayer = Layer.mergeAll( FetchHttpClient.layer, OtlpSerialization.layerJson, @@ -343,9 +333,32 @@ const buildAppUnderTest = (options?: { ...options?.config, }; const layerConfig = Layer.succeed(ServerConfig, config); + const gitCoreLayer = Layer.mock(GitCore)({ + isInsideWorkTree: () => Effect.succeed(false), + listWorkspaceFiles: () => + Effect.succeed({ + paths: [], + truncated: false, + }), + filterIgnoredPaths: (_cwd, relativePaths) => Effect.succeed(relativePaths), + ...options?.layers?.gitCore, + }); const gitManagerLayer = Layer.mock(GitManager)({ ...options?.layers?.gitManager, }); + const workspaceEntriesLayer = WorkspaceEntriesLive.pipe( + Layer.provide(WorkspacePathsLive), + Layer.provideMerge(gitCoreLayer), + ); + const workspaceAndProjectServicesLayer = Layer.mergeAll( + WorkspacePathsLive, + workspaceEntriesLayer, + WorkspaceFileSystemLive.pipe( + Layer.provide(WorkspacePathsLive), + Layer.provide(workspaceEntriesLayer), + ), + ProjectFaviconResolverLive, + ); const gitStatusBroadcasterLayer = options?.layers?.gitStatusBroadcaster ? Layer.mock(GitStatusBroadcaster)({ ...options.layers.gitStatusBroadcaster, @@ -389,11 +402,7 @@ const buildAppUnderTest = (options?: { ...options?.layers?.open, }), ), - Layer.provide( - Layer.mock(GitCore)({ - ...options?.layers?.gitCore, - }), - ), + Layer.provide(gitCoreLayer), Layer.provide(gitManagerLayer), Layer.provideMerge(gitStatusBroadcasterLayer), Layer.provide( @@ -1990,6 +1999,58 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("routes websocket rpc projects.searchEntries excludes gitignored files", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const workspaceDir = yield* fs.makeTempDirectoryScoped({ + prefix: "t3-ws-project-search-gitignored-", + }); + yield* fs.writeFileString(path.join(workspaceDir, ".gitignore"), ".venv/\n"); + yield* fs.makeDirectory(path.join(workspaceDir, ".venv", "lib"), { recursive: true }); + yield* fs.writeFileString( + path.join(workspaceDir, ".venv", "lib", "ignored-search-target.ts"), + "export const ignored = true;", + ); + yield* fs.makeDirectory(path.join(workspaceDir, "src"), { recursive: true }); + yield* fs.writeFileString( + path.join(workspaceDir, "src", "tracked.ts"), + "export const ok = 1;", + ); + + yield* buildAppUnderTest({ + layers: { + gitCore: { + isInsideWorkTree: () => Effect.succeed(true), + listWorkspaceFiles: () => + Effect.succeed({ + paths: ["src/tracked.ts"], + truncated: false, + }), + filterIgnoredPaths: (_cwd, relativePaths) => + Effect.succeed( + relativePaths.filter((relativePath) => !relativePath.startsWith(".venv/")), + ), + }, + }, + }); + + const wsUrl = yield* getWsServerUrl("/ws"); + const response = yield* Effect.scoped( + withWsRpcClient(wsUrl, (client) => + client[WS_METHODS.projectsSearchEntries]({ + cwd: workspaceDir, + query: "ignored-search-target", + limit: 10, + }), + ), + ); + + assert.equal(response.entries.length, 0); + assert.equal(response.truncated, false); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("routes websocket rpc projects.searchEntries errors", () => Effect.gen(function* () { yield* buildAppUnderTest(); diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 23c53ad07fd..36672d6deda 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -180,13 +180,20 @@ const GitLayerLive = Layer.empty.pipe( const TerminalLayerLive = TerminalManagerLive.pipe(Layer.provide(PtyAdapterLive)); +const WorkspaceEntriesLayerLive = WorkspaceEntriesLive.pipe( + Layer.provide(WorkspacePathsLive), + Layer.provideMerge(GitCoreLive), +); + +const WorkspaceFileSystemLayerLive = WorkspaceFileSystemLive.pipe( + Layer.provide(WorkspacePathsLive), + Layer.provide(WorkspaceEntriesLayerLive), +); + const WorkspaceLayerLive = Layer.mergeAll( WorkspacePathsLive, - WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive)), - WorkspaceFileSystemLive.pipe( - Layer.provide(WorkspacePathsLive), - Layer.provide(WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive))), - ), + WorkspaceEntriesLayerLive, + WorkspaceFileSystemLayerLive, ); const AuthLayerLive = ServerAuthLive.pipe(