authorgravatar for yordis.prieto@gmail.comYordis Prieto <yordis.prieto@gmail.com> 2026-10-07 16:43:24-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-07 13:43:24-07:00
log85489611b5477050d7d777a4195c1c50ac0ce6be
tree049072fa1dc6b9d47e6ec1c834b5bc0d74d072d6
parent538329603352173afe3ae9f4586a4f772c0f3a4f
signature Signed by PGP key B5690EEEBB952194

fix(server): missing project folders no longer log favicon warnings (#16757)

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>

3 files changed, 68 insertions(+), 0 deletions(-)

apps/server/src/project/ProjectEnrichmentService.test.ts+55
......@@ -3,10 +3,13 @@ import type { RepositoryIdentity } from "@t3tools/contracts";
33import * as Deferred from "effect/Deferred";
44import * as Effect from "effect/Effect";
55import * as Layer from "effect/Layer";
6import * as Logger from "effect/Logger";
67import * as PubSub from "effect/PubSub";
78import * as Ref from "effect/Ref";
89import { TestClock } from "effect/testing";
910
11import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
12
1013import * as ProjectEnrichment from "./ProjectEnrichmentService.ts";
1114import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";
1215import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts";
......@@ -95,6 +98,58 @@ it.effect("preserves either enrichment field when the other resolver fails", ()
9598 }),
9699);
97100
101it.effect("does not warn about favicons for workspace roots that no longer exist", () =>
102 Effect.gen(function* () {
103 const warnings: Array<unknown> = [];
104 const logger = Logger.make(({ message }) => {
105 warnings.push(message);
106 });
107 const metadataLayer = Layer.merge(
108 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
109 resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)),
110 }),
111 Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {
112 resolvePath: (workspaceRoot) =>
113 Effect.fail(
114 workspaceRoot === "/missing"
115 ? new ProjectFaviconResolver.ProjectFaviconResolutionError({
116 operation: "normalize-workspace",
117 workspaceRoot,
118 cause: new WorkspacePaths.WorkspaceRootNotExistsError({
119 workspaceRoot,
120 normalizedWorkspaceRoot: workspaceRoot,
121 }),
122 })
123 : new ProjectFaviconResolver.ProjectFaviconResolutionError({
124 operation: "stat-candidate",
125 workspaceRoot,
126 cause: "favicon resolver failed",
127 }),
128 ),
129 }),
130 );
131
132 yield* Effect.gen(function* () {
133 const service = yield* ProjectEnrichment.ProjectEnrichmentService;
134 yield* service.request("/missing");
135 yield* service.request("/broken");
136
137 for (let attempt = 0; attempt < 100 && warnings.length === 0; attempt += 1) {
138 yield* Effect.yieldNow;
139 }
140
141 assert.lengthOf(warnings, 1);
142 assert.nestedPropertyVal(warnings[0], "[1].workspaceRoot", "/broken");
143 }).pipe(
144 Effect.provide(
145 layer(metadataLayer, { concurrency: 1 }).pipe(
146 Layer.provide(Logger.layer([logger], { mergeWithExisting: false })),
147 ),
148 ),
149 );
150 }),
151);
152
98153it.effect("publishes repository completion while favicon enrichment is still pending", () =>
99154 Effect.gen(function* () {
100155 const releaseFavicon = yield* Deferred.make<void>();
apps/server/src/project/ProjectEnrichmentService.ts+7
......@@ -192,6 +192,13 @@ export const make = Effect.fn("ProjectEnrichmentService.make")(function* (
192192 workspaceRoot: string,
193193 ) {
194194 const faviconPath = yield* Cache.get(faviconCache, workspaceRoot);
195 const isMissingWorkspaceRoot = Option.exists(
196 Exit.findErrorOption(faviconPath),
197 ProjectFaviconResolver.isMissingWorkspaceRoot,
198 );
199 if (isMissingWorkspaceRoot) {
200 return;
201 }
195202 yield* logFailure(workspaceRoot, "faviconPath", faviconPath);
196203 });
197204
apps/server/src/project/ProjectFaviconResolver.ts+6
......@@ -111,6 +111,12 @@ export class ProjectFaviconResolutionError extends Schema.TaggedError<ProjectFav
111111 }
112112}
113113
114const isWorkspaceRootNotExistsError = Schema.is(WorkspacePaths.WorkspaceRootNotExistsError);
115
116/** True when the workspace root itself is gone, e.g. a moved or deleted checkout. */
117export const isMissingWorkspaceRoot = (error: ProjectFaviconResolutionError): boolean =>
118 error.operation === "normalize-workspace" && isWorkspaceRootNotExistsError(error.cause);
119
114120/** Service tag for project favicon resolution. */
115121export class ProjectFaviconResolver extends Context.Service<
116122 ProjectFaviconResolver,