import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer"; import { GitManagerError, GitCommandError, type VcsSwitchRefInput, type VcsSwitchRefResult, type VcsCreateRefInput, type VcsCreateRefResult, type VcsCreateWorktreeInput, type VcsCreateWorktreeResult, type VcsListRefsInput, type VcsListRefsResult, type GitManagerServiceError, type GitPreparePullRequestThreadInput, type GitPreparePullRequestThreadResult, type GitPullRequestRefInput, type VcsPullResult, type VcsRemoveWorktreeInput, type GitResolvePullRequestResult, type GitRunStackedActionInput, type GitRunStackedActionResult, type VcsStatusInput, type VcsStatusLocalResult, type VcsStatusRemoteResult, type VcsStatusResult,} from "@t3tools/contracts"; import * as GitManager from "./GitManager.ts";import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; export class GitWorkflowService extends Context.Service< GitWorkflowService, { readonly status: ( input: VcsStatusInput, ) => Effect.Effect<VcsStatusResult, GitManagerServiceError>; readonly localStatus: ( input: VcsStatusInput, ) => Effect.Effect<VcsStatusLocalResult, GitManagerServiceError>; readonly remoteStatus: ( input: VcsStatusInput, options?: GitManager.GitRemoteStatusOptions, ) => Effect.Effect<VcsStatusRemoteResult | null, GitManagerServiceError>; readonly invalidateLocalStatus: (cwd: string) => Effect.Effect<void, never>; readonly invalidateRemoteStatus: (cwd: string) => Effect.Effect<void, never>; readonly invalidateStatus: (cwd: string) => Effect.Effect<void, never>; readonly pullCurrentBranch: (cwd: string) => Effect.Effect<VcsPullResult, GitCommandError>; readonly runStackedAction: ( input: GitRunStackedActionInput, options?: GitManager.GitRunStackedActionOptions, ) => Effect.Effect<GitRunStackedActionResult, GitManagerServiceError>; readonly resolvePullRequest: ( input: GitPullRequestRefInput, ) => Effect.Effect<GitResolvePullRequestResult, GitManagerServiceError>; readonly preparePullRequestThread: ( input: GitPreparePullRequestThreadInput, ) => Effect.Effect<GitPreparePullRequestThreadResult, GitManagerServiceError>; readonly listRefs: ( input: VcsListRefsInput, ) => Effect.Effect<VcsListRefsResult, GitCommandError>; readonly createWorktree: ( input: VcsCreateWorktreeInput, ) => Effect.Effect<VcsCreateWorktreeResult, GitCommandError>; readonly fetchRemote: (input: { readonly cwd: string; readonly remoteName: string; }) => Effect.Effect<void, GitCommandError>; readonly remoteExists: (input: { readonly cwd: string; readonly remoteName: string; }) => Effect.Effect<boolean, GitCommandError>; readonly remoteBranchExists: (input: { readonly cwd: string; readonly remoteName: string; readonly refName: string; }) => Effect.Effect<boolean, GitCommandError>; readonly resolveRemoteTrackingCommit: (input: { readonly cwd: string; readonly refName: string; readonly fallbackRemoteName: string; }) => Effect.Effect< { readonly commitSha: string; readonly remoteRefName: string }, GitCommandError >; readonly removeWorktree: ( input: VcsRemoveWorktreeInput, ) => Effect.Effect<void, GitCommandError>; readonly pruneWorktrees: (input: { readonly cwd: string; }) => Effect.Effect<void, GitCommandError>; readonly createRef: ( input: VcsCreateRefInput, ) => Effect.Effect<VcsCreateRefResult, GitCommandError>; readonly switchRef: ( input: VcsSwitchRefInput, ) => Effect.Effect<VcsSwitchRefResult, GitCommandError>; readonly renameBranch: (input: { readonly cwd: string; readonly oldBranch: string; readonly newBranch: string; }) => Effect.Effect<{ readonly branch: string }, GitManagerServiceError>; }>()("t3/git/GitWorkflowService") {} function nonRepositoryLocalStatus(): VcsStatusLocalResult { return { isRepo: false, hasPrimaryRemote: false, isDefaultRef: false, refName: null, hasWorkingTreeChanges: false, workingTree: { files: [], insertions: 0, deletions: 0, }, };} function nonRepositoryStatus(): VcsStatusResult { return { ...nonRepositoryLocalStatus(), hasUpstream: false, aheadCount: 0, behindCount: 0, aheadOfDefaultCount: 0, pr: null, };} function nonRepositoryListRefs(): VcsListRefsResult { return { refs: [], isRepo: false, hasPrimaryRemote: false, nextCursor: null, totalCount: 0, };} /** @public Service construction is part of the canonical Effect module API. */export const make = Effect.gen(function* () { const registry = yield* VcsDriverRegistry.VcsDriverRegistry; const git = yield* GitVcsDriver.GitVcsDriver; const gitManager = yield* GitManager.GitManager; const ensureGit = Effect.fn("GitWorkflowService.ensureGit")(function* ( operation: string, cwd: string, ) { const handle = yield* registry.resolve({ cwd }).pipe( Effect.mapError( (cause) => new GitManagerError({ operation, cwd, detail: "Failed to resolve the VCS driver for this Git workflow.", cause, }), ), ); if (handle.kind !== "git") { return yield* new GitManagerError({ operation, cwd, detail: `The ${operation} workflow currently supports Git repositories only; detected ${handle.kind}. (${cwd})`, }); } }); const ensureGitCommand = Effect.fn("GitWorkflowService.ensureGitCommand")(function* ( operation: string, cwd: string, ) { const handle = yield* registry.resolve({ cwd }).pipe( Effect.mapError( (cause) => new GitCommandError({ operation, command: "vcs-route", cwd, detail: "Failed to resolve the VCS driver for this Git command.", cause, }), ), ); if (handle.kind !== "git") { return yield* new GitCommandError({ operation, command: "vcs-route", cwd, detail: `The ${operation} command currently supports Git repositories only; detected ${handle.kind}.`, }); } }); const detectGitRepositoryForStatus = Effect.fn("GitWorkflowService.detectGitRepositoryForStatus")( function* (operation: string, cwd: string) { const handle = yield* registry.detect({ cwd }).pipe( Effect.mapError( (cause) => new GitManagerError({ operation, cwd, detail: "Failed to detect a VCS repository for this Git workflow.", cause, }), ), ); if (!handle) { return false; } if (handle.kind !== "git") { return yield* new GitManagerError({ operation, cwd, detail: `The ${operation} workflow currently supports Git repositories only; detected ${handle.kind}. (${cwd})`, }); } return true; }, ); const detectGitRepositoryForCommand = Effect.fn( "GitWorkflowService.detectGitRepositoryForCommand", )(function* (operation: string, cwd: string) { const handle = yield* registry.detect({ cwd }).pipe( Effect.mapError( (cause) => new GitCommandError({ operation, command: "vcs-route", cwd, detail: "Failed to detect a VCS repository for this Git command.", cause, }), ), ); if (!handle) { return false; } if (handle.kind !== "git") { return yield* new GitCommandError({ operation, command: "vcs-route", cwd, detail: `The ${operation} command currently supports Git repositories only; detected ${handle.kind}.`, }); } return true; }); const routeGitManager = <Input extends { readonly cwd: string }, Output>( operation: string, run: (input: Input) => Effect.Effect<Output, GitManagerServiceError>, ) => (input: Input) => ensureGit(operation, input.cwd).pipe(Effect.andThen(run(input))); return GitWorkflowService.of({ status: (input) => detectGitRepositoryForStatus("GitWorkflowService.status", input.cwd).pipe( Effect.flatMap((isGitRepository) => isGitRepository ? gitManager.status(input) : Effect.succeed(nonRepositoryStatus()), ), ), localStatus: (input) => detectGitRepositoryForStatus("GitWorkflowService.localStatus", input.cwd).pipe( Effect.flatMap((isGitRepository) => isGitRepository ? gitManager.localStatus(input) : Effect.succeed(nonRepositoryLocalStatus()), ), ), remoteStatus: (input, options) => detectGitRepositoryForStatus("GitWorkflowService.remoteStatus", input.cwd).pipe( Effect.flatMap((isGitRepository) => isGitRepository ? gitManager.remoteStatus(input, options) : Effect.succeed(null), ), ), invalidateLocalStatus: gitManager.invalidateLocalStatus, invalidateRemoteStatus: gitManager.invalidateRemoteStatus, invalidateStatus: gitManager.invalidateStatus, pullCurrentBranch: (cwd) => ensureGitCommand("GitWorkflowService.pullCurrentBranch", cwd).pipe( Effect.andThen(git.pullCurrentBranch(cwd)), ), runStackedAction: (input, options) => ensureGit("GitWorkflowService.runStackedAction", input.cwd).pipe( Effect.andThen(gitManager.runStackedAction(input, options)), ), resolvePullRequest: routeGitManager( "GitWorkflowService.resolvePullRequest", gitManager.resolvePullRequest, ), preparePullRequestThread: routeGitManager( "GitWorkflowService.preparePullRequestThread", gitManager.preparePullRequestThread, ), listRefs: (input) => detectGitRepositoryForCommand("GitWorkflowService.listRefs", input.cwd).pipe( Effect.flatMap((isGitRepository) => isGitRepository ? git.listRefs(input) : Effect.succeed(nonRepositoryListRefs()), ), ), createWorktree: (input) => ensureGitCommand("GitWorkflowService.createWorktree", input.cwd).pipe( Effect.andThen(git.createWorktree(input)), ), fetchRemote: (input) => ensureGitCommand("GitWorkflowService.fetchRemote", input.cwd).pipe( Effect.andThen(git.fetchRemote(input)), ), remoteExists: (input) => ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe( Effect.andThen(git.remoteExists(input)), ), remoteBranchExists: (input) => ensureGitCommand("GitWorkflowService.remoteBranchExists", input.cwd).pipe( Effect.andThen(git.remoteBranchExists(input)), ), resolveRemoteTrackingCommit: (input) => ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe( Effect.andThen(git.resolveRemoteTrackingCommit(input)), ), removeWorktree: (input) => ensureGitCommand("GitWorkflowService.removeWorktree", input.cwd).pipe( Effect.andThen(git.removeWorktree(input)), ), pruneWorktrees: (input) => ensureGitCommand("GitWorkflowService.pruneWorktrees", input.cwd).pipe( Effect.andThen(git.pruneWorktrees(input)), ), createRef: (input) => ensureGitCommand("GitWorkflowService.createRef", input.cwd).pipe( Effect.andThen(git.createRef(input)), ), switchRef: (input) => ensureGitCommand("GitWorkflowService.switchRef", input.cwd).pipe( Effect.andThen(Effect.scoped(git.switchRef(input))), ), renameBranch: (input) => ensureGit("GitWorkflowService.renameBranch", input.cwd).pipe( Effect.andThen(git.renameBranch(input)), ), });}); export const layer = Layer.effect(GitWorkflowService, make);
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import {
GitManagerError,
GitCommandError,
type VcsSwitchRefInput,
type VcsSwitchRefResult,
type VcsCreateRefInput,
type VcsCreateRefResult,
type VcsCreateWorktreeInput,
type VcsCreateWorktreeResult,
type VcsListRefsInput,
type VcsListRefsResult,
type GitManagerServiceError,
type GitPreparePullRequestThreadInput,
type GitPreparePullRequestThreadResult,
type GitPullRequestRefInput,
type VcsPullResult,
type VcsRemoveWorktreeInput,
type GitResolvePullRequestResult,
type GitRunStackedActionInput,
type GitRunStackedActionResult,
type VcsStatusInput,
type VcsStatusLocalResult,
type VcsStatusRemoteResult,
type VcsStatusResult,
} from "@t3tools/contracts";
import * as GitManager from "./GitManager.ts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
export class GitWorkflowService extends Context.Service<
GitWorkflowService,
{
readonly status: (
input: VcsStatusInput,
) => Effect.Effect<VcsStatusResult, GitManagerServiceError>;
readonly localStatus: (
input: VcsStatusInput,
) => Effect.Effect<VcsStatusLocalResult, GitManagerServiceError>;
readonly remoteStatus: (
input: VcsStatusInput,
options?: GitManager.GitRemoteStatusOptions,
) => Effect.Effect<VcsStatusRemoteResult | null, GitManagerServiceError>;
readonly invalidateLocalStatus: (cwd: string) => Effect.Effect<void, never>;
readonly invalidateRemoteStatus: (cwd: string) => Effect.Effect<void, never>;
readonly invalidateStatus: (cwd: string) => Effect.Effect<void, never>;
readonly pullCurrentBranch: (cwd: string) => Effect.Effect<VcsPullResult, GitCommandError>;
readonly runStackedAction: (
input: GitRunStackedActionInput,
options?: GitManager.GitRunStackedActionOptions,
) => Effect.Effect<GitRunStackedActionResult, GitManagerServiceError>;
readonly resolvePullRequest: (
input: GitPullRequestRefInput,
) => Effect.Effect<GitResolvePullRequestResult, GitManagerServiceError>;
readonly preparePullRequestThread: (
input: GitPreparePullRequestThreadInput,
) => Effect.Effect<GitPreparePullRequestThreadResult, GitManagerServiceError>;
readonly listRefs: (
input: VcsListRefsInput,
) => Effect.Effect<VcsListRefsResult, GitCommandError>;
readonly createWorktree: (
input: VcsCreateWorktreeInput,
) => Effect.Effect<VcsCreateWorktreeResult, GitCommandError>;
readonly fetchRemote: (input: {
readonly cwd: string;
readonly remoteName: string;
}) => Effect.Effect<void, GitCommandError>;
readonly remoteExists: (input: {
readonly cwd: string;
readonly remoteName: string;
}) => Effect.Effect<boolean, GitCommandError>;
readonly remoteBranchExists: (input: {
readonly cwd: string;
readonly remoteName: string;
readonly refName: string;
}) => Effect.Effect<boolean, GitCommandError>;
readonly resolveRemoteTrackingCommit: (input: {
readonly cwd: string;
readonly refName: string;
readonly fallbackRemoteName: string;
}) => Effect.Effect<
{ readonly commitSha: string; readonly remoteRefName: string },
GitCommandError
>;
readonly removeWorktree: (
input: VcsRemoveWorktreeInput,
) => Effect.Effect<void, GitCommandError>;
readonly pruneWorktrees: (input: {
readonly cwd: string;
}) => Effect.Effect<void, GitCommandError>;
readonly createRef: (
input: VcsCreateRefInput,
) => Effect.Effect<VcsCreateRefResult, GitCommandError>;
readonly switchRef: (
input: VcsSwitchRefInput,
) => Effect.Effect<VcsSwitchRefResult, GitCommandError>;
readonly renameBranch: (input: {
readonly cwd: string;
readonly oldBranch: string;
readonly newBranch: string;
}) => Effect.Effect<{ readonly branch: string }, GitManagerServiceError>;
}
>()("t3/git/GitWorkflowService") {}
function nonRepositoryLocalStatus(): VcsStatusLocalResult {
return {
isRepo: false,
hasPrimaryRemote: false,
isDefaultRef: false,
refName: null,
hasWorkingTreeChanges: false,
workingTree: {
files: [],
insertions: 0,
deletions: 0,
},
};
}
function nonRepositoryStatus(): VcsStatusResult {
return {
...nonRepositoryLocalStatus(),
hasUpstream: false,
aheadCount: 0,
behindCount: 0,
aheadOfDefaultCount: 0,
pr: null,
};
}
function nonRepositoryListRefs(): VcsListRefsResult {
return {
refs: [],
isRepo: false,
hasPrimaryRemote: false,
nextCursor: null,
totalCount: 0,
};
}
/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const registry = yield* VcsDriverRegistry.VcsDriverRegistry;
const git = yield* GitVcsDriver.GitVcsDriver;
const gitManager = yield* GitManager.GitManager;
const ensureGit = Effect.fn("GitWorkflowService.ensureGit")(function* (
operation: string,
cwd: string,
) {
const handle = yield* registry.resolve({ cwd }).pipe(
Effect.mapError(
(cause) =>
new GitManagerError({
operation,
cwd,
detail: "Failed to resolve the VCS driver for this Git workflow.",
cause,
}),
),
);
if (handle.kind !== "git") {
return yield* new GitManagerError({
operation,
cwd,
detail: `The ${operation} workflow currently supports Git repositories only; detected ${handle.kind}. (${cwd})`,
});
}
});
const ensureGitCommand = Effect.fn("GitWorkflowService.ensureGitCommand")(function* (
operation: string,
cwd: string,
) {
const handle = yield* registry.resolve({ cwd }).pipe(
Effect.mapError(
(cause) =>
new GitCommandError({
operation,
command: "vcs-route",
cwd,
detail: "Failed to resolve the VCS driver for this Git command.",
cause,
}),
),
);
if (handle.kind !== "git") {
return yield* new GitCommandError({
operation,
command: "vcs-route",
cwd,
detail: `The ${operation} command currently supports Git repositories only; detected ${handle.kind}.`,
});
}
});
const detectGitRepositoryForStatus = Effect.fn("GitWorkflowService.detectGitRepositoryForStatus")(
function* (operation: string, cwd: string) {
const handle = yield* registry.detect({ cwd }).pipe(
Effect.mapError(
(cause) =>
new GitManagerError({
operation,
cwd,
detail: "Failed to detect a VCS repository for this Git workflow.",
cause,
}),
),
);
if (!handle) {
return false;
}
if (handle.kind !== "git") {
return yield* new GitManagerError({
operation,
cwd,
detail: `The ${operation} workflow currently supports Git repositories only; detected ${handle.kind}. (${cwd})`,
});
}
return true;
},
);
const detectGitRepositoryForCommand = Effect.fn(
"GitWorkflowService.detectGitRepositoryForCommand",
)(function* (operation: string, cwd: string) {
const handle = yield* registry.detect({ cwd }).pipe(
Effect.mapError(
(cause) =>
new GitCommandError({
operation,
command: "vcs-route",
cwd,
detail: "Failed to detect a VCS repository for this Git command.",
cause,
}),
),
);
if (!handle) {
return false;
}
if (handle.kind !== "git") {
return yield* new GitCommandError({
operation,
command: "vcs-route",
cwd,
detail: `The ${operation} command currently supports Git repositories only; detected ${handle.kind}.`,
});
}
return true;
});
const routeGitManager =
<Input extends { readonly cwd: string }, Output>(
operation: string,
run: (input: Input) => Effect.Effect<Output, GitManagerServiceError>,
) =>
(input: Input) =>
ensureGit(operation, input.cwd).pipe(Effect.andThen(run(input)));
return GitWorkflowService.of({
status: (input) =>
detectGitRepositoryForStatus("GitWorkflowService.status", input.cwd).pipe(
Effect.flatMap((isGitRepository) =>
isGitRepository ? gitManager.status(input) : Effect.succeed(nonRepositoryStatus()),
),
),
localStatus: (input) =>
detectGitRepositoryForStatus("GitWorkflowService.localStatus", input.cwd).pipe(
Effect.flatMap((isGitRepository) =>
isGitRepository
? gitManager.localStatus(input)
: Effect.succeed(nonRepositoryLocalStatus()),
),
),
remoteStatus: (input, options) =>
detectGitRepositoryForStatus("GitWorkflowService.remoteStatus", input.cwd).pipe(
Effect.flatMap((isGitRepository) =>
isGitRepository ? gitManager.remoteStatus(input, options) : Effect.succeed(null),
),
),
invalidateLocalStatus: gitManager.invalidateLocalStatus,
invalidateRemoteStatus: gitManager.invalidateRemoteStatus,
invalidateStatus: gitManager.invalidateStatus,
pullCurrentBranch: (cwd) =>
ensureGitCommand("GitWorkflowService.pullCurrentBranch", cwd).pipe(
Effect.andThen(git.pullCurrentBranch(cwd)),
),
runStackedAction: (input, options) =>
ensureGit("GitWorkflowService.runStackedAction", input.cwd).pipe(
Effect.andThen(gitManager.runStackedAction(input, options)),
),
resolvePullRequest: routeGitManager(
"GitWorkflowService.resolvePullRequest",
gitManager.resolvePullRequest,
),
preparePullRequestThread: routeGitManager(
"GitWorkflowService.preparePullRequestThread",
gitManager.preparePullRequestThread,
),
listRefs: (input) =>
detectGitRepositoryForCommand("GitWorkflowService.listRefs", input.cwd).pipe(
Effect.flatMap((isGitRepository) =>
isGitRepository ? git.listRefs(input) : Effect.succeed(nonRepositoryListRefs()),
),
),
createWorktree: (input) =>
ensureGitCommand("GitWorkflowService.createWorktree", input.cwd).pipe(
Effect.andThen(git.createWorktree(input)),
),
fetchRemote: (input) =>
ensureGitCommand("GitWorkflowService.fetchRemote", input.cwd).pipe(
Effect.andThen(git.fetchRemote(input)),
),
remoteExists: (input) =>
ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe(
Effect.andThen(git.remoteExists(input)),
),
remoteBranchExists: (input) =>
ensureGitCommand("GitWorkflowService.remoteBranchExists", input.cwd).pipe(
Effect.andThen(git.remoteBranchExists(input)),
),
resolveRemoteTrackingCommit: (input) =>
ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe(
Effect.andThen(git.resolveRemoteTrackingCommit(input)),
),
removeWorktree: (input) =>
ensureGitCommand("GitWorkflowService.removeWorktree", input.cwd).pipe(
Effect.andThen(git.removeWorktree(input)),
),
pruneWorktrees: (input) =>
ensureGitCommand("GitWorkflowService.pruneWorktrees", input.cwd).pipe(
Effect.andThen(git.pruneWorktrees(input)),
),
createRef: (input) =>
ensureGitCommand("GitWorkflowService.createRef", input.cwd).pipe(
Effect.andThen(git.createRef(input)),
),
switchRef: (input) =>
ensureGitCommand("GitWorkflowService.switchRef", input.cwd).pipe(
Effect.andThen(Effect.scoped(git.switchRef(input))),
),
renameBranch: (input) =>
ensureGit("GitWorkflowService.renameBranch", input.cwd).pipe(
Effect.andThen(git.renameBranch(input)),
),
});
});
export const layer = Layer.effect(GitWorkflowService, make);