Skip to content
Source
Sign inSign up

ReviewService.ts

154 lines5.2 KB

1import * as Context from "effect/Context";
2import * as DateTime from "effect/DateTime";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Path from "effect/Path";
7​
8import {
9 VcsRepositoryDetectionError,
10 VcsUnsupportedOperationError,
11 type ReviewBranchCommitsInput,
12 type ReviewBranchCommitsResult,
13 type ReviewDiffFileContentsInput,
14 type ReviewDiffFileContentsResult,
15 type ReviewDiffPreviewError,
16 type ReviewDiffPreviewInput,
17 type ReviewDiffPreviewResult,
18} from "@t3tools/contracts";
19​
20import * as ServerConfig from "../config.ts";
21import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
22import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
23import * as KnownWorkspaces from "../workspace/KnownWorkspaces.ts";
24​
25const WORKSPACE_BOUND_SUBJECTS = {
26 "ReviewService.getBranchCommits": "commit list",
27 "ReviewService.getDiffPreview": "diff preview",
28 "ReviewService.getDiffFileContents": "diff file contents",
29} as const;
30​
31export class ReviewService extends Context.Service<
32 ReviewService,
33 {
34 readonly getBranchCommits: (
35 input: ReviewBranchCommitsInput,
36 ) => Effect.Effect<ReviewBranchCommitsResult, ReviewDiffPreviewError>;
37 readonly getDiffPreview: (
38 input: ReviewDiffPreviewInput,
39 ) => Effect.Effect<ReviewDiffPreviewResult, ReviewDiffPreviewError>;
40 readonly getDiffFileContents: (
41 input: ReviewDiffFileContentsInput,
42 ) => Effect.Effect<ReviewDiffFileContentsResult, ReviewDiffPreviewError>;
43 }
44>()("t3/review/ReviewService") {}
45​
46/** @public */
47export const make = Effect.gen(function* () {
48 const config = yield* ServerConfig.ServerConfig;
49 const fileSystem = yield* FileSystem.FileSystem;
50 const path = yield* Path.Path;
51 const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry;
52 const git = yield* GitVcsDriver.GitVcsDriver;
53 const knownWorkspaces = yield* KnownWorkspaces.KnownWorkspaces;
54​
55 const canonicalizePath = (value: string) => {
56 const resolvedPath = path.resolve(value);
57 return fileSystem.realPath(resolvedPath).pipe(
58 Effect.catchTags({
59 PlatformError: (cause) =>
60 cause.reason._tag === "NotFound"
61 ? Effect.succeed(resolvedPath)
62 : Effect.fail(
63 new VcsRepositoryDetectionError({
64 operation: "ReviewService.assertWorkspaceBoundCwd.canonicalizePath",
65 cwd: resolvedPath,
66 detail: "Failed to resolve a path while validating the review workspace.",
67 cause,
68 }),
69 ),
70 }),
71 );
72 };
73​
74 const assertWorkspaceBoundCwd = Effect.fn("ReviewService.assertWorkspaceBoundCwd")(function* (
75 operation: keyof typeof WORKSPACE_BOUND_SUBJECTS,
76 cwd: string,
77 ) {
78 const [candidate, workspaceRoot] = yield* Effect.all([
79 canonicalizePath(cwd),
80 canonicalizePath(config.cwd),
81 ]);
82 if (
83 KnownWorkspaces.isWithinRoot(path, candidate, workspaceRoot) ||
84 (yield* knownWorkspaces.contains(candidate))
85 ) {
86 return;
87 }
88​
89 return yield* new VcsRepositoryDetectionError({
90 operation,
91 cwd,
92 detail: `Review ${WORKSPACE_BOUND_SUBJECTS[operation]} cwd must stay within the configured workspace root.`,
93 });
94 });
95​
96 const getBranchCommits: ReviewService["Service"]["getBranchCommits"] = Effect.fn(
97 "ReviewService.getBranchCommits",
98 )(function* (input) {
99 yield* assertWorkspaceBoundCwd("ReviewService.getBranchCommits", input.cwd);
100​
101 const handle = yield* vcsRegistry.detect({ cwd: input.cwd, requestedKind: "auto" });
102 if (!handle) return { commits: [], truncated: false };
103 if (handle.kind !== "git") {
104 return yield* new VcsUnsupportedOperationError({
105 operation: "ReviewService.getBranchCommits",
106 kind: handle.kind,
107 detail: "Review commit lists require a Git repository.",
108 });
109 }
110 return yield* git.getReviewBranchCommits(input);
111 });
112​
113 const getDiffPreview: ReviewService["Service"]["getDiffPreview"] = Effect.fn(
114 "ReviewService.getDiffPreview",
115 )(function* (input) {
116 yield* assertWorkspaceBoundCwd("ReviewService.getDiffPreview", input.cwd);
117​
118 const handle = yield* vcsRegistry.detect({ cwd: input.cwd, requestedKind: "auto" });
119 if (!handle) {
120 return {
121 cwd: input.cwd,
122 generatedAt: yield* DateTime.now,
123 sources: [],
124 };
125 }
126​
127 return yield* git.getReviewDiffPreview(input);
128 });
129​
130 const getDiffFileContents: ReviewService["Service"]["getDiffFileContents"] = Effect.fn(
131 "ReviewService.getDiffFileContents",
132 )(function* (input) {
133 yield* assertWorkspaceBoundCwd("ReviewService.getDiffFileContents", input.cwd);
134​
135 const handle = yield* vcsRegistry.detect({ cwd: input.cwd, requestedKind: "auto" });
136 if (handle?.kind !== "git") {
137 return yield* new VcsUnsupportedOperationError({
138 operation: "ReviewService.getDiffFileContents",
139 kind: handle?.kind ?? "unknown",
140 detail: "Unchanged diff expansion currently requires a Git repository.",
141 });
142 }
143​
144 return yield* git.getReviewDiffFileContents(input);
145 });
146​
147 return ReviewService.of({
148 getBranchCommits,
149 getDiffPreview,
150 getDiffFileContents,
151 });
152});
153​
154export const layer = Layer.effect(ReviewService, make);