t3code-cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2 -2
- package/dist/index.js +1 -1
- package/dist/{layer-DIg0RxSO.js → layer-MqCKkIGD.js} +45 -56
- package/dist/orchestration.js +1 -1
- package/dist/runtime.js +1 -1
- package/dist/src/domain/helpers.d.ts +2 -3
- package/package.json +5 -2
- package/src/application/threads.ts +0 -2
- package/src/domain/helpers.test.ts +103 -0
- package/src/domain/helpers.ts +61 -85
package/dist/bin.js
CHANGED
|
@@ -8,7 +8,7 @@ import { C as make$12, S as Crypto, x as T3Auth } from "./layer-CfC5qZol.js";
|
|
|
8
8
|
import { n as T3Config, t as Environment } from "./service-CLmRO2Dp.js";
|
|
9
9
|
import { t as T3Application } from "./service-ybOWV9pL.js";
|
|
10
10
|
import { i as resolveWorktreePath, r as resolveThreadId, t as resolveCommandProjectRef } from "./scope-GycYiJ54.js";
|
|
11
|
-
import { d as latestAssistantMessage, f as threadStatus, m as ThreadSessionError, p as ProjectLookupError, t as AppLayer } from "./layer-
|
|
11
|
+
import { d as latestAssistantMessage, f as threadStatus, m as ThreadSessionError, p as ProjectLookupError, t as AppLayer } from "./layer-MqCKkIGD.js";
|
|
12
12
|
import { t as NodeEnvironmentLive } from "./layer-DHhKS5jd.js";
|
|
13
13
|
import * as NodeChildProcess from "node:child_process";
|
|
14
14
|
import * as NodeCrypto from "node:crypto";
|
|
@@ -4859,7 +4859,7 @@ var T3Version = class extends Service()("t3cli/T3Version") {};
|
|
|
4859
4859
|
//#endregion
|
|
4860
4860
|
//#region src/version/layer.ts
|
|
4861
4861
|
const PackageJsonSchema = fromJsonString(Struct({ version: String$1 }));
|
|
4862
|
-
const T3VersionBundledLive = sync$2(T3Version, () => ({ version: "0.
|
|
4862
|
+
const T3VersionBundledLive = sync$2(T3Version, () => ({ version: "0.7.0" }));
|
|
4863
4863
|
effect(T3Version, gen(function* () {
|
|
4864
4864
|
const packageJson = yield* (yield* FileSystem).readFileString(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
4865
4865
|
return { version: (yield* decodeUnknownEffect(PackageJsonSchema)(packageJson)).version };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as T3Application } from "./service-ybOWV9pL.js";
|
|
2
|
-
import { n as AuthAppLayer, t as AppLayer } from "./layer-
|
|
2
|
+
import { n as AuthAppLayer, t as AppLayer } from "./layer-MqCKkIGD.js";
|
|
3
3
|
import "./application.js";
|
|
4
4
|
import "./runtime.js";
|
|
5
5
|
export { AppLayer, AuthAppLayer, T3Application };
|
|
@@ -120,73 +120,68 @@ const makeModelsApplication = fn("makeModelsApplication")(function* () {
|
|
|
120
120
|
//#endregion
|
|
121
121
|
//#region src/domain/helpers.ts
|
|
122
122
|
const resolveProjectScope = fn("resolveProjectScope")(function* (snapshot, input) {
|
|
123
|
-
const
|
|
123
|
+
const path = yield* Path;
|
|
124
124
|
const byId = findProjectById(snapshot, input.ref);
|
|
125
125
|
if (byId !== null) return { project: byId };
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const byAncestor = yield* findProjectByAncestorPath(snapshot, absoluteRef, input.cwd);
|
|
129
|
-
if (byAncestor !== void 0) return byAncestor;
|
|
130
|
-
return yield* findProjectByKnownWorktreePath(snapshot, absoluteRef, input.cwd);
|
|
126
|
+
if (!path.isAbsolute(input.ref)) return;
|
|
127
|
+
return yield* findProjectByPathPriority(snapshot, path.normalize(input.ref));
|
|
131
128
|
});
|
|
132
129
|
function findProjectById(snapshot, projectId) {
|
|
133
130
|
return snapshot.projects.find((project) => project.id === projectId) ?? null;
|
|
134
131
|
}
|
|
135
|
-
const resolveAbsolutePath = fn("resolveAbsolutePath")(function* (ref, cwd) {
|
|
136
|
-
const path = yield* Path;
|
|
137
|
-
return path.isAbsolute(ref) ? path.normalize(ref) : path.normalize(path.resolve(cwd, ref));
|
|
138
|
-
});
|
|
139
132
|
const isDescendantPath = fn("isDescendantPath")(function* (parent, child) {
|
|
140
133
|
const path = yield* Path;
|
|
141
134
|
if (child === parent) return true;
|
|
142
135
|
const relative = path.relative(parent, child);
|
|
143
136
|
return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
144
137
|
});
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
const findProjectByAncestorPath = fn("findProjectByAncestorPath")(function* (snapshot, absolutePath, cwd) {
|
|
149
|
-
let bestProject;
|
|
150
|
-
let bestWorkspaceRoot = "";
|
|
151
|
-
for (const project of snapshot.projects) {
|
|
152
|
-
const workspaceRoot = yield* resolveAbsolutePath(project.workspaceRoot, cwd);
|
|
153
|
-
if (!(yield* isDescendantPath(workspaceRoot, absolutePath))) continue;
|
|
154
|
-
if (workspaceRoot.length > bestWorkspaceRoot.length) {
|
|
155
|
-
bestProject = project;
|
|
156
|
-
bestWorkspaceRoot = workspaceRoot;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (bestProject === void 0) return;
|
|
160
|
-
if (absolutePath === bestWorkspaceRoot) return { project: bestProject };
|
|
161
|
-
return {
|
|
162
|
-
project: bestProject,
|
|
163
|
-
inferredWorktreePath: absolutePath
|
|
164
|
-
};
|
|
165
|
-
});
|
|
166
|
-
const findProjectByKnownWorktreePath = fn("findProjectByKnownWorktreePath")(function* (snapshot, absolutePath, cwd) {
|
|
138
|
+
const findProjectByPathPriority = fn("findProjectByPathPriority")(function* (snapshot, absolutePath) {
|
|
139
|
+
const path = yield* Path;
|
|
167
140
|
const projectsById = new Map(snapshot.projects.map((project) => [project.id, project]));
|
|
168
|
-
|
|
169
|
-
|
|
141
|
+
const workspaceRoots = new Map(snapshot.projects.map((project) => [project.id, path.normalize(project.workspaceRoot)]));
|
|
142
|
+
const candidates = [];
|
|
170
143
|
for (const thread of snapshot.threads) {
|
|
171
144
|
if (thread.worktreePath === null) continue;
|
|
172
145
|
const project = projectsById.get(thread.projectId);
|
|
173
146
|
if (project === void 0) continue;
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
147
|
+
const workspaceRoot = workspaceRoots.get(thread.projectId);
|
|
148
|
+
if (workspaceRoot === void 0) continue;
|
|
149
|
+
const matchPath = path.normalize(thread.worktreePath);
|
|
150
|
+
if (!(yield* isDescendantPath(matchPath, absolutePath))) continue;
|
|
151
|
+
candidates.push({
|
|
152
|
+
project,
|
|
153
|
+
matchPath,
|
|
154
|
+
workspaceRoot,
|
|
155
|
+
source: "worktree"
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
for (const project of snapshot.projects) {
|
|
159
|
+
const workspaceRoot = workspaceRoots.get(project.id);
|
|
160
|
+
if (workspaceRoot === void 0) continue;
|
|
161
|
+
if (!(yield* isDescendantPath(workspaceRoot, absolutePath))) continue;
|
|
162
|
+
candidates.push({
|
|
163
|
+
project,
|
|
164
|
+
matchPath: workspaceRoot,
|
|
165
|
+
workspaceRoot,
|
|
166
|
+
source: "project"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (candidates.length === 0) return;
|
|
170
|
+
candidates.sort((left, right) => {
|
|
171
|
+
if (left.matchPath.length !== right.matchPath.length) return right.matchPath.length - left.matchPath.length;
|
|
172
|
+
if (left.source === right.source) return 0;
|
|
173
|
+
return left.source === "worktree" ? -1 : 1;
|
|
174
|
+
});
|
|
175
|
+
const best = candidates[0];
|
|
176
|
+
if (absolutePath === best.matchPath) {
|
|
177
|
+
if (best.source === "worktree" && best.matchPath !== best.workspaceRoot) return {
|
|
178
|
+
project: best.project,
|
|
179
|
+
inferredWorktreePath: best.matchPath
|
|
180
|
+
};
|
|
181
|
+
return { project: best.project };
|
|
180
182
|
}
|
|
181
|
-
if (bestProject === void 0) return;
|
|
182
|
-
const workspaceRoot = yield* resolveAbsolutePath(bestProject.workspaceRoot, cwd);
|
|
183
|
-
if (absolutePath === workspaceRoot) return { project: bestProject };
|
|
184
|
-
if (absolutePath === bestKnownPath) return bestKnownPath === workspaceRoot ? { project: bestProject } : {
|
|
185
|
-
project: bestProject,
|
|
186
|
-
inferredWorktreePath: bestKnownPath
|
|
187
|
-
};
|
|
188
183
|
return {
|
|
189
|
-
project:
|
|
184
|
+
project: best.project,
|
|
190
185
|
inferredWorktreePath: absolutePath
|
|
191
186
|
};
|
|
192
187
|
});
|
|
@@ -444,10 +439,7 @@ const makeThreadApplication = fn("makeThreadApplication")(function* () {
|
|
|
444
439
|
const environment = yield* Environment;
|
|
445
440
|
const listThreads = fn("T3ApplicationLive.listThreads")(function* (projectRef) {
|
|
446
441
|
const snapshot = yield* orchestration.getShellSnapshot();
|
|
447
|
-
const scope = yield* resolveProjectScope(snapshot, {
|
|
448
|
-
ref: projectRef,
|
|
449
|
-
cwd: environment.cwd
|
|
450
|
-
}).pipe(provideService(Path, path));
|
|
442
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: projectRef }).pipe(provideService(Path, path));
|
|
451
443
|
if (scope === void 0) return yield* fail(new ProjectLookupError({
|
|
452
444
|
message: `project not found: ${projectRef}`,
|
|
453
445
|
ref: projectRef
|
|
@@ -471,10 +463,7 @@ const makeThreadApplication = fn("makeThreadApplication")(function* () {
|
|
|
471
463
|
message: "project is required",
|
|
472
464
|
ref: environment.cwd
|
|
473
465
|
}));
|
|
474
|
-
const scope = yield* resolveProjectScope(snapshot, {
|
|
475
|
-
ref: projectRef,
|
|
476
|
-
cwd: environment.cwd
|
|
477
|
-
}).pipe(provideService(Path, path));
|
|
466
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: projectRef }).pipe(provideService(Path, path));
|
|
478
467
|
if (scope === void 0) return yield* fail(new ProjectLookupError({
|
|
479
468
|
message: `project not found: ${projectRef}`,
|
|
480
469
|
ref: projectRef
|
package/dist/orchestration.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { l as T3OrchestrationLayer, u as T3Orchestration } from "./layer-
|
|
1
|
+
import { l as T3OrchestrationLayer, u as T3Orchestration } from "./layer-MqCKkIGD.js";
|
|
2
2
|
export { T3Orchestration, T3OrchestrationLayer };
|
package/dist/runtime.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as T3AuthTransportLayer, c as T3LocalAuthTokenLayer, i as T3AuthPairingLayer, l as T3OrchestrationLayer, n as AuthAppLayer, o as T3LocalAuthLayer, r as T3AuthLayer, s as T3LocalAuthOriginLayer, t as AppLayer } from "./layer-
|
|
1
|
+
import { a as T3AuthTransportLayer, c as T3LocalAuthTokenLayer, i as T3AuthPairingLayer, l as T3OrchestrationLayer, n as AuthAppLayer, o as T3LocalAuthLayer, r as T3AuthLayer, s as T3LocalAuthOriginLayer, t as AppLayer } from "./layer-MqCKkIGD.js";
|
|
2
2
|
import { t as NodeEnvironmentLive } from "./layer-DHhKS5jd.js";
|
|
3
3
|
export { AppLayer, AuthAppLayer, NodeEnvironmentLive, T3AuthLayer, T3AuthPairingLayer, T3AuthTransportLayer, T3LocalAuthLayer, T3LocalAuthOriginLayer, T3LocalAuthTokenLayer, T3OrchestrationLayer };
|
|
@@ -91,7 +91,6 @@ export declare const resolveProjectScope: (snapshot: {
|
|
|
91
91
|
readonly updatedAt: string;
|
|
92
92
|
}, input: {
|
|
93
93
|
readonly ref: string;
|
|
94
|
-
readonly cwd: string;
|
|
95
94
|
}) => Effect.Effect<{
|
|
96
95
|
project: {
|
|
97
96
|
readonly id: string & import("effect/Brand").Brand<"ProjectId">;
|
|
@@ -128,7 +127,7 @@ export declare const resolveProjectScope: (snapshot: {
|
|
|
128
127
|
readonly name?: string;
|
|
129
128
|
} | null | undefined;
|
|
130
129
|
};
|
|
131
|
-
inferredWorktreePath
|
|
130
|
+
inferredWorktreePath: string;
|
|
132
131
|
} | {
|
|
133
132
|
project: {
|
|
134
133
|
readonly id: string & import("effect/Brand").Brand<"ProjectId">;
|
|
@@ -165,6 +164,6 @@ export declare const resolveProjectScope: (snapshot: {
|
|
|
165
164
|
readonly name?: string;
|
|
166
165
|
} | null | undefined;
|
|
167
166
|
};
|
|
168
|
-
inferredWorktreePath
|
|
167
|
+
inferredWorktreePath?: never;
|
|
169
168
|
} | undefined, never, Path.Path>;
|
|
170
169
|
export declare function findProjectById(snapshot: OrchestrationShellSnapshot, projectId: string): OrchestrationProjectShell | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "t3code-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CLI for t3code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -93,13 +93,15 @@
|
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@changesets/cli": "^2.31.0",
|
|
96
|
+
"@effect/vitest": "4.0.0-beta.78",
|
|
97
|
+
"@total-typescript/shoehorn": "^0.1.2",
|
|
96
98
|
"@types/node": "^25.9.1",
|
|
97
99
|
"typescript": "^6.0.3",
|
|
98
100
|
"vite-plus": "^0.1.24",
|
|
99
101
|
"@t3tools/contracts": "0.0.24"
|
|
100
102
|
},
|
|
101
103
|
"engines": {
|
|
102
|
-
"node": "
|
|
104
|
+
"node": ">=24.0.0"
|
|
103
105
|
},
|
|
104
106
|
"scripts": {
|
|
105
107
|
"build": "vp pack && tsc -p tsconfig.dts.json",
|
|
@@ -108,6 +110,7 @@
|
|
|
108
110
|
"format:check": "vp fmt --check",
|
|
109
111
|
"lint": "vp lint",
|
|
110
112
|
"lint:fix": "vp lint --fix",
|
|
113
|
+
"test": "vp test run",
|
|
111
114
|
"changeset": "changeset",
|
|
112
115
|
"release:version": "changeset version && pnpm format",
|
|
113
116
|
"release:check": "pnpm check && pnpm typecheck && pnpm pack --dry-run",
|
|
@@ -29,7 +29,6 @@ export const makeThreadApplication = Effect.fn("makeThreadApplication")(function
|
|
|
29
29
|
const snapshot = yield* orchestration.getShellSnapshot();
|
|
30
30
|
const scope = yield* resolveProjectScope(snapshot, {
|
|
31
31
|
ref: projectRef,
|
|
32
|
-
cwd: environment.cwd,
|
|
33
32
|
}).pipe(Effect.provideService(Path.Path, path));
|
|
34
33
|
if (scope === undefined) {
|
|
35
34
|
return yield* Effect.fail(
|
|
@@ -70,7 +69,6 @@ export const makeThreadApplication = Effect.fn("makeThreadApplication")(function
|
|
|
70
69
|
}
|
|
71
70
|
const scope = yield* resolveProjectScope(snapshot, {
|
|
72
71
|
ref: projectRef,
|
|
73
|
-
cwd: environment.cwd,
|
|
74
72
|
}).pipe(Effect.provideService(Path.Path, path));
|
|
75
73
|
if (scope === undefined) {
|
|
76
74
|
return yield* Effect.fail(
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import "vite-plus/test/config";
|
|
2
|
+
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
5
|
+
import { assert, describe, it } from "@effect/vitest";
|
|
6
|
+
import { fromPartial } from "@total-typescript/shoehorn";
|
|
7
|
+
|
|
8
|
+
import type { OrchestrationShellSnapshot } from "#t3tools/contracts";
|
|
9
|
+
|
|
10
|
+
import { resolveProjectScope } from "./helpers.ts";
|
|
11
|
+
|
|
12
|
+
describe("resolveProjectScope", () => {
|
|
13
|
+
it.layer(NodeServices.layer)("resolveProjectScope", (t) => {
|
|
14
|
+
t.effect("resolves by id first (even if ref not absolute)", () =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
17
|
+
projects: [{ id: "proj-1", workspaceRoot: "/workspace" }],
|
|
18
|
+
threads: [],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "proj-1" });
|
|
22
|
+
assert.equal(scope?.project.id, "proj-1");
|
|
23
|
+
assert.equal(scope?.inferredWorktreePath, undefined);
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
t.effect("returns undefined for non-absolute path refs", () =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
30
|
+
projects: [{ id: "proj-1", workspaceRoot: "/workspace" }],
|
|
31
|
+
threads: [],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "workspace/subdir" });
|
|
35
|
+
assert.equal(scope, undefined);
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
t.effect("prefers longest matching workspaceRoot", () =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
42
|
+
projects: [
|
|
43
|
+
{ id: "proj-a", workspaceRoot: "/workspace" },
|
|
44
|
+
{ id: "proj-b", workspaceRoot: "/workspace/sub" },
|
|
45
|
+
],
|
|
46
|
+
threads: [],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "/workspace/sub/deep" });
|
|
50
|
+
assert.equal(scope?.project.id, "proj-b");
|
|
51
|
+
assert.equal(scope?.inferredWorktreePath, "/workspace/sub/deep");
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
t.effect("does not infer worktree when ref equals workspaceRoot", () =>
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
58
|
+
projects: [
|
|
59
|
+
{ id: "proj-a", workspaceRoot: "/workspace" },
|
|
60
|
+
{ id: "proj-b", workspaceRoot: "/workspace/sub" },
|
|
61
|
+
],
|
|
62
|
+
threads: [],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "/workspace/sub" });
|
|
66
|
+
assert.equal(scope?.project.id, "proj-b");
|
|
67
|
+
assert.equal(scope?.inferredWorktreePath, undefined);
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
t.effect("prefers worktree candidate over project candidate for same match path", () =>
|
|
72
|
+
Effect.gen(function* () {
|
|
73
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
74
|
+
projects: [
|
|
75
|
+
{ id: "proj-a", workspaceRoot: "/workspace" },
|
|
76
|
+
{ id: "proj-b", workspaceRoot: "/workspace/proj" },
|
|
77
|
+
],
|
|
78
|
+
threads: [{ projectId: "proj-a", worktreePath: "/workspace/proj" }],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "/workspace/proj" });
|
|
82
|
+
assert.equal(scope?.project.id, "proj-a");
|
|
83
|
+
assert.equal(scope?.inferredWorktreePath, "/workspace/proj");
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
t.effect("prefers longest matching worktree path", () =>
|
|
88
|
+
Effect.gen(function* () {
|
|
89
|
+
const snapshot: OrchestrationShellSnapshot = fromPartial({
|
|
90
|
+
projects: [{ id: "proj-a", workspaceRoot: "/workspace" }],
|
|
91
|
+
threads: [
|
|
92
|
+
{ projectId: "proj-a", worktreePath: "/workspace/proj" },
|
|
93
|
+
{ projectId: "proj-a", worktreePath: "/workspace/proj/deep" },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const scope = yield* resolveProjectScope(snapshot, { ref: "/workspace/proj/deep/child" });
|
|
98
|
+
assert.equal(scope?.project.id, "proj-a");
|
|
99
|
+
assert.equal(scope?.inferredWorktreePath, "/workspace/proj/deep/child");
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/domain/helpers.ts
CHANGED
|
@@ -11,27 +11,20 @@ export const resolveProjectScope = Effect.fn("resolveProjectScope")(function* (
|
|
|
11
11
|
snapshot: OrchestrationShellSnapshot,
|
|
12
12
|
input: {
|
|
13
13
|
readonly ref: string;
|
|
14
|
-
readonly cwd: string;
|
|
15
14
|
},
|
|
16
15
|
) {
|
|
17
|
-
const
|
|
16
|
+
const path = yield* Path.Path;
|
|
18
17
|
|
|
19
18
|
const byId = findProjectById(snapshot, input.ref);
|
|
20
19
|
if (byId !== null) {
|
|
21
20
|
return { project: byId };
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return { project: byExactPath };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const byAncestor = yield* findProjectByAncestorPath(snapshot, absoluteRef, input.cwd);
|
|
30
|
-
if (byAncestor !== undefined) {
|
|
31
|
-
return byAncestor;
|
|
23
|
+
if (!path.isAbsolute(input.ref)) {
|
|
24
|
+
return undefined;
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
return yield*
|
|
27
|
+
return yield* findProjectByPathPriority(snapshot, path.normalize(input.ref));
|
|
35
28
|
});
|
|
36
29
|
|
|
37
30
|
export function findProjectById(
|
|
@@ -41,11 +34,6 @@ export function findProjectById(
|
|
|
41
34
|
return snapshot.projects.find((project) => project.id === projectId) ?? null;
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
const resolveAbsolutePath = Effect.fn("resolveAbsolutePath")(function* (ref: string, cwd: string) {
|
|
45
|
-
const path = yield* Path.Path;
|
|
46
|
-
return path.isAbsolute(ref) ? path.normalize(ref) : path.normalize(path.resolve(cwd, ref));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
37
|
const isDescendantPath = Effect.fn("isDescendantPath")(function* (parent: string, child: string) {
|
|
50
38
|
const path = yield* Path.Path;
|
|
51
39
|
if (child === parent) {
|
|
@@ -55,61 +43,23 @@ const isDescendantPath = Effect.fn("isDescendantPath")(function* (parent: string
|
|
|
55
43
|
return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
56
44
|
});
|
|
57
45
|
|
|
58
|
-
const
|
|
59
|
-
snapshot: OrchestrationShellSnapshot,
|
|
60
|
-
absolutePath: string,
|
|
61
|
-
cwd: string,
|
|
62
|
-
) {
|
|
63
|
-
for (const project of snapshot.projects) {
|
|
64
|
-
const workspaceRoot = yield* resolveAbsolutePath(project.workspaceRoot, cwd);
|
|
65
|
-
if (workspaceRoot === absolutePath) {
|
|
66
|
-
return project;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return undefined;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const findProjectByAncestorPath = Effect.fn("findProjectByAncestorPath")(function* (
|
|
46
|
+
const findProjectByPathPriority = Effect.fn("findProjectByPathPriority")(function* (
|
|
73
47
|
snapshot: OrchestrationShellSnapshot,
|
|
74
48
|
absolutePath: string,
|
|
75
|
-
cwd: string,
|
|
76
|
-
) {
|
|
77
|
-
let bestProject: OrchestrationProjectShell | undefined;
|
|
78
|
-
let bestWorkspaceRoot = "";
|
|
79
|
-
|
|
80
|
-
for (const project of snapshot.projects) {
|
|
81
|
-
const workspaceRoot = yield* resolveAbsolutePath(project.workspaceRoot, cwd);
|
|
82
|
-
if (!(yield* isDescendantPath(workspaceRoot, absolutePath))) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (workspaceRoot.length > bestWorkspaceRoot.length) {
|
|
86
|
-
bestProject = project;
|
|
87
|
-
bestWorkspaceRoot = workspaceRoot;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (bestProject === undefined) {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (absolutePath === bestWorkspaceRoot) {
|
|
96
|
-
return { project: bestProject };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
project: bestProject,
|
|
101
|
-
inferredWorktreePath: absolutePath,
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const findProjectByKnownWorktreePath = Effect.fn("findProjectByKnownWorktreePath")(function* (
|
|
106
|
-
snapshot: OrchestrationShellSnapshot,
|
|
107
|
-
absolutePath: string,
|
|
108
|
-
cwd: string,
|
|
109
49
|
) {
|
|
50
|
+
const path = yield* Path.Path;
|
|
110
51
|
const projectsById = new Map(snapshot.projects.map((project) => [project.id, project]));
|
|
111
|
-
|
|
112
|
-
|
|
52
|
+
const workspaceRoots = new Map(
|
|
53
|
+
snapshot.projects.map(
|
|
54
|
+
(project) => [project.id, path.normalize(project.workspaceRoot)] as const,
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
const candidates: Array<{
|
|
58
|
+
readonly project: OrchestrationProjectShell;
|
|
59
|
+
readonly matchPath: string;
|
|
60
|
+
readonly workspaceRoot: string;
|
|
61
|
+
readonly source: "worktree" | "project";
|
|
62
|
+
}> = [];
|
|
113
63
|
|
|
114
64
|
for (const thread of snapshot.threads) {
|
|
115
65
|
if (thread.worktreePath === null) {
|
|
@@ -119,33 +69,59 @@ const findProjectByKnownWorktreePath = Effect.fn("findProjectByKnownWorktreePath
|
|
|
119
69
|
if (project === undefined) {
|
|
120
70
|
continue;
|
|
121
71
|
}
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
72
|
+
const workspaceRoot = workspaceRoots.get(thread.projectId);
|
|
73
|
+
if (workspaceRoot === undefined) {
|
|
124
74
|
continue;
|
|
125
75
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
76
|
+
const matchPath = path.normalize(thread.worktreePath);
|
|
77
|
+
if (!(yield* isDescendantPath(matchPath, absolutePath))) {
|
|
78
|
+
continue;
|
|
129
79
|
}
|
|
80
|
+
candidates.push({
|
|
81
|
+
project,
|
|
82
|
+
matchPath,
|
|
83
|
+
workspaceRoot,
|
|
84
|
+
source: "worktree",
|
|
85
|
+
});
|
|
130
86
|
}
|
|
131
87
|
|
|
132
|
-
|
|
133
|
-
|
|
88
|
+
for (const project of snapshot.projects) {
|
|
89
|
+
const workspaceRoot = workspaceRoots.get(project.id);
|
|
90
|
+
if (workspaceRoot === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!(yield* isDescendantPath(workspaceRoot, absolutePath))) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
candidates.push({
|
|
97
|
+
project,
|
|
98
|
+
matchPath: workspaceRoot,
|
|
99
|
+
workspaceRoot,
|
|
100
|
+
source: "project",
|
|
101
|
+
});
|
|
134
102
|
}
|
|
135
103
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return { project: bestProject };
|
|
104
|
+
if (candidates.length === 0) {
|
|
105
|
+
return undefined;
|
|
139
106
|
}
|
|
140
107
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
108
|
+
candidates.sort((left, right) => {
|
|
109
|
+
if (left.matchPath.length !== right.matchPath.length) {
|
|
110
|
+
return right.matchPath.length - left.matchPath.length;
|
|
111
|
+
}
|
|
112
|
+
if (left.source === right.source) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
return left.source === "worktree" ? -1 : 1;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const best = candidates[0]!;
|
|
119
|
+
if (absolutePath === best.matchPath) {
|
|
120
|
+
if (best.source === "worktree" && best.matchPath !== best.workspaceRoot) {
|
|
121
|
+
return { project: best.project, inferredWorktreePath: best.matchPath };
|
|
122
|
+
}
|
|
123
|
+
return { project: best.project };
|
|
145
124
|
}
|
|
146
125
|
|
|
147
|
-
return {
|
|
148
|
-
project: bestProject,
|
|
149
|
-
inferredWorktreePath: absolutePath,
|
|
150
|
-
};
|
|
126
|
+
return { project: best.project, inferredWorktreePath: absolutePath };
|
|
151
127
|
});
|