From 8c4d76f7eb874f092435017026f70e71bc350d29 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 25 May 2026 16:05:51 +0000 Subject: [PATCH 1/2] Use idiomatic Effect config parsing Co-authored-by: Julius Marminge --- .../src/diagnostics/ProcessResourceMonitor.ts | 6 ++- apps/server/src/vcs/VcsProjectConfig.test.ts | 20 +++++++++ apps/server/src/vcs/VcsProjectConfig.ts | 45 +++++++------------ 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/apps/server/src/diagnostics/ProcessResourceMonitor.ts b/apps/server/src/diagnostics/ProcessResourceMonitor.ts index 2b6dfe8d362..804bf101fca 100644 --- a/apps/server/src/diagnostics/ProcessResourceMonitor.ts +++ b/apps/server/src/diagnostics/ProcessResourceMonitor.ts @@ -6,10 +6,12 @@ import type { } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; +import * as Schedule from "effect/Schedule"; import { ChildProcessSpawner } from "effect/unstable/process"; import { @@ -20,6 +22,7 @@ import { } from "./ProcessDiagnostics.ts"; const SAMPLE_INTERVAL_MS = 5_000; +const SAMPLE_INTERVAL = Duration.millis(SAMPLE_INTERVAL_MS); const RETENTION_MS = 60 * 60_000; const MAX_RETAINED_SAMPLES = 20_000; @@ -274,7 +277,8 @@ export const make = Effect.fn("makeProcessResourceMonitor")(function* () { ), ); - yield* Effect.forever(sampleOnce.pipe(Effect.andThen(Effect.sleep(SAMPLE_INTERVAL_MS)))).pipe( + yield* sampleOnce.pipe( + Effect.repeat(Schedule.spaced(SAMPLE_INTERVAL)), Effect.forkScoped, ); diff --git a/apps/server/src/vcs/VcsProjectConfig.test.ts b/apps/server/src/vcs/VcsProjectConfig.test.ts index aac4beb7e32..476a5ab5cf1 100644 --- a/apps/server/src/vcs/VcsProjectConfig.test.ts +++ b/apps/server/src/vcs/VcsProjectConfig.test.ts @@ -67,4 +67,24 @@ describe("VcsProjectConfig", () => { }), ); }); + + it.layer(TestLayer)("falls back to auto when config is invalid", (it) => { + it.effect("returns auto", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-vcs-config-test-", + }); + const configDir = path.join(root, ".t3code"); + yield* fileSystem.makeDirectory(configDir, { recursive: true }); + yield* fileSystem.writeFileString(path.join(configDir, "vcs.json"), "{"); + + const config = yield* VcsProjectConfig.VcsProjectConfig; + const kind = yield* config.resolveKind({ cwd: root }); + + assert.equal(kind, "auto"); + }), + ); + }); }); diff --git a/apps/server/src/vcs/VcsProjectConfig.ts b/apps/server/src/vcs/VcsProjectConfig.ts index 3e5ee2347ce..0779a706b70 100644 --- a/apps/server/src/vcs/VcsProjectConfig.ts +++ b/apps/server/src/vcs/VcsProjectConfig.ts @@ -2,6 +2,7 @@ import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; @@ -15,16 +16,9 @@ const ProjectVcsConfig = Schema.Struct({ ), vcsKind: Schema.optional(VcsDriverKind), }); -const isProjectVcsConfig = Schema.is(ProjectVcsConfig); +type ProjectVcsConfigFile = Schema.Schema.Type; -interface ProjectVcsConfigFile { - readonly vcs?: - | { - readonly kind?: VcsDriverKindType | undefined; - } - | undefined; - readonly vcsKind?: VcsDriverKindType | undefined; -} +const decodeProjectVcsConfig = Schema.decodeUnknownEffect(Schema.fromJsonString(ProjectVcsConfig)); export interface VcsProjectConfigResolveInput { readonly cwd: string; @@ -45,15 +39,6 @@ function configuredKind(config: ProjectVcsConfigFile): VcsDriverKindType | "auto return config.vcs?.kind ?? config.vcsKind ?? "auto"; } -function parseConfig(raw: string): ProjectVcsConfigFile | null { - try { - const parsed = JSON.parse(raw) as unknown; - return isProjectVcsConfig(parsed) ? parsed : null; - } catch { - return null; - } -} - export const make = Effect.fn("makeVcsProjectConfig")(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -63,12 +48,12 @@ export const make = Effect.fn("makeVcsProjectConfig")(function* () { while (true) { const candidate = path.join(current, ".t3code", "vcs.json"); if (yield* fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false))) { - return candidate; + return Option.some(candidate); } const parent = path.dirname(current); if (parent === current) { - return null; + return Option.none(); } current = parent; } @@ -78,26 +63,27 @@ export const make = Effect.fn("makeVcsProjectConfig")(function* () { configPath: string, ) { const raw = yield* fileSystem.readFileString(configPath).pipe( + Effect.map(Option.some), Effect.catch((error) => Effect.logWarning("failed to read VCS project config", { configPath, error, - }).pipe(Effect.as(null)), + }).pipe(Effect.as(Option.none())), ), ); - if (raw === null) { + if (Option.isNone(raw)) { return "auto" as const; } - const parsed = parseConfig(raw); - if (parsed === null) { + const parsed = yield* decodeProjectVcsConfig(raw.value).pipe(Effect.option); + if (Option.isNone(parsed)) { yield* Effect.logWarning("invalid VCS project config", { configPath, }); return "auto" as const; } - return configuredKind(parsed); + return configuredKind(parsed.value); }); const resolveKind: VcsProjectConfigShape["resolveKind"] = Effect.fn( @@ -108,11 +94,10 @@ export const make = Effect.fn("makeVcsProjectConfig")(function* () { } const configPath = yield* findConfigPath(input.cwd); - if (configPath === null) { - return "auto"; - } - - return yield* readConfiguredKind(configPath); + return yield* Option.match(configPath, { + onNone: () => Effect.succeed("auto" as const), + onSome: readConfiguredKind, + }); }); return VcsProjectConfig.of({ From 4419ba0e0ca438194ff0bc8a0a46545be200e516 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 25 May 2026 16:08:14 +0000 Subject: [PATCH 2/2] Format diagnostics sampler schedule Co-authored-by: Julius Marminge --- apps/server/src/diagnostics/ProcessResourceMonitor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/server/src/diagnostics/ProcessResourceMonitor.ts b/apps/server/src/diagnostics/ProcessResourceMonitor.ts index 804bf101fca..bbf48c2d336 100644 --- a/apps/server/src/diagnostics/ProcessResourceMonitor.ts +++ b/apps/server/src/diagnostics/ProcessResourceMonitor.ts @@ -277,10 +277,7 @@ export const make = Effect.fn("makeProcessResourceMonitor")(function* () { ), ); - yield* sampleOnce.pipe( - Effect.repeat(Schedule.spaced(SAMPLE_INTERVAL)), - Effect.forkScoped, - ); + yield* sampleOnce.pipe(Effect.repeat(Schedule.spaced(SAMPLE_INTERVAL)), Effect.forkScoped); const readHistory: ProcessResourceMonitorShape["readHistory"] = (input) => Effect.gen(function* () {