singularity-forge/web/lib/git-summary-contract.ts
2026-05-05 14:31:16 +02:00

85 lines
2.2 KiB
TypeScript

export const GIT_SUMMARY_SCOPE = "current_project" as const;
export interface GitSummaryCounts {
changed: number;
staged: number;
dirty: number;
untracked: number;
conflicts: number;
}
export interface GitSummaryFile {
path: string;
repoPath: string;
status: string;
staged: boolean;
dirty: boolean;
untracked: boolean;
conflict: boolean;
}
export interface GitSummaryProjectScope {
scope: typeof GIT_SUMMARY_SCOPE;
cwd: string;
repoRoot: string | null;
repoRelativePath: string | null;
}
export interface GitSummaryRepoResponse {
kind: "repo";
project: GitSummaryProjectScope & {
repoRoot: string;
};
branch: string | null;
mainBranch: string | null;
hasChanges: boolean;
hasConflicts: boolean;
counts: GitSummaryCounts;
changedFiles: GitSummaryFile[];
truncatedFileCount: number;
}
export interface GitSummaryNotRepoResponse {
kind: "not_repo";
project: GitSummaryProjectScope;
message: string;
}
export type GitSummaryResponse =
| GitSummaryRepoResponse
| GitSummaryNotRepoResponse;
export function isGitSummaryResponse(
value: unknown,
): value is GitSummaryResponse {
if (!value || typeof value !== "object") return false;
const response = value as Partial<GitSummaryResponse>;
if (response.kind !== "repo" && response.kind !== "not_repo") return false;
if (!response.project || typeof response.project !== "object") return false;
if (response.project.scope !== GIT_SUMMARY_SCOPE) return false;
if (typeof response.project.cwd !== "string") return false;
if (response.kind === "not_repo") {
return typeof (response as GitSummaryNotRepoResponse).message === "string";
}
const repo = response as Partial<GitSummaryRepoResponse>;
if (typeof repo.project?.repoRoot !== "string") return false;
if (
typeof repo.hasChanges !== "boolean" ||
typeof repo.hasConflicts !== "boolean"
)
return false;
if (!repo.counts || typeof repo.counts !== "object") return false;
if (!Array.isArray(repo.changedFiles)) return false;
if (typeof repo.truncatedFileCount !== "number") return false;
return (
typeof repo.counts.changed === "number" &&
typeof repo.counts.staged === "number" &&
typeof repo.counts.dirty === "number" &&
typeof repo.counts.untracked === "number" &&
typeof repo.counts.conflicts === "number"
);
}