Repository navigation
Expand file tree
/
Copy pathKnownWorkspaces.ts
More file actions
150 lines (138 loc) · 6.27 KB
/
Copy pathKnownWorkspaces.ts
File metadata and controls
150 lines (138 loc) · 6.27 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Client-supplied worktrees count only under managed folders or when Git lists them for the project.
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
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 Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as SqlClient from "effect/sql/SqlClient";
import * as ServerConfig from "../config.ts";
import * as ProjectStore from "../orchestration-v2/ProjectStore.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import { isFilesystemRoot, managedWorktreesDirectories } from "../worktreesDirectory.ts";
export class UnknownWorkspaceError extends Schema.TaggedError<UnknownWorkspaceError>()(
"UnknownWorkspaceError",
{
cwd: Schema.String,
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
return `'${this.cwd}' is not a registered project, a managed worktree, or a thread's worktree.`;
}
}
/** Resolve candidate and root consistently before checking containment. */
export const isWithinRoot = (path: Path.Path, candidate: string, root: string) => {
const relative = path.relative(root, candidate);
return (
relative === "" ||
(relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
);
};
export class KnownWorkspaces extends Context.Service<
KnownWorkspaces,
{
/** Takes a symlink-resolved path. */
readonly contains: (realPath: string) => Effect.Effect<boolean>;
/** Fails unless `cwd` exists and resolves inside a known workspace. */
readonly assertContains: (cwd: string) => Effect.Effect<void, UnknownWorkspaceError>;
}
>()("t3/workspace/KnownWorkspaces") {}
/** @public */
export const make = Effect.gen(function* () {
const config = yield* ServerConfig.ServerConfig;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const settings = yield* ServerSettings.ServerSettingsService;
const projectStore = yield* ProjectStore.ProjectStoreV2;
const sql = yield* SqlClient.SqlClient;
const git = yield* GitVcsDriver.GitVcsDriver;
// Missing roots keep lexical paths; other resolution failures and filesystem roots grant nothing.
const resolveRoot = (value: string) => {
const resolvedPath = path.resolve(value);
return fileSystem.realPath(resolvedPath).pipe(
Effect.catchTags({
PlatformError: (cause) =>
Effect.succeed(cause.reason._tag === "NotFound" ? resolvedPath : null),
}),
Effect.map((root) => (root === null || isFilesystemRoot(root, path) ? null : root)),
);
};
const contains: KnownWorkspaces["Service"]["contains"] = Effect.fn("KnownWorkspaces.contains")(
function* (realPath) {
const within = (root: string | null) => root !== null && isWithinRoot(path, realPath, root);
const worktreeSettings = yield* settings.getSettings.pipe(
Effect.orElseSucceed(() => ({ worktreesDirectory: "", previousWorktreesDirectories: [] })),
);
const home = yield* HostProcess.HomeDirectory;
const managedDirectories = managedWorktreesDirectories(
worktreeSettings,
config.worktreesDir,
path,
home,
);
const managed = (yield* Effect.forEach(managedDirectories, resolveRoot)).filter(
(root) => root !== null,
);
if (managed.some(within)) return true;
const projects = yield* projectStore.listShells().pipe(Effect.orElseSucceed(() => []));
for (const project of projects) {
if (within(yield* resolveRoot(project.workspaceRoot))) return true;
}
const threads = yield* sql<{ readonly worktreePath: string; readonly projectId: string }>`
SELECT DISTINCT
json_extract(payload_json, '$.worktreePath') AS "worktreePath",
project_id AS "projectId"
FROM orchestration_v2_projection_threads
WHERE deleted_at IS NULL AND json_extract(payload_json, '$.worktreePath') IS NOT NULL
`.pipe(
NodeSqliteClient.readOnly,
Effect.orElseSucceed(() => []),
);
const projectWorktrees = new Map<string, ReadonlyArray<string>>();
const isProjectWorktree = Effect.fnUntraced(function* (
projectId: string,
worktreePath: string,
) {
const project = projects.find((candidate) => candidate.id === projectId);
if (project === undefined) return false;
let listed = projectWorktrees.get(projectId);
if (listed === undefined) {
listed = yield* git.listWorktreePaths(project.workspaceRoot).pipe(
Effect.flatMap((paths) => Effect.forEach(paths, resolveRoot)),
Effect.map((paths) => paths.filter((listedPath) => listedPath !== null)),
Effect.orElseSucceed((): ReadonlyArray<string> => []),
);
projectWorktrees.set(projectId, listed);
}
const real = yield* resolveRoot(worktreePath);
return real !== null && listed.includes(real);
});
for (const { worktreePath, projectId } of threads) {
// Relative paths use server cwd; managed paths still need resolution to reject symlinks elsewhere.
if (!path.isAbsolute(worktreePath)) continue;
if (!within(yield* resolveRoot(worktreePath))) continue;
const lexical = path.resolve(worktreePath);
if (managedDirectories.some((directory) => isWithinRoot(path, lexical, directory))) {
return true;
}
if (yield* isProjectWorktree(projectId, worktreePath)) return true;
}
return false;
},
);
const assertContains: KnownWorkspaces["Service"]["assertContains"] = Effect.fn(
"KnownWorkspaces.assertContains",
)(function* (cwd) {
// `..` collapses on the path as named before symlinks resolve, as the write does.
const realCwd = yield* fileSystem
.realPath(path.resolve(cwd))
.pipe(Effect.mapError((cause) => new UnknownWorkspaceError({ cwd, cause })));
if (!(yield* contains(realCwd))) return yield* new UnknownWorkspaceError({ cwd });
});
return KnownWorkspaces.of({ contains, assertContains });
});
export const layer = Layer.effect(KnownWorkspaces, make);