1/**
2 * SourceControlHost — what a source control provider package may ask of the server it runs in.
3 *
4 * Providers run inside a T3 server but must not import it. The server provides this one
5 * service; everything a provider needs from its environment (settings, the process runner)
6 * goes through it, so a provider package depends only on `@t3tools/source-control-core` and
7 * its own API and CLI code. HTTP, the filesystem, and paths come from Effect's platform
8 * services directly.
9 *
10 * `git` is the subset of the server's git driver that checking out a change request needs; it
11 * grows only when a provider needs another operation.
12 *
13 * @module source-control-core/server/SourceControlHost
14 */
15import type {
16 GitCommandError,
17 ServerSettings,
18 ServerSettingsError,
19 VcsError,
20 VcsListRemotesResult,
21 VcsSwitchRefInput,
22 VcsSwitchRefResult,
23} from "@t3tools/contracts";
24import * as Context from "effect/Context";
25import type * as Effect from "effect/Effect";
26import type * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
27
28/** One CLI invocation. The server bounds concurrency, output, and time for every run. */
29export interface SourceControlProcessInput {
30 readonly operation: string;
31 readonly command: string;
32 readonly args: ReadonlyArray<string>;
33 readonly cwd: string;
34 readonly spawnCwd?: string;
35 readonly stdin?: string;
36 readonly onStdoutChunk?: (chunk: Uint8Array) => void;
37 readonly env?: NodeJS.ProcessEnv;
38 readonly allowNonZeroExit?: boolean;
39 readonly timeoutMs?: number;
40 readonly maxOutputBytes?: number;
41 /** What happens past `maxOutputBytes`: fail the run, or keep the first bytes. */
42 readonly outputMode?: "error" | "truncate" | undefined;
43 readonly appendTruncationMarker?: boolean;
44}
45
46export interface SourceControlProcessOutput {
47 readonly exitCode: ChildProcessSpawner.ExitCode;
48 readonly stdout: string;
49 readonly stderr: string;
50 readonly stdoutTruncated: boolean;
51 readonly stderrTruncated: boolean;
52 /** Present on real process output; optional so narrow test doubles remain lightweight. */
53 readonly stdoutInvalidUtf8?: boolean;
54 readonly stderrInvalidUtf8?: boolean;
55}
56
57export type SourceControlGitOutput = Omit<
58 SourceControlProcessOutput,
59 "stdoutInvalidUtf8" | "stderrInvalidUtf8"
60>;
61
62export interface SourceControlGitRemoteBranch {
63 readonly cwd: string;
64 readonly remoteName: string;
65 readonly remoteBranch: string;
66}
67
68export class SourceControlHost extends Context.Service<
69 SourceControlHost,
70 {
71 readonly settings: {
72 /** Read fresh on each call, so a credential saved in Settings applies without a restart. */
73 readonly get: Effect.Effect<ServerSettings, ServerSettingsError>;
74 };
75 readonly process: {
76 readonly run: (
77 input: SourceControlProcessInput,
78 ) => Effect.Effect<SourceControlProcessOutput, VcsError>;
79 };
80 readonly git: {
81 /** Runs `git -C cwd …args` through the server's git driver, with its limits and metrics. */
82 readonly execute: (input: {
83 readonly operation: string;
84 readonly cwd: string;
85 readonly args: ReadonlyArray<string>;
86 readonly maxOutputBytes?: number;
87 readonly appendTruncationMarker?: boolean;
88 }) => Effect.Effect<SourceControlGitOutput, GitCommandError>;
89 readonly resolveCommit: (input: {
90 readonly cwd: string;
91 readonly revision: string;
92 }) => Effect.Effect<{ readonly commitSha: string }, GitCommandError>;
93 /**
94 * Lists the remotes of the repository at `cwd`. The outer effect resolves the repository
95 * and fails when the server cannot drive one there; the inner one lists its remotes.
96 */
97 readonly remotes: (
98 cwd: string,
99 ) => Effect.Effect<Effect.Effect<VcsListRemotesResult, VcsError>, VcsError>;
100 readonly readConfigValue: (
101 cwd: string,
102 key: string,
103 ) => Effect.Effect<string | null, GitCommandError>;
104 readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;
105 /** Adds a remote for `url` unless one exists, and returns the name it is under. */
106 readonly ensureRemote: (input: {
107 readonly cwd: string;
108 readonly preferredName: string;
109 readonly url: string;
110 }) => Effect.Effect<string, GitCommandError>;
111 readonly listLocalBranchNames: (cwd: string) => Effect.Effect<string[], GitCommandError>;
112 readonly fetchRemoteBranch: (
113 input: SourceControlGitRemoteBranch & { readonly localBranch: string },
114 ) => Effect.Effect<void, GitCommandError>;
115 readonly fetchRemoteTrackingBranch: (
116 input: SourceControlGitRemoteBranch,
117 ) => Effect.Effect<void, GitCommandError>;
118 readonly setBranchUpstream: (
119 input: SourceControlGitRemoteBranch & { readonly branch: string },
120 ) => Effect.Effect<void, GitCommandError>;
121 readonly switchRef: (
122 input: VcsSwitchRefInput,
123 ) => Effect.Effect<VcsSwitchRefResult, GitCommandError>;
124 };
125 }
126>()("@t3tools/source-control-core/server/SourceControlHost") {}