@nodecg/types
Version:
Dynamic broadcast graphics rendered in a browser
103 lines (89 loc) • 3.08 kB
text/typescript
import nodeFs from "node:fs";
import { FileSystem, Path } from "@effect/platform";
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
import * as NodePath from "@effect/platform-node/NodePath";
import { Array, Data, DateTime, Effect, Match, Option } from "effect";
import git from "isomorphic-git";
export class GitBranchReadError extends Data.TaggedError("GitBranchReadError")<{
path: string;
cause: unknown;
}> {
override readonly message = `Failed to read git branch for repository at ${this.path}`;
}
export class GitHeadReadError extends Data.TaggedError("GitHeadReadError")<{
path: string;
cause: unknown;
}> {
override readonly message = `Failed to read git HEAD for repository at ${this.path}`;
}
export class GitDateParseError extends Data.TaggedError("GitDateParseError")<{
path: string;
timestamp: number;
}> {
override readonly message = `Failed to parse git commit date (timestamp: ${this.timestamp}) for repository at ${this.path}`;
}
export class GitService extends Effect.Service<GitService>()("GitService", {
effect: Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const getGitHead = Effect.fn("GitService.getGitHead")(function* (
bundlePath: string,
) {
const gitDir = path.join(bundlePath, ".git");
const hasGitDir = yield* fs.exists(gitDir);
if (!hasGitDir) {
return Option.none();
}
// currentBranch returns undefined for detached HEAD, null for errors
const branch = yield* Effect.tryPromise({
try: () => git.currentBranch({ fs: nodeFs, dir: bundlePath }),
catch: (cause) => new GitBranchReadError({ path: bundlePath, cause }),
}).pipe(
Effect.map((branch) =>
Match.value(branch).pipe(
Match.when(Match.string, (branch) => Option.some(branch)),
Match.orElse(() => Option.none()),
),
),
);
// Get HEAD commit
// git.log throws NotFoundError when there are no commits
const headCommit = yield* Effect.tryPromise({
try: async () =>
Array.head(await git.log({ fs: nodeFs, dir: bundlePath, depth: 1 })),
catch: (cause) => ({ cause }),
}).pipe(
Effect.catchIf(
({ cause }) => cause instanceof git.Errors.NotFoundError,
() => Effect.succeed(Option.none()),
),
Effect.mapError(
({ cause }) => new GitHeadReadError({ path: bundlePath, cause }),
),
);
if (Option.isNone(headCommit)) {
return Option.none();
}
const commit = headCommit.value;
const hash = commit.oid;
const shortHash = hash.slice(0, 7);
const timestamp = commit.commit.committer.timestamp;
const date = yield* DateTime.make(timestamp * 1000).pipe(
Option.match({
onSome: (dt) => Effect.succeed(DateTime.formatIso(dt)),
onNone: () => new GitDateParseError({ path: bundlePath, timestamp }),
}),
);
const message = commit.commit.message;
return Option.some({
hash,
shortHash,
date,
message,
branch,
});
});
return { getGitHead };
}),
dependencies: [NodeFileSystem.layer, NodePath.layer],
}) {}