@vitest-agent/sdk 3.1.2 → 4.0.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/README.md +23 -19
- package/dispatch.d.ts +25 -6
- package/formatters/markdown.js +1 -0
- package/formatters/terminal.js +4 -3
- package/index.d.ts +448 -2341
- package/index.js +10 -68
- package/internal-inject-env.js +5 -7
- package/package.json +3 -19
- package/sidecar-dispatch.js +7 -5
- package/utils/format-console.js +9 -10
- package/utils/format-terminal.js +7 -7
- package/utils/posix-path.js +52 -0
- package/utils/test-location.js +11 -5
- package/version.js +17 -0
- package/layers/ConfigLive.js +0 -46
- package/layers/DataReaderLive.js +0 -1923
- package/layers/DataStoreLive.js +0 -1287
- package/layers/DetailResolverLive.js +0 -9
- package/layers/DiscoveryRegistryLive.js +0 -70
- package/layers/EnvironmentDetectorLive.js +0 -21
- package/layers/EnvironmentDetectorTest.js +0 -13
- package/layers/ExecutorResolverLive.js +0 -9
- package/layers/FormatSelectorLive.js +0 -20
- package/layers/HistoryTrackerLive.js +0 -49
- package/layers/HistoryTrackerTest.js +0 -25
- package/layers/LoggerLive.js +0 -64
- package/layers/OutputPipelineLive.js +0 -13
- package/layers/OutputRendererLive.js +0 -28
- package/layers/PathResolutionLive.js +0 -40
- package/layers/PerClientSessionMapLive.js +0 -145
- package/layers/ProjectDiscoveryLive.js +0 -68
- package/layers/ProjectDiscoveryTest.js +0 -12
- package/layers/ProjectIdentityLive.js +0 -96
- package/layers/RunContextLive.js +0 -81
- package/lib/format-triage.js +0 -103
- package/lib/format-wrapup.js +0 -67
- package/migrations/0001_initial.js +0 -824
- package/migrations/0002_test_artifacts.js +0 -30
- package/migrations/index.js +0 -26
- package/migrations/registry_0001_initial.js +0 -39
- package/migrations/session_map_0001_initial.js +0 -38
- package/services/Config.js +0 -15
- package/services/DataReader.js +0 -8
- package/services/DataStore.js +0 -15
- package/services/DetailResolver.js +0 -8
- package/services/DiscoveryRegistry.js +0 -8
- package/services/EnvironmentDetector.js +0 -8
- package/services/ExecutorResolver.js +0 -8
- package/services/FormatSelector.js +0 -8
- package/services/HistoryTracker.js +0 -20
- package/services/OutputRenderer.js +0 -8
- package/services/PerClientSessionMap.js +0 -22
- package/services/ProjectDiscovery.js +0 -8
- package/services/ProjectIdentity.js +0 -63
- package/services/RunContext.js +0 -53
- package/services/idempotency.js +0 -58
- package/sql/assemblers.js +0 -69
- package/testing/layers.js +0 -21
- package/testing.d.ts +0 -1862
- package/testing.js +0 -254
- package/utils/ensure-migrated.js +0 -52
- package/utils/failure-signature.js +0 -34
- package/utils/resolve-data-path.js +0 -65
- package/utils/resolve-project-key-from-cwd.js +0 -53
- package/utils/resolve-workspace-key.js +0 -41
package/testing.js
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { AgentNotFoundError, RegistrationConflictError } from "./errors/AgentErrors.js";
|
|
2
|
-
import { DataStoreError } from "./errors/DataStoreError.js";
|
|
3
|
-
import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
|
|
4
|
-
import { DataReader } from "./services/DataReader.js";
|
|
5
|
-
import { AgentId, ChatId } from "./schemas/Identity.js";
|
|
6
|
-
import { Agent, IdempotencyHit } from "./schemas/Agent.js";
|
|
7
|
-
import { DataStore } from "./services/DataStore.js";
|
|
8
|
-
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
9
|
-
import { CoverageReport, FileCoverageReport } from "./schemas/Coverage.js";
|
|
10
|
-
import { AgentReport } from "./schemas/AgentReport.js";
|
|
11
|
-
import { CoverageBaselines } from "./schemas/Baselines.js";
|
|
12
|
-
import { CacheManifest } from "./schemas/CacheManifest.js";
|
|
13
|
-
import { HistoryRecord } from "./schemas/History.js";
|
|
14
|
-
import { BehaviorDetail, BehaviorRow, BehaviorStatus, GoalDetail, GoalRow, GoalStatus } from "./schemas/Tdd.js";
|
|
15
|
-
import { TrendEntry, TrendRecord } from "./schemas/Trends.js";
|
|
16
|
-
import { DataStoreTestLayer, makeTestLayer } from "./testing/layers.js";
|
|
17
|
-
import { Effect, Layer } from "effect";
|
|
18
|
-
|
|
19
|
-
//#region src/testing/index.ts
|
|
20
|
-
/** @public */
|
|
21
|
-
function empty(filename) {
|
|
22
|
-
return makeTestLayer(filename);
|
|
23
|
-
}
|
|
24
|
-
/** @public */
|
|
25
|
-
function singlePassingRun(filename) {
|
|
26
|
-
const base = makeTestLayer(filename);
|
|
27
|
-
const seed = Effect.gen(function* () {
|
|
28
|
-
const store = yield* DataStore;
|
|
29
|
-
yield* store.writeSettings("hash-preset-spr", {
|
|
30
|
-
vitestVersion: "5.0.0",
|
|
31
|
-
pool: "forks"
|
|
32
|
-
}, {});
|
|
33
|
-
const runId = yield* store.writeRun({
|
|
34
|
-
invocationId: "inv-preset-spr-1",
|
|
35
|
-
project: "default",
|
|
36
|
-
settingsHash: "hash-preset-spr",
|
|
37
|
-
timestamp: "2026-01-01T00:00:00.000Z",
|
|
38
|
-
commitSha: null,
|
|
39
|
-
branch: null,
|
|
40
|
-
reason: "passed",
|
|
41
|
-
duration: 1200,
|
|
42
|
-
total: 3,
|
|
43
|
-
passed: 3,
|
|
44
|
-
failed: 0,
|
|
45
|
-
skipped: 0,
|
|
46
|
-
scoped: false
|
|
47
|
-
});
|
|
48
|
-
const fileId = yield* store.ensureFile("src/math.test.ts");
|
|
49
|
-
const [moduleId] = yield* store.writeModules(runId, [{
|
|
50
|
-
fileId,
|
|
51
|
-
relativeModuleId: "src/math.test.ts",
|
|
52
|
-
state: "passed",
|
|
53
|
-
duration: 1200
|
|
54
|
-
}]);
|
|
55
|
-
yield* store.writeTestCases(moduleId, [
|
|
56
|
-
{
|
|
57
|
-
name: "adds two numbers",
|
|
58
|
-
fullName: "math > adds two numbers",
|
|
59
|
-
state: "passed",
|
|
60
|
-
duration: 400
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "subtracts two numbers",
|
|
64
|
-
fullName: "math > subtracts two numbers",
|
|
65
|
-
state: "passed",
|
|
66
|
-
duration: 400
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "multiplies two numbers",
|
|
70
|
-
fullName: "math > multiplies two numbers",
|
|
71
|
-
state: "passed",
|
|
72
|
-
duration: 400
|
|
73
|
-
}
|
|
74
|
-
]);
|
|
75
|
-
});
|
|
76
|
-
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
77
|
-
}
|
|
78
|
-
/** @public */
|
|
79
|
-
function withFailures(filename) {
|
|
80
|
-
const base = makeTestLayer(filename);
|
|
81
|
-
const seed = Effect.gen(function* () {
|
|
82
|
-
const store = yield* DataStore;
|
|
83
|
-
yield* store.writeSettings("hash-preset-wf", {
|
|
84
|
-
vitestVersion: "5.0.0",
|
|
85
|
-
pool: "forks"
|
|
86
|
-
}, {});
|
|
87
|
-
const runId = yield* store.writeRun({
|
|
88
|
-
invocationId: "inv-preset-wf-1",
|
|
89
|
-
project: "default",
|
|
90
|
-
settingsHash: "hash-preset-wf",
|
|
91
|
-
timestamp: "2026-01-01T00:00:00.000Z",
|
|
92
|
-
commitSha: null,
|
|
93
|
-
branch: null,
|
|
94
|
-
reason: "failed",
|
|
95
|
-
duration: 1500,
|
|
96
|
-
total: 4,
|
|
97
|
-
passed: 2,
|
|
98
|
-
failed: 2,
|
|
99
|
-
skipped: 0,
|
|
100
|
-
scoped: false
|
|
101
|
-
});
|
|
102
|
-
const fileId = yield* store.ensureFile("src/parser.test.ts");
|
|
103
|
-
const [moduleId] = yield* store.writeModules(runId, [{
|
|
104
|
-
fileId,
|
|
105
|
-
relativeModuleId: "src/parser.test.ts",
|
|
106
|
-
state: "failed",
|
|
107
|
-
duration: 1500
|
|
108
|
-
}]);
|
|
109
|
-
yield* store.writeTestCases(moduleId, [
|
|
110
|
-
{
|
|
111
|
-
name: "parses valid input",
|
|
112
|
-
fullName: "parser > parses valid input",
|
|
113
|
-
state: "passed",
|
|
114
|
-
duration: 300
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "parses nested structure",
|
|
118
|
-
fullName: "parser > parses nested structure",
|
|
119
|
-
state: "passed",
|
|
120
|
-
duration: 300
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: "rejects empty input",
|
|
124
|
-
fullName: "parser > rejects empty input",
|
|
125
|
-
state: "failed",
|
|
126
|
-
duration: 450
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "rejects malformed input",
|
|
130
|
-
fullName: "parser > rejects malformed input",
|
|
131
|
-
state: "failed",
|
|
132
|
-
duration: 450
|
|
133
|
-
}
|
|
134
|
-
]);
|
|
135
|
-
});
|
|
136
|
-
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
137
|
-
}
|
|
138
|
-
/** @public */
|
|
139
|
-
function flaky(filename) {
|
|
140
|
-
const base = makeTestLayer(filename);
|
|
141
|
-
const seed = Effect.gen(function* () {
|
|
142
|
-
const store = yield* DataStore;
|
|
143
|
-
yield* store.writeSettings("hash-preset-flaky", {
|
|
144
|
-
vitestVersion: "5.0.0",
|
|
145
|
-
pool: "forks"
|
|
146
|
-
}, {});
|
|
147
|
-
const runId1 = yield* store.writeRun({
|
|
148
|
-
invocationId: "inv-preset-flaky-1",
|
|
149
|
-
project: "default",
|
|
150
|
-
settingsHash: "hash-preset-flaky",
|
|
151
|
-
timestamp: "2026-01-01T00:00:00.000Z",
|
|
152
|
-
commitSha: null,
|
|
153
|
-
branch: null,
|
|
154
|
-
reason: "failed",
|
|
155
|
-
duration: 800,
|
|
156
|
-
total: 2,
|
|
157
|
-
passed: 1,
|
|
158
|
-
failed: 1,
|
|
159
|
-
skipped: 0,
|
|
160
|
-
scoped: false
|
|
161
|
-
});
|
|
162
|
-
const fileId = yield* store.ensureFile("src/async.test.ts");
|
|
163
|
-
const [moduleId1] = yield* store.writeModules(runId1, [{
|
|
164
|
-
fileId,
|
|
165
|
-
relativeModuleId: "src/async.test.ts",
|
|
166
|
-
state: "failed",
|
|
167
|
-
duration: 800
|
|
168
|
-
}]);
|
|
169
|
-
yield* store.writeTestCases(moduleId1, [{
|
|
170
|
-
name: "resolves within timeout",
|
|
171
|
-
fullName: "async > resolves within timeout",
|
|
172
|
-
state: "failed",
|
|
173
|
-
duration: 500
|
|
174
|
-
}, {
|
|
175
|
-
name: "rejects on error",
|
|
176
|
-
fullName: "async > rejects on error",
|
|
177
|
-
state: "passed",
|
|
178
|
-
duration: 300
|
|
179
|
-
}]);
|
|
180
|
-
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2025-12-31T23:00:00.000Z", "passed", 500, false, 0, null);
|
|
181
|
-
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "failed", 500, false, 0, null);
|
|
182
|
-
yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "passed", 300, false, 0, null);
|
|
183
|
-
const runId2 = yield* store.writeRun({
|
|
184
|
-
invocationId: "inv-preset-flaky-2",
|
|
185
|
-
project: "default",
|
|
186
|
-
settingsHash: "hash-preset-flaky",
|
|
187
|
-
timestamp: "2026-01-01T01:00:00.000Z",
|
|
188
|
-
commitSha: null,
|
|
189
|
-
branch: null,
|
|
190
|
-
reason: "passed",
|
|
191
|
-
duration: 700,
|
|
192
|
-
total: 2,
|
|
193
|
-
passed: 2,
|
|
194
|
-
failed: 0,
|
|
195
|
-
skipped: 0,
|
|
196
|
-
scoped: false
|
|
197
|
-
});
|
|
198
|
-
const [moduleId2] = yield* store.writeModules(runId2, [{
|
|
199
|
-
fileId,
|
|
200
|
-
relativeModuleId: "src/async.test.ts",
|
|
201
|
-
state: "passed",
|
|
202
|
-
duration: 700
|
|
203
|
-
}]);
|
|
204
|
-
yield* store.writeTestCases(moduleId2, [{
|
|
205
|
-
name: "resolves within timeout",
|
|
206
|
-
fullName: "async > resolves within timeout",
|
|
207
|
-
state: "passed",
|
|
208
|
-
duration: 400
|
|
209
|
-
}, {
|
|
210
|
-
name: "rejects on error",
|
|
211
|
-
fullName: "async > rejects on error",
|
|
212
|
-
state: "passed",
|
|
213
|
-
duration: 300
|
|
214
|
-
}]);
|
|
215
|
-
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 400, false, 0, null);
|
|
216
|
-
yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 300, false, 0, null);
|
|
217
|
-
});
|
|
218
|
-
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
219
|
-
}
|
|
220
|
-
/** @public */
|
|
221
|
-
function withTddTask(filename) {
|
|
222
|
-
const base = makeTestLayer(filename);
|
|
223
|
-
const seed = Effect.gen(function* () {
|
|
224
|
-
const store = yield* DataStore;
|
|
225
|
-
const sessionId = yield* store.writeSession({
|
|
226
|
-
chatId: "chat-preset-tdd",
|
|
227
|
-
project: "default",
|
|
228
|
-
cwd: "/workspace",
|
|
229
|
-
agentKind: "main",
|
|
230
|
-
startedAt: "2026-01-01T00:00:00.000Z"
|
|
231
|
-
});
|
|
232
|
-
const tddTaskId = yield* store.writeTddTask({
|
|
233
|
-
sessionId,
|
|
234
|
-
goal: "Implement the parser feature",
|
|
235
|
-
startedAt: "2026-01-01T00:00:00.000Z"
|
|
236
|
-
});
|
|
237
|
-
const goal = yield* store.createGoal({
|
|
238
|
-
tddTaskId,
|
|
239
|
-
goal: "Handle edge cases"
|
|
240
|
-
});
|
|
241
|
-
yield* store.createBehavior({
|
|
242
|
-
goalId: goal.id,
|
|
243
|
-
behavior: "rejects empty input"
|
|
244
|
-
});
|
|
245
|
-
yield* store.createBehavior({
|
|
246
|
-
goalId: goal.id,
|
|
247
|
-
behavior: "rejects null input"
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
//#endregion
|
|
254
|
-
export { Agent, AgentId, AgentNotFoundError, AgentReport, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, ChatId, CoverageBaselines, CoverageReport, DataReader, DataStore, DataStoreError, DataStoreTestLayer, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, PROJECT_MIGRATIONS, RegistrationConflictError, TddTaskAlreadyEndedError, TddTaskNotFoundError, TrendEntry, TrendRecord, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
|
package/utils/ensure-migrated.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { LoggerLive } from "../layers/LoggerLive.js";
|
|
2
|
-
import { PROJECT_MIGRATIONS } from "../migrations/index.js";
|
|
3
|
-
import { Effect, Layer } from "effect";
|
|
4
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
5
|
-
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
6
|
-
import { layer } from "@effect/sql-sqlite-node/SqliteClient";
|
|
7
|
-
import * as SqliteMigrator from "@effect/sql-sqlite-node/SqliteMigrator";
|
|
8
|
-
|
|
9
|
-
//#region src/utils/ensure-migrated.ts
|
|
10
|
-
const GLOBAL_KEY = Symbol.for("vitest-agent/migration-promises");
|
|
11
|
-
const getCache = () => {
|
|
12
|
-
const g = globalThis;
|
|
13
|
-
let cache = g[GLOBAL_KEY];
|
|
14
|
-
if (!cache) {
|
|
15
|
-
cache = /* @__PURE__ */ new Map();
|
|
16
|
-
g[GLOBAL_KEY] = cache;
|
|
17
|
-
}
|
|
18
|
-
return cache;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Ensure the SQLite database at `dbPath` is migrated. Runs migrations at
|
|
22
|
-
* most once per dbPath in the current process; concurrent calls share the
|
|
23
|
-
* same in-flight promise. Subsequent calls (after the first resolves) are
|
|
24
|
-
* no-ops.
|
|
25
|
-
* @public
|
|
26
|
-
*/
|
|
27
|
-
function ensureMigrated(dbPath, logLevel, logFile) {
|
|
28
|
-
const cache = getCache();
|
|
29
|
-
const cached = cache.get(dbPath);
|
|
30
|
-
if (cached) return cached;
|
|
31
|
-
const SqliteLayer = layer({ filename: dbPath });
|
|
32
|
-
const PlatformLayer = NodeServices.layer;
|
|
33
|
-
const MigratorLayer = SqliteMigrator.layer({ loader: SqliteMigrator.fromRecord(PROJECT_MIGRATIONS) }).pipe(Layer.provide(Layer.merge(SqliteLayer, PlatformLayer)));
|
|
34
|
-
const program = Effect.gen(function* () {
|
|
35
|
-
yield* SqlClient;
|
|
36
|
-
}).pipe(Effect.provide(MigratorLayer), Effect.provide(Layer.merge(SqliteLayer, PlatformLayer)), Effect.provide(LoggerLive(logLevel, logFile)));
|
|
37
|
-
const promise = Effect.runPromise(program);
|
|
38
|
-
cache.set(dbPath, promise);
|
|
39
|
-
promise.catch(() => {});
|
|
40
|
-
return promise;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Reset the migration cache. Test-only.
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
function _resetMigrationCacheForTesting() {
|
|
48
|
-
getCache().clear();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
|
-
export { _resetMigrationCacheForTesting, ensureMigrated };
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
//#region src/utils/failure-signature.ts
|
|
4
|
-
const ASSERTION_REGEX = /(toBe|toEqual|toContain|toMatch|toThrow|toBeNull|toBeUndefined|toBeTruthy|toBeFalsy|toBeGreaterThan|toBeLessThan|toBeInstanceOf|toBeCloseTo|toHaveBeenCalled|toHaveBeenCalledWith|toHaveProperty|toHaveLength)\s*\(([^)]*)\)/;
|
|
5
|
-
const typeTagOf = (literal) => {
|
|
6
|
-
const trimmed = literal.trim();
|
|
7
|
-
if (trimmed === "" || trimmed === ")") return "";
|
|
8
|
-
if (/^\d+(\.\d+)?$/.test(trimmed)) return "<number>";
|
|
9
|
-
if (/^["'`]/.test(trimmed)) return "<string>";
|
|
10
|
-
if (/^(true|false)$/.test(trimmed)) return "<boolean>";
|
|
11
|
-
if (/^null$/.test(trimmed)) return "<null>";
|
|
12
|
-
if (/^undefined$/.test(trimmed)) return "<undefined>";
|
|
13
|
-
if (/^[{[]/.test(trimmed)) return "<object>";
|
|
14
|
-
return "<expr>";
|
|
15
|
-
};
|
|
16
|
-
/** @public */
|
|
17
|
-
const normalizeAssertionShape = (message) => {
|
|
18
|
-
if (typeof message !== "string") return "";
|
|
19
|
-
const match = message.match(ASSERTION_REGEX);
|
|
20
|
-
if (match === null) return null;
|
|
21
|
-
const matcher = match[1];
|
|
22
|
-
const arg = typeTagOf(match[2]);
|
|
23
|
-
return arg === "" ? matcher : `${matcher}(${arg})`;
|
|
24
|
-
};
|
|
25
|
-
/** @public */
|
|
26
|
-
const computeFailureSignature = (input) => {
|
|
27
|
-
const shape = normalizeAssertionShape(input.assertion_message) ?? "<unknown>";
|
|
28
|
-
const lineCoord = input.top_frame_function_boundary_line !== null ? `fb:${input.top_frame_function_boundary_line}` : input.top_frame_raw_line !== void 0 ? `raw:${Math.floor(input.top_frame_raw_line / 10) * 10}` : "raw:?";
|
|
29
|
-
const key = `${input.error_name}|${shape}|${input.top_frame_function_name}|${lineCoord}`;
|
|
30
|
-
return createHash("sha256").update(key).digest("hex").substring(0, 16);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
export { computeFailureSignature, normalizeAssertionShape };
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { VitestAgentConfig } from "../schemas/Config.js";
|
|
2
|
-
import { VitestAgentConfigFile } from "../services/Config.js";
|
|
3
|
-
import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
|
|
4
|
-
import { resolveProjectKeyFromCwd } from "./resolve-project-key-from-cwd.js";
|
|
5
|
-
import { Effect, FileSystem } from "effect";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { AppDirs } from "@effected/xdg";
|
|
8
|
-
|
|
9
|
-
//#region src/utils/resolve-data-path.ts
|
|
10
|
-
/**
|
|
11
|
-
* Filename of the SQLite database that stores all reporter data.
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
const DATABASE_FILENAME = "data.db";
|
|
15
|
-
/**
|
|
16
|
-
* Resolve the absolute path to the SQLite database for `projectDir`.
|
|
17
|
-
*
|
|
18
|
-
* The directory containing the database is ensured to exist before the
|
|
19
|
-
* function returns, so callers can open the database immediately without
|
|
20
|
-
* needing to create parent directories. The SQLite driver creates the file
|
|
21
|
-
* but not its parent.
|
|
22
|
-
*
|
|
23
|
-
* Precedence (highest first):
|
|
24
|
-
*
|
|
25
|
-
* 1. `options.cacheDir` — programmatic override.
|
|
26
|
-
* 2. `cacheDir` from `vitest-agent.config.toml`.
|
|
27
|
-
* 3. `<XDG data>/<normalized projectKey from config>/data.db`.
|
|
28
|
-
* 4. `<XDG data>/<projectKey resolved from cwd's package.json>/data.db`.
|
|
29
|
-
* Source 4 prefers `repository.url` (canonicalized to `host__path`),
|
|
30
|
-
* falling back to the normalized `name`. This matches the sidecar
|
|
31
|
-
* CLI's `_internal register-agent` resolver so the reporter and
|
|
32
|
-
* sidecar always write to the same `data.db`.
|
|
33
|
-
*
|
|
34
|
-
* The XDG data directory is namespaced via `AppDirs` (typically
|
|
35
|
-
* `$XDG_DATA_HOME/vitest-agent`, defaulting to
|
|
36
|
-
* `~/.local/share/vitest-agent`).
|
|
37
|
-
*
|
|
38
|
-
* The path is a function of identity, not filesystem layout. When no
|
|
39
|
-
* `package.json` is reachable from `projectDir`, the cwd basename is used
|
|
40
|
-
* as the final fallback so the function never throws on identity lookup.
|
|
41
|
-
*
|
|
42
|
-
* @param projectDir - Absolute path inside the user's workspace.
|
|
43
|
-
* @param options - Optional programmatic overrides.
|
|
44
|
-
* @public
|
|
45
|
-
*/
|
|
46
|
-
const resolveDataPath = (projectDir, options = {}) => Effect.gen(function* () {
|
|
47
|
-
const fs = yield* FileSystem.FileSystem;
|
|
48
|
-
if (options.cacheDir) {
|
|
49
|
-
yield* fs.makeDirectory(options.cacheDir, { recursive: true });
|
|
50
|
-
return join(options.cacheDir, DATABASE_FILENAME);
|
|
51
|
-
}
|
|
52
|
-
const loaded = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({}));
|
|
53
|
-
if (loaded.cacheDir) {
|
|
54
|
-
yield* fs.makeDirectory(loaded.cacheDir, { recursive: true });
|
|
55
|
-
return join(loaded.cacheDir, DATABASE_FILENAME);
|
|
56
|
-
}
|
|
57
|
-
const dataRoot = yield* (yield* AppDirs).ensureData;
|
|
58
|
-
const key = loaded.projectKey ? normalizeWorkspaceKey(loaded.projectKey) : resolveProjectKeyFromCwd(projectDir);
|
|
59
|
-
const dir = join(dataRoot, key);
|
|
60
|
-
yield* fs.makeDirectory(dir, { recursive: true });
|
|
61
|
-
return join(dir, DATABASE_FILENAME);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
//#endregion
|
|
65
|
-
export { DATABASE_FILENAME, resolveDataPath };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { gitUrlToProjectKey } from "./canonicalize-git-url.js";
|
|
2
|
-
import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
|
|
6
|
-
//#region src/utils/resolve-project-key-from-cwd.ts
|
|
7
|
-
const PACKAGE_JSON = "package.json";
|
|
8
|
-
const findNearestPackageJson = (startDir) => {
|
|
9
|
-
let dir = startDir;
|
|
10
|
-
while (true) {
|
|
11
|
-
const candidate = join(dir, PACKAGE_JSON);
|
|
12
|
-
if (existsSync(candidate)) return candidate;
|
|
13
|
-
const parent = dirname(dir);
|
|
14
|
-
if (parent === dir) return null;
|
|
15
|
-
dir = parent;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const readRepositoryUrl = (parsed) => {
|
|
19
|
-
const repo = parsed.repository;
|
|
20
|
-
if (typeof repo === "string" && repo.trim().length > 0) return repo.trim();
|
|
21
|
-
if (repo !== null && typeof repo === "object" && "url" in repo && typeof repo.url === "string") {
|
|
22
|
-
const url = repo.url.trim();
|
|
23
|
-
return url.length > 0 ? url : null;
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Compute the project key for a workspace by reading `package.json`
|
|
29
|
-
* fields directly. Returns the canonical `host__path` form when a git
|
|
30
|
-
* remote URL is present, otherwise the normalized package name.
|
|
31
|
-
*
|
|
32
|
-
* Always returns a non-empty string — falls back to the cwd basename
|
|
33
|
-
* (or `"anonymous-project"`) so callers don't have to handle the
|
|
34
|
-
* empty case.
|
|
35
|
-
* @public
|
|
36
|
-
*/
|
|
37
|
-
const resolveProjectKeyFromCwd = (cwd) => {
|
|
38
|
-
const pkgPath = findNearestPackageJson(cwd);
|
|
39
|
-
if (pkgPath !== null) try {
|
|
40
|
-
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
41
|
-
const repoUrl = readRepositoryUrl(parsed);
|
|
42
|
-
if (repoUrl !== null) {
|
|
43
|
-
const key = gitUrlToProjectKey(repoUrl);
|
|
44
|
-
if (key !== null) return key;
|
|
45
|
-
}
|
|
46
|
-
if (typeof parsed.name === "string" && parsed.name.length > 0) return normalizeWorkspaceKey(parsed.name);
|
|
47
|
-
} catch {}
|
|
48
|
-
const basename = cwd.split("/").filter((segment) => segment.length > 0).pop() ?? "anonymous-project";
|
|
49
|
-
return normalizeWorkspaceKey(basename);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
//#endregion
|
|
53
|
-
export { resolveProjectKeyFromCwd };
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
|
-
import { WorkspaceDiscovery, WorkspaceRootNotFoundError } from "@effected/workspaces";
|
|
4
|
-
|
|
5
|
-
//#region src/utils/resolve-workspace-key.ts
|
|
6
|
-
/**
|
|
7
|
-
* Resolve the normalized workspace key for the workspace containing
|
|
8
|
-
* `projectDir`.
|
|
9
|
-
*
|
|
10
|
-
* The key is the root `package.json`'s `name`, normalized via
|
|
11
|
-
* `normalizeWorkspaceKey`. This becomes the directory segment under
|
|
12
|
-
* `$XDG_DATA_HOME/vitest-agent/` where the SQLite database lives.
|
|
13
|
-
*
|
|
14
|
-
* Fails with `WorkspaceRootNotFoundError` when no root workspace is
|
|
15
|
-
* discoverable. `WorkspacePackage.name` is enforced non-empty by
|
|
16
|
-
* `@effected/workspaces`'s schema, so a successful root lookup always yields a
|
|
17
|
-
* usable name.
|
|
18
|
-
*
|
|
19
|
-
* Discovery is anchored at `projectDir` per call via `listPackagesIn`, not at
|
|
20
|
-
* the `WorkspaceDiscovery` layer's `cwd`: patterns, member manifests and names
|
|
21
|
-
* are read from beneath `projectDir`'s own root, so one long-lived layer
|
|
22
|
-
* answers correctly for a git worktree, a nested repository, or an unrelated
|
|
23
|
-
* project. Fails with the library's `WorkspaceRootNotFoundError` when
|
|
24
|
-
* `projectDir` sits in no workspace at all; results are memoized per resolved
|
|
25
|
-
* root, so many directories in one workspace share a single discovery.
|
|
26
|
-
*
|
|
27
|
-
* @param projectDir - Absolute path inside the workspace. Typically the
|
|
28
|
-
* reporter's resolved `projectDir` (CLAUDE_PROJECT_DIR or process.cwd()).
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
const resolveWorkspaceKey = (projectDir) => Effect.gen(function* () {
|
|
32
|
-
const root = (yield* (yield* WorkspaceDiscovery).listPackagesIn(projectDir)).find((pkg) => pkg.isRootWorkspace);
|
|
33
|
-
if (!root) return yield* Effect.fail(new WorkspaceRootNotFoundError({
|
|
34
|
-
searchPath: projectDir,
|
|
35
|
-
markers: []
|
|
36
|
-
}));
|
|
37
|
-
return normalizeWorkspaceKey(root.name);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
//#endregion
|
|
41
|
-
export { resolveWorkspaceKey };
|