-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathVcsProjectConfig.ts
More file actions
108 lines (91 loc) · 3.25 KB
/
VcsProjectConfig.ts
File metadata and controls
108 lines (91 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { VcsDriverKind, type VcsDriverKind as VcsDriverKindType } from "@t3tools/contracts";
const ProjectVcsConfig = Schema.Struct({
vcs: Schema.optional(
Schema.Struct({
kind: Schema.optional(VcsDriverKind),
}),
),
vcsKind: Schema.optional(VcsDriverKind),
});
type ProjectVcsConfigFile = Schema.Schema.Type<typeof ProjectVcsConfig>;
const decodeProjectVcsConfig = Schema.decodeUnknownEffect(Schema.fromJsonString(ProjectVcsConfig));
export interface VcsProjectConfigResolveInput {
readonly cwd: string;
readonly requestedKind?: VcsDriverKindType | "auto";
}
export interface VcsProjectConfigShape {
readonly resolveKind: (
input: VcsProjectConfigResolveInput,
) => Effect.Effect<VcsDriverKindType | "auto">;
}
export class VcsProjectConfig extends Context.Service<VcsProjectConfig, VcsProjectConfigShape>()(
"t3/vcs/VcsProjectConfig",
) {}
function configuredKind(config: ProjectVcsConfigFile): VcsDriverKindType | "auto" {
return config.vcs?.kind ?? config.vcsKind ?? "auto";
}
export const make = Effect.fn("makeVcsProjectConfig")(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const findConfigPath = Effect.fn("VcsProjectConfig.findConfigPath")(function* (cwd: string) {
let current = cwd;
while (true) {
const candidate = path.join(current, ".t3code", "vcs.json");
if (yield* fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false))) {
return Option.some(candidate);
}
const parent = path.dirname(current);
if (parent === current) {
return Option.none<string>();
}
current = parent;
}
});
const readConfiguredKind = Effect.fn("VcsProjectConfig.readConfiguredKind")(function* (
configPath: string,
) {
const raw = yield* fileSystem.readFileString(configPath).pipe(
Effect.map(Option.some),
Effect.catch((error) =>
Effect.logWarning("failed to read VCS project config", {
configPath,
error,
}).pipe(Effect.as(Option.none<string>())),
),
);
if (Option.isNone(raw)) {
return "auto" as const;
}
const parsed = yield* decodeProjectVcsConfig(raw.value).pipe(Effect.option);
if (Option.isNone(parsed)) {
yield* Effect.logWarning("invalid VCS project config", {
configPath,
});
return "auto" as const;
}
return configuredKind(parsed.value);
});
const resolveKind: VcsProjectConfigShape["resolveKind"] = Effect.fn(
"VcsProjectConfig.resolveKind",
)(function* (input) {
if (input.requestedKind !== undefined && input.requestedKind !== "auto") {
return input.requestedKind;
}
const configPath = yield* findConfigPath(input.cwd);
return yield* Option.match(configPath, {
onNone: () => Effect.succeed("auto" as const),
onSome: readConfiguredKind,
});
});
return VcsProjectConfig.of({
resolveKind,
});
});
export const layer = Layer.effect(VcsProjectConfig, make());