1import * as NodeModule from "node:module";
2
3import * as HostProcess from "@t3tools/shared/HostProcess";
4import * as Context from "effect/Context";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import * as Schema from "effect/Schema";
8
9export class CheckoutCloneError extends Schema.TaggedError<CheckoutCloneError>()(
10 "CheckoutCloneError",
11 {
12 source: Schema.String,
13 destination: Schema.String,
14 errno: Schema.optional(Schema.Number),
15 cause: Schema.optional(Schema.Defect()),
16 },
17) {
18 override get message(): string {
19 return `Could not clone ${this.source} to ${this.destination}${this.errno === undefined ? "" : ` (errno ${this.errno})`}.`;
20 }
21}
22
23/**
24 * Copies a file or a whole folder copy-on-write in one call, sharing data blocks until either
25 * copy changes. The destination must not exist. Undefined where the platform has no such call.
26 */
27export const CheckoutCloner = Context.Reference<
28 ((source: string, destination: string) => Effect.Effect<void, CheckoutCloneError>) | undefined
29>("t3/vcs/CheckoutCloner", { defaultValue: () => undefined });
30
31// A single-executable server resolves packages on disk only through `require`.
32const requireForFfi = NodeModule.createRequire(import.meta.url);
33
34/** macOS `clonefile(2)`, which clones a folder hierarchy in the kernel; elsewhere nothing. */
35export const layer = Layer.effect(
36 CheckoutCloner,
37 Effect.gen(function* () {
38 if ((yield* HostProcess.Platform) !== "darwin") return undefined;
39 const loadClonefile = yield* Effect.cached(
40 Effect.try({
41 try: () => {
42 const { DataType, load, open } = requireForFfi("ffi-rs") as typeof import("ffi-rs");
43 open({ library: "t3-libsystem", path: "/usr/lib/libSystem.B.dylib" });
44 return (source: string, destination: string) =>
45 load({
46 library: "t3-libsystem",
47 funcName: "clonefile",
48 retType: DataType.I32,
49 paramsType: [DataType.String, DataType.String, DataType.I32],
50 // CLONE_NOFOLLOW: clone a symlink itself rather than its target.
51 paramsValue: [source, destination, 0x0001],
52 errno: true,
53 // A large folder takes hundreds of milliseconds; keep it off the event loop.
54 runInNewThread: true,
55 });
56 },
57 catch: (cause) =>
58 new CheckoutCloneError({ source: "ffi-rs", destination: "clonefile", cause }),
59 }),
60 );
61 return Effect.fn("CheckoutCloner.clonefile")(function* (source: string, destination: string) {
62 const clonefile = yield* loadClonefile.pipe(
63 Effect.mapError(
64 (error) => new CheckoutCloneError({ source, destination, cause: error.cause }),
65 ),
66 );
67 const result = yield* Effect.tryPromise({
68 try: () => clonefile(source, destination),
69 catch: (cause) => new CheckoutCloneError({ source, destination, cause }),
70 });
71 // ffi-rs types the errno form as a union with the plain return value.
72 if (typeof result === "number" || result.value !== 0) {
73 return yield* new CheckoutCloneError({
74 source,
75 destination,
76 ...(typeof result === "number" ? {} : { errno: result.errnoCode }),
77 });
78 }
79 });
80 }),
81);